{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":117682,"databundleVersionId":15062069},{"sourceType":"modelInstanceVersion","sourceId":732880,"databundleVersionId":15477237,"modelInstanceId":516822,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":672178,"databundleVersionId":14761443,"modelInstanceId":495238,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":681152,"databundleVersionId":14910215,"modelInstanceId":516822,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":665924,"databundleVersionId":14691066,"modelInstanceId":504051,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":674747,"databundleVersionId":14811492,"modelInstanceId":503784,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":665589,"databundleVersionId":14687610,"modelInstanceId":503784,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":660383,"databundleVersionId":14626934,"modelInstanceId":499479,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":673516,"databundleVersionId":14789938,"modelInstanceId":499479,"modelId":510647},{"sourceType":"modelInstanceVersion","sourceId":655294,"databundleVersionId":14570138,"modelInstanceId":495238,"modelId":510647},{"sourceType":"kernelVersion","sourceId":290917305}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":227.91671,"end_time":"2026-02-03T18:40:23.43662","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-02-03T18:36:35.51991","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Inference","metadata":{"papermill":{"duration":0.004389,"end_time":"2026-02-03T18:36:41.203211","exception":false,"start_time":"2026-02-03T18:36:41.198822","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"1\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T04:53:28.035519Z","iopub.execute_input":"2026-02-12T04:53:28.035749Z","iopub.status.idle":"2026-02-12T04:53:29.006804Z","shell.execute_reply.started":"2026-02-12T04:53:28.035725Z","shell.execute_reply":"2026-02-12T04:53:29.006015Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import clear_output\n\nvar=\"/kaggle/input/vsdetection-packages-offline-installer-only/whls\"\n!pip install \\\n  \"$var\"/keras_nightly-*.whl \\\n  \"$var\"/tifffile-*.whl \\\n  \"$var\"/imagecodecs-*.whl \\\n  \"$var\"/medicai-*.whl \\\n  --no-index \\\n  --find-links \"$var\"\n\nclear_output()","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2026-02-12T04:53:29.009244Z","iopub.execute_input":"2026-02-12T04:53:29.009606Z","iopub.status.idle":"2026-02-12T04:53:36.266487Z","shell.execute_reply.started":"2026-02-12T04:53:29.009584Z","shell.execute_reply":"2026-02-12T04:53:36.265831Z"},"papermill":{"duration":9.707772,"end_time":"2026-02-03T18:36:50.914562","exception":false,"start_time":"2026-02-03T18:36:41.20679","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\n\nimport keras\nfrom medicai.transforms import (\n    Compose,\n    ScaleIntensityRange,\n    NormalizeIntensity\n)\nfrom medicai.models import SegFormer, TransUNet\nfrom medicai.utils.inference import SlidingWindowInference\n\nimport numpy as np\nimport pandas as pd\nimport zipfile\nimport tifffile\nimport scipy.ndimage as ndi\nfrom skimage.morphology import remove_small_objects\nfrom matplotlib import pyplot as plt\n\nkeras.config.backend(), keras.version()","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2026-02-12T04:53:36.26743Z","iopub.execute_input":"2026-02-12T04:53:36.267684Z","iopub.status.idle":"2026-02-12T04:53:50.384887Z","shell.execute_reply.started":"2026-02-12T04:53:36.267657Z","shell.execute_reply":"2026-02-12T04:53:50.384288Z"},"papermill":{"duration":26.183152,"end_time":"2026-02-03T18:37:17.101495","exception":false,"start_time":"2026-02-03T18:36:50.918343","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Dataset**","metadata":{"papermill":{"duration":0.004075,"end_time":"2026-02-03T18:37:17.109927","exception":false,"start_time":"2026-02-03T18:37:17.105852","status":"completed"},"tags":[]}},{"cell_type":"code","source":"root_dir = \"/kaggle/input/vesuvius-challenge-surface-detection\"\ntest_dir = f\"{root_dir}/test_images\"\noutput_dir = \"/kaggle/working/submission_masks\"\nzip_path = \"/kaggle/working/submission.zip\"\nos.makedirs(output_dir, exist_ok=True)","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.385559Z","iopub.execute_input":"2026-02-12T04:53:50.386489Z","iopub.status.idle":"2026-02-12T04:53:50.389983Z","shell.execute_reply.started":"2026-02-12T04:53:50.386462Z","shell.execute_reply":"2026-02-12T04:53:50.389301Z"},"papermill":{"duration":0.010475,"end_time":"2026-02-03T18:37:17.124303","exception":false,"start_time":"2026-02-03T18:37:17.113828","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(f\"{root_dir}/test.csv\")\ntest_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.390746Z","iopub.execute_input":"2026-02-12T04:53:50.391889Z","iopub.status.idle":"2026-02-12T04:53:50.829856Z","shell.execute_reply.started":"2026-02-12T04:53:50.391869Z","shell.execute_reply":"2026-02-12T04:53:50.829273Z"},"papermill":{"duration":0.05346,"end_time":"2026-02-03T18:37:17.181766","exception":false,"start_time":"2026-02-03T18:37:17.128306","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Transformation**","metadata":{"papermill":{"duration":0.004308,"end_time":"2026-02-03T18:37:17.190807","exception":false,"start_time":"2026-02-03T18:37:17.186499","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Vesuvius Surface Detection — inference-only upgrade block\n# Drop this into your Kaggle notebook (after imports) and replace your predict_with_tta / postprocess calls.\n#\n# What this block does:\n# 1) Returns TRUE surface probability maps from TTA (instead of argmax labels)\n# 2) Optional “surfaceness” enhancement via multi-axis 2D Frangi (host baseline idea)\n# 3) Clean, ablation-friendly postprocess wrapper (threshold + dust + optional closing)\n#\n# Attribution:\n# - Host baseline discussion: threshold 0.75 + surfaceness (Frangi-like) postprocess (see Kaggle discussion 666453)\n# - Using skimage.filters.frangi for 2D Frangi responses\n\nimport numpy as np\nimport time\n\n# (These are used later for zip writing / TIFF serialization)\nimport zipfile\nimport tifffile\n\ntry:\n    from skimage.filters import frangi\n    from skimage.measure import label\nexcept Exception:\n    frangi = None\n    label = None\n\ntry:\n    import scipy.ndimage as ndi\nexcept Exception:\n    ndi = None\n\n\n# ----------------------------\n# Config — tweak in one place\n# ----------------------------\n# ============================================================\n# RUN LOG (keep this updated to avoid re-testing the same idea)\n# ------------------------------------------------------------\n# Format:\n#   - date: YYYY-MM-DD\n#     run: short_name\n#     score: 0.xxx (public)\n#     runtime: ~XmYs (save&runall)\n#     delta: one-line description of what changed vs baseline\n#     cfg:\n#       KEY: VALUE   (only the keys you changed)\n#     notes: anything important (e.g., \"timed out\", \"seeded hyst\", \"rescore day\")\n#\n# BASELINE REFERENCE (do NOT resubmit):\n#   MODE=\"quantile\", HYST_BAND=0.0, USE_FRANGI=False\n#   TTA_MODE=\"full\" (flips (1,2,3), rots (1,2,3)), TTA_AGG=\"mean\"\n#   Z_RADIUS=3, XY_RADIUS=2, DUST_MIN_SIZE=200\n#   LOGIT_TEMP=1.00, PROB_GAMMA=1.00\n#   Typical public score plateau: ~0.551\n#\n# RECENT RESULTS SUMMARY (fill in dates as you like):\n#   - run: median_tta          -> 0.529 (hurt a lot)\n#   - run: gamma_0.85          -> 0.551 (plateau)\n#   - run: gamma_1.25          -> 0.551 (plateau)\n#   - run: fixed+frangi_on_bin -> 0.542 (hurt)\n#   - run: logit_temp_1.15     -> 0.551 (sometimes ranks above other plateau runs)\n#\n# After each submission, append a new bullet below.\n# ============================================================\n\nCFG = {\n    # Thresholding / hysteresis\n    # If T_LOW == T_HIGH, this becomes a simple threshold at T_LOW.\n    \"T_LOW\": 0.75,\n    \"T_HIGH\": 0.75,\n\n    # Remove tiny islands (helps VOI/topology noise)\n    \"DUST_MIN_SIZE\": 200,\n\n    # Optional very light closing (use carefully; can merge nearby sheets)\n    \"USE_CLOSING\": False,\n    \"CLOSING_KERNEL\": (3, 3, 3),\n\n    # ----------------------------\n    # Frangi / surfaceness enhancement\n    # ----------------------------\n    \"USE_FRANGI\": True,\n\n    # \"2d\" = multi-axis 2D Frangi per-slice (skimage)\n    # \"3d\" = true 3D Hessian sheetness (custom; closer to host intent)\n    \"FRANGI_KIND\": \"2d\",\n\n    # Apply boost only where prob >= seed threshold\n    \"FRANGI_SEED_THR\": 0.85,\n\n    # Scale(s) (in voxels) for Hessian/Frangi analysis.\n    # If FRANGI_DS>1, we automatically scale these down by /FRANGI_DS internally.\n    \"FRANGI_SIGMAS\": (1,),\n\n    # 2D-only: which axes to run 2D slices along. (0,1,2) = all three.\n    \"FRANGI_AXES\": (0,),\n    \"FRANGI_BLACK_RIDGES\": False,\n\n    # Boost strength\n    \"FRANGI_LAM\": 0.25,\n\n    # Host-style option: run Frangi/sheetness on a binarized seed map,\n    # then add the response back onto the original probability volume.\n    # This approximates: \"threshold softmax at 0.75, then run surfaceness filter\".\n    \"FRANGI_ON_BIN\": False,\n    \"FRANGI_BIN_THR\": 0.75,\n\n    # Speed knobs\n    \"FRANGI_MASKED\": True,\n    \"FRANGI_DS\": 4,\n    \"FRANGI_CROP\": True,\n    \"FRANGI_CROP_MARGIN\": 12,\n\n    # 3D-only: sheetness parameters\n    \"FRANGI_ALPHA\": 0.5,\n    \"FRANGI_BETA\": 0.5,\n    \"FRANGI_C\": 0.2,\n\n    # Debug\n    \"DEBUG_TIMERS\": False,\n\n    # If your model outputs 2 classes, surface prob is class-1.\n    # If outputs 3 classes, surface prob = P(class1)+P(class2).\n    \"NUM_CLASSES\": 3,\n\n    # ----------------------------\n    # Minimal-change hole filling (post-mask)\n    # ----------------------------\n    # Fills enclosed 2D holes slice-by-slice (fast, low-risk).\n    # Axis: 0=z-slices, 1=y-slices, 2=x-slices.\n    \"FILL_2D_HOLES\": False,\n    \"FILL_2D_HOLES_AXIS\": 0,\n}\n\n\n# ----------------------------\n# Utility: stable softmax\n# ----------------------------\n\ndef softmax(x, axis=-1):\n    x = x - np.max(x, axis=axis, keepdims=True)\n    e = np.exp(x)\n    return e / np.sum(e, axis=axis, keepdims=True)\n\n\n# ----------------------------\n# Minimal-change 2D hole filling (post-mask)\n# ----------------------------\n\ndef fill_2d_holes(mask_u8, axis=0):\n    \"\"\"Fill enclosed holes slice-by-slice along one axis.\n\n    Intentionally minimal-change: fills only *2D enclosed holes* within slices.\n    Does not aggressively bridge tunnels.\n\n    mask_u8: uint8/bool array (D,H,W)\n    axis: 0,1,2\n    \"\"\"\n    if ndi is None:\n        return np.asarray(mask_u8, dtype=np.uint8)\n\n    m = np.asarray(mask_u8).astype(bool)\n    ax = int(axis)\n\n    m2 = np.moveaxis(m, ax, 0)\n    for i in range(m2.shape[0]):\n        m2[i] = ndi.binary_fill_holes(m2[i])\n\n    m = np.moveaxis(m2, 0, ax)\n    return m.astype(np.uint8)\n\n\ndef _maybe_fill_holes(mask_u8):\n    \"\"\"Apply optional hole filling based on CFG.\"\"\"\n    if not bool(CFG.get(\"FILL_2D_HOLES\", False)):\n        return np.asarray(mask_u8, dtype=np.uint8)\n    return fill_2d_holes(mask_u8, axis=int(CFG.get(\"FILL_2D_HOLES_AXIS\", 0)))\n\n\n# ----------------------------\n# Frangi “surfaceness” boost\n# ----------------------------\n\ndef _frangi2d_slice(img2d, sigmas=(1,), black_ridges=False):\n    \"\"\"Safe 2D Frangi call with fallbacks.\"\"\"\n    if frangi is None:\n        return None\n    x = np.asarray(img2d, dtype=np.float32)\n    # frangi expects finite values\n    x = np.nan_to_num(x, nan=0.0, posinf=0.0, neginf=0.0)\n    resp = frangi(x, sigmas=tuple(sigmas), black_ridges=bool(black_ridges))\n    resp = np.asarray(resp, dtype=np.float32)\n    resp = np.nan_to_num(resp, nan=0.0, posinf=0.0, neginf=0.0)\n    resp[resp < 0] = 0.0\n    return resp\n\n\ndef _upsample_repeat3d(x, ds, out_shape):\n    \"\"\"Nearest upsample by repeat, then crop/pad.\"\"\"\n    if ds <= 1:\n        y = x\n    else:\n        y = np.repeat(np.repeat(np.repeat(x, ds, axis=0), ds, axis=1), ds, axis=2)\n    y = y[: out_shape[0], : out_shape[1], : out_shape[2]]\n    if y.shape != out_shape:\n        pad = [(0, out_shape[i] - y.shape[i]) for i in range(3)]\n        y = np.pad(y, pad, mode=\"constant\", constant_values=0)\n    return y\n\n\ndef _enhance_surface_prob_frangi_2d(prob, cfg=CFG):\n    \"\"\"Boost a probability volume using multi-axis 2D Frangi responses.\"\"\"\n    if frangi is None:\n        return prob\n\n    p = np.asarray(prob, dtype=np.float32)\n    if p.ndim != 3:\n        return p\n\n    axes = tuple(cfg.get(\"FRANGI_AXES\", (0,)))\n    sigmas = tuple(cfg.get(\"FRANGI_SIGMAS\", (1,)))\n    black_ridges = bool(cfg.get(\"FRANGI_BLACK_RIDGES\", False))\n    seed_thr = float(cfg.get(\"FRANGI_SEED_THR\", 0.90))\n    lam = float(cfg.get(\"FRANGI_LAM\", 0.20))\n    masked = bool(cfg.get(\"FRANGI_MASKED\", True))\n\n    D, H, W = p.shape\n    resp_acc = np.zeros_like(p, dtype=np.float32)\n    resp_max = 0.0\n\n    def _accumulate(axis_id):\n        nonlocal resp_max\n        if axis_id == 0:\n            for z in range(D):\n                sl = p[z]\n                if masked and np.max(sl) < seed_thr:\n                    continue\n                r = _frangi2d_slice(sl, sigmas=sigmas, black_ridges=black_ridges)\n                if r is None:\n                    return\n                if masked:\n                    r = r * (sl >= seed_thr).astype(np.float32)\n                resp_acc[z] = np.maximum(resp_acc[z], r)\n                resp_max = max(resp_max, float(r.max()))\n        elif axis_id == 1:\n            for y in range(H):\n                sl = p[:, y, :]\n                if masked and np.max(sl) < seed_thr:\n                    continue\n                r = _frangi2d_slice(sl, sigmas=sigmas, black_ridges=black_ridges)\n                if r is None:\n                    return\n                if masked:\n                    r = r * (sl >= seed_thr).astype(np.float32)\n                resp_acc[:, y, :] = np.maximum(resp_acc[:, y, :], r)\n                resp_max = max(resp_max, float(r.max()))\n        elif axis_id == 2:\n            for x0 in range(W):\n                sl = p[:, :, x0]\n                if masked and np.max(sl) < seed_thr:\n                    continue\n                r = _frangi2d_slice(sl, sigmas=sigmas, black_ridges=black_ridges)\n                if r is None:\n                    return\n                if masked:\n                    r = r * (sl >= seed_thr).astype(np.float32)\n                resp_acc[:, :, x0] = np.maximum(resp_acc[:, :, x0], r)\n                resp_max = max(resp_max, float(r.max()))\n\n    for ax in axes:\n        _accumulate(int(ax))\n\n    if resp_max <= 0:\n        return p\n\n    resp_norm = resp_acc / resp_max\n    return np.clip(p + lam * resp_norm, 0.0, 1.0)\n\n\ndef _hessian3d_gaussian(img, sigma):\n    \"\"\"3D Hessian components via Gaussian derivatives.\"\"\"\n    if ndi is None:\n        return None\n    x = np.asarray(img, dtype=np.float32)\n    x = np.nan_to_num(x, nan=0.0, posinf=0.0, neginf=0.0)\n\n    Dxx = ndi.gaussian_filter(x, sigma=sigma, order=(2, 0, 0), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    Dyy = ndi.gaussian_filter(x, sigma=sigma, order=(0, 2, 0), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    Dzz = ndi.gaussian_filter(x, sigma=sigma, order=(0, 0, 2), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    Dxy = ndi.gaussian_filter(x, sigma=sigma, order=(1, 1, 0), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    Dxz = ndi.gaussian_filter(x, sigma=sigma, order=(1, 0, 1), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    Dyz = ndi.gaussian_filter(x, sigma=sigma, order=(0, 1, 1), mode=\"nearest\", truncate=3.0) * (sigma ** 2)\n    return Dxx, Dyy, Dzz, Dxy, Dxz, Dyz\n\n\ndef _eigvals3_from_hessian(Dxx, Dyy, Dzz, Dxy, Dxz, Dyz):\n    H = np.empty(Dxx.shape + (3, 3), dtype=np.float32)\n    H[..., 0, 0] = Dxx\n    H[..., 1, 1] = Dyy\n    H[..., 2, 2] = Dzz\n    H[..., 0, 1] = H[..., 1, 0] = Dxy\n    H[..., 0, 2] = H[..., 2, 0] = Dxz\n    H[..., 1, 2] = H[..., 2, 1] = Dyz\n\n    flat = H.reshape(-1, 3, 3)\n    evals = np.linalg.eigvalsh(flat).astype(np.float32)\n    return evals.reshape(Dxx.shape + (3,))\n\n\ndef _sheetness_from_eigvals(evals, cfg):\n    eps = 1e-6\n    lam = np.asarray(evals, dtype=np.float32)\n\n    idx = np.argsort(np.abs(lam), axis=-1)\n    l1 = np.take_along_axis(lam, idx[..., 0:1], axis=-1)[..., 0]\n    l2 = np.take_along_axis(lam, idx[..., 1:2], axis=-1)[..., 0]\n    l3 = np.take_along_axis(lam, idx[..., 2:3], axis=-1)[..., 0]\n\n    black = bool(cfg.get(\"FRANGI_BLACK_RIDGES\", False))\n    sign_ok = (l3 > 0) if black else (l3 < 0)\n\n    R1 = np.abs(l1) / (np.abs(l3) + eps)\n    R2 = np.abs(l2) / (np.abs(l3) + eps)\n\n    alpha = float(cfg.get(\"FRANGI_ALPHA\", 0.5))\n    beta = float(cfg.get(\"FRANGI_BETA\", 0.5))\n    c = float(cfg.get(\"FRANGI_C\", 0.2))\n\n    S = np.sqrt(l1 * l1 + l2 * l2 + l3 * l3)\n\n    A = np.exp(-(R1 * R1) / (2 * alpha * alpha))\n    B = np.exp(-(R2 * R2) / (2 * beta * beta))\n    C = (1.0 - np.exp(-(S * S) / (2 * c * c)))\n\n    resp = (A * B * C) * sign_ok.astype(np.float32)\n    resp = np.nan_to_num(resp, nan=0.0, posinf=0.0, neginf=0.0)\n    resp[resp < 0] = 0.0\n    return resp.astype(np.float32)\n\n\ndef _enhance_surface_prob_frangi_3d(prob, cfg=CFG):\n    if ndi is None:\n        return prob\n\n    p = np.asarray(prob, dtype=np.float32)\n    if p.ndim != 3:\n        return p\n\n    seed_thr = float(cfg.get(\"FRANGI_SEED_THR\", 0.85))\n    masked = bool(cfg.get(\"FRANGI_MASKED\", True))\n    lam = float(cfg.get(\"FRANGI_LAM\", 0.10))\n    sigmas = tuple(float(s) for s in cfg.get(\"FRANGI_SIGMAS\", (1.0,)))\n\n    # Optional crop around seed region for speed\n    roi_slices = (slice(None), slice(None), slice(None))\n    p_roi = p\n    if bool(cfg.get(\"FRANGI_CROP\", True)) and masked:\n        m = (p >= seed_thr)\n        if m.any():\n            zz, yy, xx = np.where(m)\n            z0, z1 = int(zz.min()), int(zz.max()) + 1\n            y0, y1 = int(yy.min()), int(yy.max()) + 1\n            x0, x1 = int(xx.min()), int(xx.max()) + 1\n            margin = int(cfg.get(\"FRANGI_CROP_MARGIN\", 12))\n            z0 = max(0, z0 - margin); y0 = max(0, y0 - margin); x0 = max(0, x0 - margin)\n            z1 = min(p.shape[0], z1 + margin); y1 = min(p.shape[1], y1 + margin); x1 = min(p.shape[2], x1 + margin)\n            roi_slices = (slice(z0, z1), slice(y0, y1), slice(x0, x1))\n            p_roi = p[roi_slices]\n\n    resp_best = np.zeros_like(p_roi, dtype=np.float32)\n\n    for sigma in sigmas:\n        h = _hessian3d_gaussian(p_roi, sigma=sigma)\n        if h is None:\n            return p\n        Dxx, Dyy, Dzz, Dxy, Dxz, Dyz = h\n        evals = _eigvals3_from_hessian(Dxx, Dyy, Dzz, Dxy, Dxz, Dyz)\n        resp = _sheetness_from_eigvals(evals, cfg)\n        if masked:\n            resp = resp * (p_roi >= seed_thr).astype(np.float32)\n        resp_best = np.maximum(resp_best, resp)\n\n    rmax = float(resp_best.max())\n    if rmax <= 0:\n        return p\n\n    resp_norm = resp_best / rmax\n    out = p.copy()\n    out_roi = np.clip(p_roi + lam * resp_norm, 0.0, 1.0)\n    out[roi_slices] = out_roi\n    return out\n\n\ndef enhance_surface_prob_frangi(prob, cfg=CFG):\n    \"\"\"Unified surfaceness enhancement wrapper.\n\n    Key idea:\n      - Compute a surfaceness response (2D Frangi or 3D sheetness) on either:\n          (a) the probability volume itself, OR\n          (b) a binarized seed map (host-style),\n        then add the *positive* delta back onto the original probability volume.\n\n    This keeps the transformation monotonic (we only boost), and plays nicely with\n    downstream thresholding / topo_postprocess.\n\n    Handles FRANGI_KIND (2d/3d) + FRANGI_DS downsample/upsample delta.\n    \"\"\"\n    p = np.asarray(prob, dtype=np.float32)\n    if p.ndim != 3:\n        return p\n\n    kind = str(cfg.get(\"FRANGI_KIND\", \"2d\")).lower()\n    ds = int(cfg.get(\"FRANGI_DS\", 1))\n\n    # Host-style: analyze a binarized seed map, but apply boosts to the original prob.\n    use_bin = bool(cfg.get(\"FRANGI_ON_BIN\", False))\n    bin_thr = float(cfg.get(\"FRANGI_BIN_THR\", cfg.get(\"T_LOW\", 0.75)))\n    p_in = (p >= bin_thr).astype(np.float32) if use_bin else p\n\n    # Adjust sigmas for DS-space so physical scale roughly matches full-res\n    sigmas = tuple(float(s) for s in cfg.get(\"FRANGI_SIGMAS\", (1.0,)))\n    if ds > 1:\n        sig_eff = tuple(max(0.5, s / float(ds)) for s in sigmas)\n    else:\n        sig_eff = sigmas\n\n    cfg_local = dict(cfg)\n    cfg_local[\"FRANGI_SIGMAS\"] = sig_eff\n\n    def _backend(x):\n        if kind == \"3d\":\n            return _enhance_surface_prob_frangi_3d(x, cfg=cfg_local)\n        return _enhance_surface_prob_frangi_2d(x, cfg=cfg_local)\n\n    # No downsample\n    if ds <= 1:\n        boosted = _backend(p_in)\n        delta = np.clip(boosted - p_in, 0.0, 1.0)\n        return np.clip(p + delta, 0.0, 1.0)\n\n    # Downsample -> backend -> upsample delta\n    p_ds = p[::ds, ::ds, ::ds]\n    p_in_ds = p_in[::ds, ::ds, ::ds]\n    boosted_ds = _backend(p_in_ds)\n    delta_ds = np.clip(boosted_ds - p_in_ds, 0.0, 1.0)\n    return np.clip(p + _upsample_repeat3d(delta_ds, ds, p.shape), 0.0, 1.0)\n\n\n# ----------------------------\n# TTA predictor that returns surface probabilities\n# ----------------------------\n\ndef predict_surface_prob_with_tta(inputs, swi, num_classes=3, _debug_once=False):\n    \"\"\"Real-valued surface probability.\n\n    NOTE: This is the *final* definition used by inference_pipelines.\n    \"\"\"\n    mean_logits = predict_logits_with_tta(inputs, swi, _debug_once=_debug_once)\n    probs = _softmax_np(mean_logits, axis=-1)\n    if num_classes == 2 or probs.shape[-1] == 2:\n        surf = probs[..., 1]\n    else:\n        surf = probs[..., 1] + probs[..., 2]\n    return surf.squeeze().astype(np.float32)\n\n\n# ----------------------------\n# Best-ROI improvement to try next: per-volume quantile threshold\n# ----------------------------\n# Idea: keep the SAME predicted surface \"thickness\" as the strong 0.551 hard mask,\n# but use probabilities to choose the threshold adaptively per volume.\n# This avoids miscalibration (the main reason fixed 0.75/0.80 tanked your LB).\n\n\ndef infer_mask_quantile_calibrated(\n    volume,\n    swi,\n    num_classes=3,\n    z_radius=3,\n    xy_radius=2,\n    dust_min_size=100,\n    hysteresis_band=0.0,\n):\n    \"\"\"One-change improvement candidate.\n\n    Steps:\n      1) get hard mask (baseline behavior) to estimate target surface fraction\n      2) get surface probability map\n      3) choose threshold t so prob>=t has same fraction as hard mask\n      4) run topo_postprocess on probs with (optional) hysteresis band\n\n    hysteresis_band:\n      0.0 -> simple threshold at t\n      0.05 -> T_low=t-0.05, T_high=t+0.05 (clipped to [0,1])\n    \"\"\"\n    hard = predict_hard_surface_mask_with_tta(volume, swi)\n    f = float(hard.mean())\n\n    # Optional thickness tweak: keep quantile calibration but allow slightly thinner/thicker masks.\n    # Values <1.0 make mask thinner; >1.0 thicker.\n    f = f * float(CFG.get(\"F_SCALE\", 1.0))\n    f = float(np.clip(f, 1e-6, 1.0 - 1e-6))\n    # Optional thickness tweak: scale target foreground fraction slightly.\n    # Values <1.0 make the mask thinner (higher threshold), >1.0 thicker (lower threshold).\n    f = f * float(CFG.get(\"F_SCALE\", 1.0))\n    # Clamp for numerical safety\n    f = float(np.clip(f, 1e-6, 1.0 - 1e-6))\n\n    prob = predict_surface_prob_with_tta(volume, swi, num_classes=num_classes)\n    t = float(np.quantile(prob, 1.0 - f))\n\n    if hysteresis_band > 0:\n        T_low = max(0.0, t - hysteresis_band)\n        T_high = min(1.0, t + hysteresis_band)\n    else:\n        T_low = t\n        T_high = t\n\n    mask = topo_postprocess(\n        prob,\n        T_low=T_low,\n        T_high=T_high,\n        z_radius=z_radius,\n        xy_radius=xy_radius,\n        dust_min_size=dust_min_size,\n    )\n    return mask.astype(np.uint8), dict(f=f, t=t, T_low=T_low, T_high=T_high)\n\n\n# ----------------------------\n# Fast postprocess sweep (recommended)\n# ----------------------------\n# Why: model inference is expensive (TTA + sliding window). Postprocess is cheap-ish.\n# So cache the probability map ONCE, then try multiple thresholds/dust settings\n# without re-running the model.\n#\n# Typical usage in your Kaggle notebook:\n#\n#   image_id = test_df[\"id\"].iloc[0]\n#   vol = load_volume(f\"{test_dir}/{image_id}.tif\")\n#   vol = val_transformation(vol)\n#\n#   # Option A (baseline behavior):\n#   hard = predict_hard_surface_mask_with_tta(vol, swi)\n#   m = topo_postprocess(hard, T_low=0.5, T_high=0.5, z_radius=3, xy_radius=2, dust_min_size=100)\n#\n#   # Option B (NEW): quantile-calibrated prob threshold:\n#   m, info = infer_mask_quantile_calibrated(\n#       vol, swi,\n#       num_classes=CFG.get(\"NUM_CLASSES\", 3),\n#       z_radius=3, xy_radius=2,\n#       dust_min_size=CFG.get(\"DUST_MIN_SIZE\", 100),\n#       hysteresis_band=0.05,  # try 0.00 and 0.05\n#   )\n#   print(\"calib\", info)\n#\n# Then write m to zip.\n\n\ndef write_single_volume_zip(mask, image_id, zip_path, tmp_dir=\"/kaggle/working/_tmp_pred\"):\n    \"\"\"Write a Kaggle submission zip for the common case len(test_df)==1.\"\"\"\n    import os, zipfile, tifffile\n\n    os.makedirs(tmp_dir, exist_ok=True)\n    out_path = f\"{tmp_dir}/{image_id}.tif\"\n    tifffile.imwrite(out_path, mask.astype(np.uint8))\n\n    with zipfile.ZipFile(zip_path, \"w\", compression=zipfile.ZIP_DEFLATED) as z:\n        z.write(out_path, arcname=f\"{image_id}.tif\")\n\n    os.remove(out_path)\n    return zip_path\n\n\n# ----------------------------\n# Override: inference_pipelines (baseline-safe + quantile-calibrated)\n# ----------------------------\n# NOTE: In notebooks, the last function definition wins. This block intentionally\n# re-defines inference_pipelines so it matches the strongest public baseline logic.\n\n# Control behavior via CFG:\n#   CFG[\"MODE\"] = \"hard\"      -> original baseline (hard argmax)\n#   CFG[\"MODE\"] = \"quantile\"  -> quantile-calibrated probs (recommended)\n#   CFG[\"HYST_BAND\"] = 0.00 or 0.05\n#   CFG[\"PRINT_CALIB\"] = True/False\n\nCFG.setdefault(\"MODE\", \"quantile\")\nCFG.setdefault(\"Z_RADIUS\", 3)\nCFG.setdefault(\"XY_RADIUS\", 2)\n# dust is already in CFG[\"DUST_MIN_SIZE\"] (keep baseline default 100 unless you sweep)\n\n# Frangi (optional; best used with quantile calibration)\nCFG.setdefault(\"USE_FRANGI\", False)\nCFG.setdefault(\"FRANGI_SEED_THR\", 0.90)\nCFG.setdefault(\"FRANGI_SIGMAS\", (1, 2))\nCFG.setdefault(\"FRANGI_BLACK_RIDGES\", False)\nCFG.setdefault(\"FRANGI_LAM\", 0.20)\nCFG.setdefault(\"FRANGI_MASKED\", True)\nCFG.setdefault(\"HYST_BAND\", 0.05)\nCFG.setdefault(\"PRINT_CALIB\", True)\n\n# IMPORTANT:\n# - You must create 'swi' (your Sliding Window Inference wrapper) BEFORE you call inference_pipelines.\n# - Call it like: mask = inference_pipelines(volume, swi)\n# - Do NOT rely on a default argument swi=swi in the function signature.\n\n\n\ndef inference_pipelines(volume, swi):\n    \"\"\"Main inference entrypoint. Returns uint8 mask (D,H,W).\"\"\"\n\n    mode = CFG.get(\"MODE\", \"quantile\")\n\n    # --- Option A: Original baseline behavior (hard argmax -> topo_postprocess)\n    if mode == \"hard\":\n        hard = predict_hard_surface_mask_with_tta(volume, swi)\n        mask = topo_postprocess(\n            hard.astype(np.float32),\n            T_low=0.5,\n            T_high=0.5,\n            z_radius=3,\n            xy_radius=2,\n            dust_min_size=CFG.get(\"DUST_MIN_SIZE\", 100),\n        )\n        return mask.astype(np.uint8)\n\n    # --- Option B: Quantile-calibrated (recommended first improvement)\n    # (and also the safest way to add Frangi later)\n    if mode in (\"quantile\", \"frangi_quantile\"):\n        # Step 1: baseline thickness (fraction) from hard mask\n        hard = predict_hard_surface_mask_with_tta(volume, swi)\n        f = float(hard.mean())\n\n        # Step 2: probability map\n        prob = predict_surface_prob_with_tta(volume, swi, num_classes=CFG.get(\"NUM_CLASSES\", 3))\n\n        # Step 3 (optional): Frangi boost on prob, but KEEP calibration via quantile\n        p2 = prob\n        if mode == \"frangi_quantile\" or CFG.get(\"USE_FRANGI\", False):\n            p2 = enhance_surface_prob_frangi(p2, cfg=CFG)\n\n        # Step 4: choose threshold so mask fraction matches baseline thickness\n        t = float(np.quantile(p2, 1.0 - f))\n        band = float(CFG.get(\"HYST_BAND\", 0.0))\n        if band > 0:\n            T_low = max(0.0, t - band)\n            T_high = min(1.0, t + band)\n        else:\n            T_low = t\n            T_high = t\n\n        # Step 5: topo postprocess\n        mask = topo_postprocess(\n            p2,\n            T_low=T_low,\n            T_high=T_high,\n            z_radius=int(CFG.get(\"Z_RADIUS\", 3)),\n            xy_radius=int(CFG.get(\"XY_RADIUS\", 2)),\n            dust_min_size=int(CFG.get(\"DUST_MIN_SIZE\", 100)),\n        ).astype(np.uint8)\n\n        if CFG.get(\"PRINT_CALIB\", True):\n            print(\"[calib]\", {\"mode\": mode, \"f\": f, \"t\": t, \"T_low\": T_low, \"T_high\": T_high,\n                              \"prob_q\": np.quantile(prob, [0, .5, .9, .99, 1.0]).tolist(),\n                              \"p2_q\": np.quantile(p2, [0, .5, .9, .99, 1.0]).tolist(),\n                              \"mask_sum\": int(mask.sum())})\n        return mask\n\n\n# ============================================================\n# SPEED + TIMEOUT FIX (IMPORTANT)\n# ============================================================\n# If your submission notebook timed out, the most common cause here is that the\n# quantile pipeline was running the model *twice* per volume:\n#   - once to get hard argmax mask (for fraction f)\n#   - once to get probabilities\n# That doubles inference cost on the hidden test.\n#\n# The override below guarantees ONLY ONE model pass (mean logits) per volume,\n# then derives both the hard mask fraction and the probability map from it.\n#\n# Also adds a lightweight TTA switch:\n#   CFG[\"TTA_MODE\"] = \"full\" (flips + rotations)  [slowest]\n#   CFG[\"TTA_MODE\"] = \"flip\" (flips only)         [~40% faster]\n#   CFG[\"TTA_MODE\"] = \"none\" (no TTA)              [fastest]\n\nCFG.setdefault(\"TTA_MODE\", \"full\")\n# Customize TTA without code edits:\n# - Flip axes are over (D,H,W) in the 5D tensor (B,D,H,W,C) => axes 1,2,3.\n# - Rotations are in-plane over (H,W) => axes (2,3). Use ks in {1,2,3}.\nCFG.setdefault(\"TTA_FLIP_AXES\", (1, 2, 3))\nCFG.setdefault(\"TTA_AGG\", \"mean\")  # \"mean\" or \"median\" over TTA logits\nCFG.setdefault(\"TTA_ROT_KS\", (1, 2, 3))\n\n\ndef _mean_logits_tta_once(inputs, swi, tta_mode=\"full\"):\n    \"\"\"Return aggregated logits across TTA. Shape (1,D,H,W,C).\n\n    Enhancement: supports passing a dict of SWIs {\"base\":..., \"hi\":...}.\n    If CFG[\"OV06_MAIN_ONLY\"] is True and a dict is provided, we use:\n      - hi overlap for the *first* (t==0) view\n      - base overlap for all other TTA views\n    If CFG[\"SWI_MAIN_AVG\"] is True, then for t==0 we average (base+hi)/2.\n\n    This mimics the 0.552 notebook's \"OV06 main-only\" idea without changing models.\n    \"\"\"\n    logits = []\n\n    def _call_swi(swi_obj, x, t_idx):\n        if isinstance(swi_obj, dict):\n            base = swi_obj.get(\"base\") or swi_obj.get(\"public\") or swi_obj.get(\"hi\")\n            hi = swi_obj.get(\"hi\") or base\n            if bool(CFG.get(\"SWI_MAIN_AVG\", False)) and (t_idx == 0) and (base is not None) and (hi is not None):\n                return 0.5 * (base(x) + hi(x))\n            if bool(CFG.get(\"OV06_MAIN_ONLY\", False)) and (t_idx == 0) and (hi is not None):\n                return hi(x)\n            return base(x)\n        return swi_obj(x)\n\n    t_idx = 0\n    logits.append(_call_swi(swi, inputs, t_idx))\n\n    flip_axes = tuple(CFG.get(\"TTA_FLIP_AXES\", (1, 2, 3)))\n    rot_ks = tuple(CFG.get(\"TTA_ROT_KS\", (1, 2, 3)))\n\n    if tta_mode in (\"flip\", \"full\"):\n        for axis in flip_axes:\n            img_f = np.flip(inputs, axis=axis)\n            t_idx += 1\n            p = _call_swi(swi, img_f, t_idx)\n            p = np.flip(p, axis=axis)\n            logits.append(p)\n\n    if tta_mode == \"full\":\n        for k in rot_ks:\n            img_r = np.rot90(inputs, k=k, axes=(2, 3))\n            t_idx += 1\n            p = _call_swi(swi, img_r, t_idx)\n            p = np.rot90(p, k=-k, axes=(2, 3))\n            logits.append(p)\n\n    stack = np.stack(logits, axis=0)\n    agg = str(CFG.get(\"TTA_AGG\", \"mean\")).lower()\n    if agg == \"median\":\n        return np.median(stack, axis=0)\n    return np.mean(stack, axis=0)\n\n\ndef _softmax_np(x, axis=-1):\n    \"\"\"Numerically-stable softmax for numpy arrays.\"\"\"\n    x = np.asarray(x, dtype=np.float32)\n    x = x - np.max(x, axis=axis, keepdims=True)\n    e = np.exp(x)\n    return e / np.sum(e, axis=axis, keepdims=True)\n\n\ndef _surface_prob_from_mean_logits(mean_logits, num_classes=3):\n    probs = _softmax_np(mean_logits, axis=-1)\n    C = probs.shape[-1]\n    if num_classes == 2 or C == 2:\n        surf = probs[..., 1]\n    else:\n        surf = probs[..., 1] + probs[..., 2]\n    return surf.squeeze().astype(np.float32)\n\n\ndef _hard_surface_mask_from_mean_logits(mean_logits):\n    pred_cls = mean_logits.argmax(-1).astype(np.uint8).squeeze()  # (D,H,W)\n    return (pred_cls >= 1).astype(np.uint8)\n\n\n# FINAL OVERRIDE: inference_pipelines (single-pass per volume)\n#\n# This function supports TWO modes:\n#  - MODE=\"quantile\" (current default): single SWI; quantile-calibrated threshold + topo_postprocess\n#  - MODE=\"seeded_hyst\": implements the public 0.552 idea (public-anchored + private-seeded hysteresis)\n#       strong = (private_prob >= T_high)\n#       weak   = (private_prob >= T_low) OR (public_fg)\n#     We implement this safely by *injecting* the public_fg into the weak region before calling topo_postprocess.\n#\n# IMPORTANT for seeded_hyst:\n#   Pass a dict of SWIs: {\"public\": swi_public, \"base\": swi_base, \"hi\": swi_hi}\n#   If you pass a single swi, it will still run (public/base/hi all map to that swi).\n\n\ndef _sigmoid_stable(x):\n    x = np.asarray(x, dtype=np.float32)\n    x = np.clip(x, -50.0, 50.0)\n    return 1.0 / (1.0 + np.exp(-x))\n\n\ndef _binary_logit_from_multiclass_logits(logits, mode=\"fg12\"):\n    \"\"\"Return a *binary* logit for foreground vs background from 3-class logits.\n\n    mode:\n      \"fg12\" -> logit = logsumexp(class1,class2) - class0\n\n    This matches the 0.552 notebook's \"INK_MODE=fg12\" idea and is equivalent to P(class1)+P(class2),\n    but does averaging in logit space before sigmoid.\n    \"\"\"\n    l0 = logits[..., 0].astype(np.float32)\n    l1 = logits[..., 1].astype(np.float32)\n    if logits.shape[-1] >= 3:\n        l2 = logits[..., 2].astype(np.float32)\n    else:\n        l2 = np.full_like(l1, -1e9, dtype=np.float32)\n\n    m = np.maximum(l1, l2)\n    lfg = m + np.log(np.exp(l1 - m) + np.exp(l2 - m))\n    return lfg - l0\n\n\ndef _iter_tta(volume, tta_mode=None):\n    \"\"\"Yield (aug_volume, inv_fn, t_index)\"\"\"\n    if tta_mode is None:\n        tta_mode = CFG.get(\"TTA_MODE\", \"full\")\n\n    flip_axes = tuple(CFG.get(\"TTA_FLIP_AXES\", (1, 2, 3)))\n    rot_ks = tuple(CFG.get(\"TTA_ROT_KS\", (1, 2, 3)))\n\n    t = 0\n    yield volume, (lambda y: y), t\n    t += 1\n\n    if tta_mode in (\"flip\", \"full\"):\n        for axis in flip_axes:\n            v = np.flip(volume, axis=axis)\n            inv = (lambda y, axis=axis: np.flip(y, axis=axis))\n            yield v, inv, t\n            t += 1\n\n    if tta_mode == \"full\":\n        for k in rot_ks:\n            v = np.rot90(volume, k=k, axes=(2, 3))\n            inv = (lambda y, k=k: np.rot90(y, k=-k, axes=(2, 3)))\n            yield v, inv, t\n            t += 1\n\n\ndef _predict_public_labels_private_prob(volume, swis):\n    \"\"\"0.552-style dual stream:\n\n    - public stream: mean multiclass logits -> argmax labels (used only as weak-region anchor)\n    - private stream: mean *binary logit* (fg12) -> sigmoid -> prob\n\n    Speed knobs:\n      CFG[\"PUBLIC_STREAM_TTA\"] = False (default): compute public stream only on t==0\n      CFG[\"OV06_MAIN_ONLY\"]    = True  (default): use swi_hi only on t==0, else swi_base\n    \"\"\"\n    swi_public = swis.get(\"public\") or swis.get(\"base\")\n    swi_base = swis.get(\"base\") or swi_public\n    swi_hi = swis.get(\"hi\") or swi_base\n\n    public_tta = bool(CFG.get(\"PUBLIC_STREAM_TTA\", False))\n    ov06_main_only = bool(CFG.get(\"OV06_MAIN_ONLY\", True))\n    ink_mode = str(CFG.get(\"INK_MODE\", \"fg12\"))\n\n    temp = float(CFG.get(\"LOGIT_TEMP\", 1.0))\n\n    logits_pub_sum = None\n    s_sum = None\n    n_pub = 0\n    n_prv = 0\n\n    for v, inv, t in _iter_tta(volume, tta_mode=CFG.get(\"TTA_MODE\", \"full\")):\n        # --- public stream (optional TTA)\n        if public_tta or (t == 0):\n            l_pub = np.asarray(swi_public(v), dtype=np.float32)\n            l_pub = inv(l_pub)\n            if temp != 1.0:\n                l_pub = l_pub / temp\n            logits_pub_sum = l_pub if logits_pub_sum is None else (logits_pub_sum + l_pub)\n            n_pub += 1\n\n        # --- private stream\n        swi_use = swi_hi if ((t == 0) or (not ov06_main_only)) else swi_base\n        l_prv = np.asarray(swi_use(v), dtype=np.float32)\n        l_prv = inv(l_prv)\n        if temp != 1.0:\n            l_prv = l_prv / temp\n\n        s = _binary_logit_from_multiclass_logits(l_prv, mode=ink_mode)\n        s_sum = s if s_sum is None else (s_sum + s)\n        n_prv += 1\n\n    mean_pub = logits_pub_sum / float(max(1, n_pub))\n    pub_labels = mean_pub.argmax(-1).astype(np.uint8).squeeze()\n\n    s_mean = s_sum / float(max(1, n_prv))\n    prob = _sigmoid_stable(s_mean).astype(np.float32).squeeze()\n\n    return pub_labels, prob\n\n\ndef inference_pipelines(volume, swi):\n    \"\"\"Return uint8 (D,H,W) mask.\n\n    - If swi is callable: used as the base SWI for all modes.\n    - If swi is dict: supports 0.552-style dual stream for MODE=\"seeded_hyst\".\n    \"\"\"\n\n    mode = str(CFG.get(\"MODE\", \"quantile\"))\n\n    # ----------------------------\n    # MODE: 0.552-style seeded hysteresis (public-anchored)\n    # ----------------------------\n    if mode == \"seeded_hyst\":\n        # Accept either a dict of SWIs or a single SWI (fallback)\n        if isinstance(swi, dict):\n            swis = swi\n        else:\n            swis = {\"public\": swi, \"base\": swi, \"hi\": swi}\n\n        pub_labels, prob = _predict_public_labels_private_prob(volume, swis)\n        pub_fg = (pub_labels != 0)\n\n        # thresholds for hysteresis\n        T_low = float(CFG.get(\"T_low\", CFG.get(\"T_LOW\", 0.50)))\n        T_high = float(CFG.get(\"T_high\", CFG.get(\"T_HIGH\", 0.90)))\n\n        # Inject public anchor into the weak region before topo_postprocess.\n        # This emulates: weak = (prob>=T_low) OR pub_fg\n        p2 = prob.copy()\n        p2[pub_fg] = np.maximum(p2[pub_fg], T_low)\n\n        mask = topo_postprocess(\n            p2,\n            T_low=T_low,\n            T_high=T_high,\n            z_radius=int(CFG.get(\"Z_RADIUS\", CFG.get(\"z_radius\", 3))),\n            xy_radius=int(CFG.get(\"XY_RADIUS\", CFG.get(\"xy_radius\", 2))),\n            dust_min_size=int(CFG.get(\"DUST_MIN_SIZE\", CFG.get(\"dust_min_size\", 100))),\n        ).astype(np.uint8)\n\n        if CFG.get(\"PRINT_CALIB\", False):\n            print(\"[seeded_hyst]\",\n                  {\"T_low\": T_low, \"T_high\": T_high,\n                   \"pub_fg_sum\": int(pub_fg.sum()),\n                   \"prob_q\": np.quantile(prob, [0, .5, .9, .99, 1.0]).tolist(),\n                   \"mask_sum\": int(mask.sum())})\n\n        return _maybe_fill_holes(mask)\n\n    # ----------------------------\n    # MODE: hard / quantile (single SWI, single pass per volume)\n    # ----------------------------\n\n    # If swi is a dict, pick \"base\" (or \"hi\") as the callable for single-stream modes\n    if isinstance(swi, dict):\n        swi_call = swi.get(\"base\") or swi.get(\"hi\") or swi.get(\"public\")\n    else:\n        swi_call = swi\n\n    tta_mode = CFG.get(\"TTA_MODE\", \"full\")\n\n    # 1) One model pass (mean logits)\n    mean_logits = _mean_logits_tta_once(volume, swi_call, tta_mode=tta_mode)\n\n    # Optional logit temperature scaling (cheap calibration knob)\n    # <1.0 sharpens, >1.0 smooths.\n    temp = float(CFG.get(\"LOGIT_TEMP\", 1.0))\n    if temp != 1.0:\n        mean_logits = mean_logits / temp\n\n    # 2) Baseline hard mask + thickness fraction\n    hard = _hard_surface_mask_from_mean_logits(mean_logits)\n    f = float(hard.mean())\n\n    # MODE: hard baseline (fast + strong reference)\n    if mode == \"hard\":\n        mask = topo_postprocess(\n            hard.astype(np.float32),\n            T_low=0.5,\n            T_high=0.5,\n            z_radius=int(CFG.get(\"Z_RADIUS\", 3)),\n            xy_radius=int(CFG.get(\"XY_RADIUS\", 2)),\n            dust_min_size=int(CFG.get(\"DUST_MIN_SIZE\", 100)),\n        )\n        return _maybe_fill_holes(mask)\n\n    # 3) Probability map from logits\n    prob = _surface_prob_from_mean_logits(mean_logits, num_classes=CFG.get(\"NUM_CLASSES\", 3))\n\n    # ----------------------------\n    # MODE: fixed threshold (host-like)\n    # ----------------------------\n    # Uses CFG[\"T_LOW\"], CFG[\"T_HIGH\"] directly (no quantile calibration).\n    # If you want host-style: set T_LOW=T_HIGH=0.75 and enable FRANGI_ON_BIN.\n    if mode in (\"fixed\", \"fixed_frangi\", \"host_fixed\"):\n        p2 = prob\n        if (mode != \"fixed\") or bool(CFG.get(\"USE_FRANGI\", False)):\n            t0 = time.time()\n            p2 = enhance_surface_prob_frangi(p2, cfg=CFG)\n            if CFG.get(\"DEBUG_TIMERS\", False):\n                print(f\"[timer] frangi={time.time() - t0:.2f}s\")\n\n        T_low = float(CFG.get(\"T_LOW\", 0.75))\n        T_high = float(CFG.get(\"T_HIGH\", T_low))\n\n        mask = topo_postprocess(\n            p2,\n            T_low=T_low,\n            T_high=T_high,\n            z_radius=int(CFG.get(\"Z_RADIUS\", 3)),\n            xy_radius=int(CFG.get(\"XY_RADIUS\", 2)),\n            dust_min_size=int(CFG.get(\"DUST_MIN_SIZE\", 100)),\n        ).astype(np.uint8)\n\n        if CFG.get(\"PRINT_CALIB\", False):\n            print(\"[fixed]\", {\n                \"mode\": mode,\n                \"tta\": tta_mode,\n                \"T_low\": T_low,\n                \"T_high\": T_high,\n                \"prob_q\": np.quantile(prob, [0, .5, .9, .99, 1.0]).tolist(),\n                \"mask_sum\": int(mask.sum()),\n            })\n\n        return _maybe_fill_holes(mask)\n\n    # 4) Optional Frangi boost (use with quantile calibration)\n    p2 = prob\n    if mode == \"frangi_quantile\" or CFG.get(\"USE_FRANGI\", False):\n        t0 = time.time()\n        p2 = enhance_surface_prob_frangi(p2, cfg=CFG)\n        if CFG.get(\"DEBUG_TIMERS\", False):\n            print(f\"[timer] frangi={time.time() - t0:.2f}s\")\n\n\n\n    # Optional monotonic reshaping before quantile thresholding.\n    gamma = float(CFG.get(\"PROB_GAMMA\", 1.0))\n    if gamma != 1.0:\n        p2 = np.clip(p2, 0.0, 1.0) ** gamma\n\n    # 5) Quantile-calibrated threshold (matches baseline thickness)\n    t = float(np.quantile(p2, 1.0 - f))\n    band = float(CFG.get(\"HYST_BAND\", 0.0))\n    if band > 0:\n        T_low = max(0.0, t - band)\n        T_high = min(1.0, t + band)\n    else:\n        T_low = t\n        T_high = t\n\n    # 6) Topology postprocess\n    mask = topo_postprocess(\n        p2,\n        T_low=T_low,\n        T_high=T_high,\n        z_radius=int(CFG.get(\"Z_RADIUS\", 3)),\n        xy_radius=int(CFG.get(\"XY_RADIUS\", 2)),\n        dust_min_size=int(CFG.get(\"DUST_MIN_SIZE\", 100)),\n    ).astype(np.uint8)\n\n    if CFG.get(\"PRINT_CALIB\", False):\n        print(\"[calib]\", {\"mode\": mode, \"tta\": tta_mode, \"f\": f, \"t\": t,\n                          \"T_low\": T_low, \"T_high\": T_high,\n                          \"mask_sum\": int(mask.sum())})\n\n    return _maybe_fill_holes(mask)\n\n\n# ============================================================\n# Speed + reliability add-ons (recommended for hidden test) (recommended for hidden test)\n# ============================================================\n# 1) Warm up JIT once (Keras/JAX) so the first hidden volume doesn’t pay the compile tax.\n# 2) Write TIFFs into the ZIP in-memory (avoid writing 120+ TIFFs to disk).\n# 3) Use fast compression level.\n\nimport io\n\n\ndef _infer_in_channels(swi_obj, default=1):\n    \"\"\"Best-effort infer input channels from the SWI wrapper's underlying Keras model.\"\"\"\n    try:\n        s = swi_obj\n        if isinstance(swi_obj, dict):\n            s = swi_obj.get(\"base\") or swi_obj.get(\"hi\") or swi_obj.get(\"public\")\n        if s is not None and hasattr(s, \"model\") and getattr(s.model, \"input_shape\", None) is not None:\n            c = s.model.input_shape[-1]\n            if c is None:\n                return default\n            return int(c)\n    except Exception:\n        pass\n    return default\n\n\ndef warmup_jit(swi, tta_mode=None, sample_volume=None):\n    \"\"\"Compile the predict path once so hidden inference is steadier.\n\n    - If sample_volume is provided, uses it (recommended; guarantees correct channels).\n    - Otherwise creates a dummy volume with inferred channel count.\n    \"\"\"\n    if tta_mode is None:\n        tta_mode = CFG.get(\"TTA_MODE\", \"full\")\n\n    if sample_volume is None:\n        c = _infer_in_channels(swi, default=1)\n        sample_volume = np.zeros((1, 320, 320, 320, c), dtype=np.float32)\n\n    _ = _mean_logits_tta_once(sample_volume, swi, tta_mode=tta_mode)\n\n\ndef write_submission_zip_in_memory(zip_path, test_df, test_dir, swi):\n    \"\"\"Create submission.zip with one {id}.tif per hidden test volume.\"\"\"\n    with zipfile.ZipFile(zip_path, \"w\", compression=zipfile.ZIP_DEFLATED, compresslevel=1) as z:\n        for image_id in test_df[\"id\"]:\n            volume = load_volume(f\"{test_dir}/{image_id}.tif\")\n            volume = val_transformation(volume)\n            mask = inference_pipelines(volume, swi)  # uint8 (D,H,W)\n\n            bio = io.BytesIO()\n            tifffile.imwrite(bio, mask.astype(np.uint8))\n            z.writestr(f\"{image_id}.tif\", bio.getvalue())\n\n    print(\"Submission ZIP:\", zip_path)\n\n# In your main flow, call:\n# warmup_jit(swi)\n# write_submission_zip_in_memory(\"/kaggle/working/submission.zip\", test_df, test_dir, swi)\n\n\n# ----------------------------\n# Quick-run presets (optional)\n# ----------------------------\n# Use these to avoid manual config typos when launching 3 daily submissions.\n# Example:\n#   set_preset(\"temp115_no_frangi\")\n#   warmup_jit(swi)  # or warmup_jit(swi[\"base\"]) if swi is dict\n#   write_submission_zip_in_memory(...)\n\nRUN_PRESETS = {\n    # Best non-Frangi variant seen so far (slightly better than baseline sometimes)\n    \"temp115_no_frangi\": {\n        \"MODE\": \"quantile\",\n        \"USE_FRANGI\": False,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 1.0,\n    },\n\n    # Same as baseline but aggregate TTA logits with median (robust to one bad aug)\n    \"median_tta_no_frangi\": {\n        \"MODE\": \"quantile\",\n        \"USE_FRANGI\": False,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"median\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.0,\n        \"PROB_GAMMA\": 1.0,\n    },\n\n    # Frangi attempt that stays within the 6-min budget: downsampled + all axes + gentle boost\n    \"frangi_light_all_axes\": {\n        \"MODE\": \"quantile\",  # keep quantile calibration\n        \"USE_FRANGI\": True,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 4,\n        \"FRANGI_AXES\": (0, 1, 2),\n        \"FRANGI_SIGMAS\": (1,),\n        \"FRANGI_SEED_THR\": 0.80,\n        \"FRANGI_LAM\": 0.08,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.0,\n        \"PROB_GAMMA\": 1.0,\n    },\n\n    # NEW: your current best family (post-rescore): quantile + temp=1.15 + gamma=0.90\n    \"best_quantile_temp115_gamma090\": {\n        \"MODE\": \"quantile\",\n        \"USE_FRANGI\": False,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n    },\n\n    # FINAL FRANGI ATTEMPT (non-host): 3D sheetness, gentle boost, still quantile-calibrated\n    \"frangi3d_quantile_gentle\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"3d\",\n        \"FRANGI_DS\": 5,\n        \"FRANGI_SIGMAS\": (2,),\n        \"FRANGI_SEED_THR\": 0.90,\n        \"FRANGI_LAM\": 0.05,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n    },\n\n    # FINAL FRANGI ATTEMPT (non-host): 2D frangi, all axes, very gentle\n    \"frangi2d_quantile_allaxes\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 4,\n        \"FRANGI_AXES\": (0, 1, 2),\n        \"FRANGI_SIGMAS\": (1,),\n        \"FRANGI_SEED_THR\": 0.90,\n        \"FRANGI_LAM\": 0.06,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n    },\n\n    # FINAL FRANGI ATTEMPT (non-host): 2D frangi on XY only, slightly stronger, multi-scale\n    \"frangi2d_quantile_xy_multiscale\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 4,\n        \"FRANGI_AXES\": (0,),\n        \"FRANGI_SIGMAS\": (1, 2),\n        \"FRANGI_SEED_THR\": 0.85,\n        \"FRANGI_LAM\": 0.08,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n    },\n\n    # HEAVIER FRANGI (runtime headroom): apply to more voxels + stronger boost\n    # Keep quantile calibration (so thickness stays comparable) but let Frangi re-rank harder.\n\n    \"frangi3d_quantile_heavy\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"3d\",\n        \"FRANGI_DS\": 3,\n        \"FRANGI_SIGMAS\": (2, 3),\n        \"FRANGI_SEED_THR\": 0.85,\n        \"FRANGI_LAM\": 0.12,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 18,\n    },\n\n    \"frangi2d_quantile_heavy_allaxes\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 2,\n        \"FRANGI_AXES\": (0, 1, 2),\n        \"FRANGI_SIGMAS\": (1, 2),\n        \"FRANGI_SEED_THR\": 0.85,\n        \"FRANGI_LAM\": 0.14,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 18,\n    },\n\n    \"frangi2d_quantile_heavy_xy\": {\n        \"MODE\": \"frangi_quantile\",\n        \"USE_FRANGI\": True,\n        \"HYST_BAND\": 0.0,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.15,\n        \"PROB_GAMMA\": 0.90,\n        \"FRANGI_ON_BIN\": False,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 2,\n        \"FRANGI_AXES\": (0,),\n        \"FRANGI_SIGMAS\": (1, 2, 3),\n        \"FRANGI_SEED_THR\": 0.80,\n        \"FRANGI_LAM\": 0.16,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 18,\n    },\n\n    # NEW: host-like fixed threshold @ 0.75 (no quantile calibration)\n    \"fixed075_no_frangi\": {\n        \"MODE\": \"fixed\",\n        \"USE_FRANGI\": False,\n        \"T_LOW\": 0.75,\n        \"T_HIGH\": 0.75,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.0,\n        \"PROB_GAMMA\": 1.0,\n    },\n\n    # NEW: host-like fixed threshold + sheetness on binarized seed map (3D)\n    \"host075_frangi3d_bin\": {\n        \"MODE\": \"fixed_frangi\",\n        \"USE_FRANGI\": True,\n        \"T_LOW\": 0.75,\n        \"T_HIGH\": 0.75,\n        \"FRANGI_ON_BIN\": True,\n        \"FRANGI_BIN_THR\": 0.75,\n        \"FRANGI_KIND\": \"3d\",\n        \"FRANGI_DS\": 5,\n        \"FRANGI_SIGMAS\": (2,),\n        \"FRANGI_SEED_THR\": 0.75,\n        \"FRANGI_LAM\": 0.10,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n        \"FRANGI_ALPHA\": 0.5,\n        \"FRANGI_BETA\": 0.5,\n        \"FRANGI_C\": 0.2,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.0,\n        \"PROB_GAMMA\": 1.0,\n    },\n\n    # NEW: host-like fixed threshold + frangi on bin seed (2D, all axes)\n    \"host075_frangi2d_bin_all\": {\n        \"MODE\": \"fixed_frangi\",\n        \"USE_FRANGI\": True,\n        \"T_LOW\": 0.75,\n        \"T_HIGH\": 0.75,\n        \"FRANGI_ON_BIN\": True,\n        \"FRANGI_BIN_THR\": 0.75,\n        \"FRANGI_KIND\": \"2d\",\n        \"FRANGI_DS\": 4,\n        \"FRANGI_AXES\": (0, 1, 2),\n        \"FRANGI_SIGMAS\": (1,),\n        \"FRANGI_SEED_THR\": 0.75,\n        \"FRANGI_LAM\": 0.12,\n        \"FRANGI_MASKED\": True,\n        \"FRANGI_CROP\": True,\n        \"FRANGI_CROP_MARGIN\": 12,\n        \"TTA_MODE\": \"full\",\n        \"TTA_AGG\": \"mean\",\n        \"DUST_MIN_SIZE\": 200,\n        \"Z_RADIUS\": 3,\n        \"XY_RADIUS\": 2,\n        \"LOGIT_TEMP\": 1.0,\n        \"PROB_GAMMA\": 1.0,\n    },\n}\n\n\ndef apply_cfg_overrides(overrides: dict):\n    for k, v in overrides.items():\n        CFG[k] = v\n\n\ndef set_preset(name: str):\n    if name not in RUN_PRESETS:\n        raise KeyError(f\"Unknown preset {name}. Available: {list(RUN_PRESETS.keys())}\")\n    apply_cfg_overrides(RUN_PRESETS[name])\n    # Print the effective settings you actually changed\n    print(\"[preset]\", name)\n    for k in RUN_PRESETS[name].keys():\n        print(f\"  CFG[{k!r}] = {CFG[k]!r}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T04:53:50.830814Z","iopub.execute_input":"2026-02-12T04:53:50.831014Z","iopub.status.idle":"2026-02-12T04:53:50.898455Z","shell.execute_reply.started":"2026-02-12T04:53:50.830997Z","shell.execute_reply":"2026-02-12T04:53:50.897941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def val_transformation(image):\n    data = {\"image\": image}\n    pipeline = Compose([\n        NormalizeIntensity(\n            keys=[\"image\"], \n            nonzero=True,\n            channel_wise=False\n        ),\n    ])\n    result = pipeline(data)\n    return result[\"image\"]","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.900331Z","iopub.execute_input":"2026-02-12T04:53:50.900796Z","iopub.status.idle":"2026-02-12T04:53:50.904458Z","shell.execute_reply.started":"2026-02-12T04:53:50.900777Z","shell.execute_reply":"2026-02-12T04:53:50.903893Z"},"papermill":{"duration":0.009642,"end_time":"2026-02-03T18:37:17.204496","exception":false,"start_time":"2026-02-03T18:37:17.194854","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Model**","metadata":{"papermill":{"duration":0.003794,"end_time":"2026-02-03T18:37:17.212123","exception":false,"start_time":"2026-02-03T18:37:17.208329","status":"completed"},"tags":[]}},{"cell_type":"code","source":"tta=1\nnum_classes=3\ninput_shape=(160, 160, 160)\nkaggle_model_path = \"/kaggle/input/vsd-model/keras/\"","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.905103Z","iopub.execute_input":"2026-02-12T04:53:50.905337Z","iopub.status.idle":"2026-02-12T04:53:50.915409Z","shell.execute_reply.started":"2026-02-12T04:53:50.905312Z","shell.execute_reply":"2026-02-12T04:53:50.914813Z"},"papermill":{"duration":0.009107,"end_time":"2026-02-03T18:37:17.225034","exception":false,"start_time":"2026-02-03T18:37:17.215927","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_model():\n    ## LB: 0.486\n    # model = SegFormer(\n    #     input_shape=(128, 128, 128, 1),\n    #     encoder_name='mit_b2',\n    #     classifier_activation='softmax',\n    #     num_classes=2,\n    # )\n    # model.load_weights(\n    #     \"/kaggle/input/vsd-model/keras/segformer.mit.b2/2/segformer.mit.b2.weights.h5\"\n    # )\n\n    ## LB: 0.5 \n    # model = TransUNet(\n    #     input_shape=(128, 128, 128, 1),\n    #     encoder_name='seresnext50',\n    #     classifier_activation='softmax',\n    #     num_classes=2,\n    # )\n    # model.load_weights(\n    #     f\"{kaggle_model_path}/transunet/2/transunet.seresnext50.128px.weights.h5\"\n    # )\n\n    # ## LB: 505\n    # model = TransUNet(\n    #     input_shape=(160, 160, 160, 1),\n    #     encoder_name='seresnext50',\n    #     classifier_activation='softmax',\n    #     num_classes=3,\n    # )\n    # model.load_weights(\n    #     f\"{kaggle_model_path}/transunet/2/transunet.seresnext50.160px.weights.h5\"\n    # )\n\n    # 0.545 (tta+pp)\n    model = TransUNet(\n        input_shape=(160, 160, 160, 1),\n        encoder_name='seresnext50',\n        classifier_activation=None,\n        num_classes=3,\n    )\n    model.load_weights(\n        f\"{kaggle_model_path}/transunet/3/transunet.seresnext50.160px.comboloss.weights.h5\"\n    )\n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.916061Z","iopub.execute_input":"2026-02-12T04:53:50.916284Z","iopub.status.idle":"2026-02-12T04:53:50.925421Z","shell.execute_reply.started":"2026-02-12T04:53:50.916267Z","shell.execute_reply":"2026-02-12T04:53:50.924754Z"},"papermill":{"duration":0.009881,"end_time":"2026-02-03T18:37:17.238753","exception":false,"start_time":"2026-02-03T18:37:17.228872","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = get_model()\nmodel.count_params() / 1e6","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:53:50.926141Z","iopub.execute_input":"2026-02-12T04:53:50.926366Z","iopub.status.idle":"2026-02-12T04:54:09.565387Z","shell.execute_reply.started":"2026-02-12T04:53:50.926347Z","shell.execute_reply":"2026-02-12T04:54:09.564766Z"},"papermill":{"duration":21.030604,"end_time":"2026-02-03T18:37:38.273238","exception":false,"start_time":"2026-02-03T18:37:17.242634","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.instance_describe()","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.566122Z","iopub.execute_input":"2026-02-12T04:54:09.566374Z","iopub.status.idle":"2026-02-12T04:54:09.6258Z","shell.execute_reply.started":"2026-02-12T04:54:09.566349Z","shell.execute_reply":"2026-02-12T04:54:09.625236Z"},"papermill":{"duration":0.0634,"end_time":"2026-02-03T18:37:38.341061","exception":false,"start_time":"2026-02-03T18:37:38.277661","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Sliding Window Inference**","metadata":{"papermill":{"duration":0.003977,"end_time":"2026-02-03T18:37:38.349205","exception":false,"start_time":"2026-02-03T18:37:38.345228","status":"completed"},"tags":[]}},{"cell_type":"code","source":"swi_base = SlidingWindowInference(\n    model,\n    num_classes=3,\n    roi_size=input_shape,\n    sw_batch_size=1,\n    mode='gaussian',\n    overlap=0.43,\n)\n\nswi_hi   = SlidingWindowInference(\n    model,\n    num_classes=3,\n    roi_size=input_shape,\n    sw_batch_size=1,\n    mode='gaussian',\n    overlap=0.6,\n)\n\nswi = {\"base\": swi_base, \"hi\": swi_hi}   # <-- pass dict into inference now\n\n# swi = SlidingWindowInference(\n#     model,\n#     num_classes=3,\n#     roi_size=input_shape,\n#     sw_batch_size=1,\n#     mode='gaussian',\n#     overlap=0.46,\n# )","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.626529Z","iopub.execute_input":"2026-02-12T04:54:09.626779Z","iopub.status.idle":"2026-02-12T04:54:09.630399Z","shell.execute_reply.started":"2026-02-12T04:54:09.626755Z","shell.execute_reply":"2026-02-12T04:54:09.629683Z"},"papermill":{"duration":0.009436,"end_time":"2026-02-03T18:37:38.362619","exception":false,"start_time":"2026-02-03T18:37:38.353183","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_volume(path):\n    vol = tifffile.imread(path)\n    vol = vol.astype(np.float32)\n    vol = vol[None, ..., None]\n    return vol","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.63114Z","iopub.execute_input":"2026-02-12T04:54:09.631347Z","iopub.status.idle":"2026-02-12T04:54:09.641284Z","shell.execute_reply.started":"2026-02-12T04:54:09.631327Z","shell.execute_reply":"2026-02-12T04:54:09.640551Z"},"papermill":{"duration":0.009699,"end_time":"2026-02-03T18:37:38.376381","exception":false,"start_time":"2026-02-03T18:37:38.366682","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Test Time Augmentation (TTA)**","metadata":{"papermill":{"duration":0.004511,"end_time":"2026-02-03T18:37:38.385472","exception":false,"start_time":"2026-02-03T18:37:38.380961","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def predict_with_tta(inputs, swi):\n    logits = []\n\n    # Original\n    logits.append(swi(inputs))\n\n    # Flips (spatial only)\n    for axis in [1, 2, 3]:\n        img_f = np.flip(inputs, axis=axis)\n        p = swi(img_f)\n        p = np.flip(p, axis=axis)\n        logits.append(p)\n\n    # Axial rotations (H, W)\n    for k in [1, 2, 3]:\n        img_r = np.rot90(inputs, k=k, axes=(2, 3))\n        p = swi(img_r)\n        p = np.rot90(p, k=-k, axes=(2, 3))\n        logits.append(p)\n\n    mean_logits = np.mean(logits, axis=0)\n    return mean_logits.argmax(-1).astype(np.uint8).squeeze()","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.64208Z","iopub.execute_input":"2026-02-12T04:54:09.642381Z","iopub.status.idle":"2026-02-12T04:54:09.653029Z","shell.execute_reply.started":"2026-02-12T04:54:09.642357Z","shell.execute_reply":"2026-02-12T04:54:09.652366Z"},"papermill":{"duration":0.010979,"end_time":"2026-02-03T18:37:38.40068","exception":false,"start_time":"2026-02-03T18:37:38.389701","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Post Processing**","metadata":{"papermill":{"duration":0.00453,"end_time":"2026-02-03T18:37:38.40933","exception":false,"start_time":"2026-02-03T18:37:38.4048","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# https://www.kaggle.com/code/choudharymanas/inference-baseline-transunet-lb-0-537\ndef build_anisotropic_struct(z_radius: int, xy_radius: int):\n    z, r = z_radius, xy_radius\n    if z == 0 and r == 0:\n        return None\n    if z == 0 and r > 0:\n        size = 2 * r + 1\n        struct = np.zeros((1, size, size), dtype=bool)\n        cy, cx = r, r\n        for dy in range(-r, r + 1):\n            for dx in range(-r, r + 1):\n                if dy * dy + dx * dx <= r * r:\n                    struct[0, cy + dy, cx + dx] = True\n        return struct\n    if z > 0 and r == 0:\n        struct = np.zeros((2 * z + 1, 1, 1), dtype=bool)\n        struct[:, 0, 0] = True\n        return struct\n    depth = 2 * z + 1\n    size = 2 * r + 1\n    struct = np.zeros((depth, size, size), dtype=bool)\n    cz, cy, cx = z, r, r\n    for dz in range(-z, z + 1):\n        for dy in range(-r, r + 1):\n            for dx in range(-r, r + 1):\n                if dy * dy + dx * dx <= r * r:\n                    struct[cz + dz, cy + dy, cx + dx] = True\n    return struct\n\ndef topo_postprocess(\n    probs,\n    T_low=0.90,\n    T_high=0.90,\n    z_radius=1,\n    xy_radius=0,\n    dust_min_size=100,\n):\n    # Step 1: 3D Hysteresis\n    strong = probs >= T_high\n    weak   = probs >= T_low\n\n    if not strong.any():\n        return np.zeros_like(probs, dtype=np.uint8)\n\n    struct_hyst = ndi.generate_binary_structure(3, 3)\n    mask = ndi.binary_propagation(\n        strong, mask=weak, structure=struct_hyst\n    )\n\n    if not mask.any():\n        return np.zeros_like(probs, dtype=np.uint8)\n\n    # Step 2: 3D Anisotropic Closing\n    if z_radius > 0 or xy_radius > 0:\n        struct_close = build_anisotropic_struct(z_radius, xy_radius)\n        if struct_close is not None:\n            mask = ndi.binary_closing(mask, structure=struct_close)\n\n    # Step 3: Dust Removal\n    if dust_min_size > 0:\n        mask = remove_small_objects(\n            mask.astype(bool), min_size=dust_min_size\n        )\n\n    return mask.astype(np.uint8)","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.653828Z","iopub.execute_input":"2026-02-12T04:54:09.654025Z","iopub.status.idle":"2026-02-12T04:54:09.666213Z","shell.execute_reply.started":"2026-02-12T04:54:09.654009Z","shell.execute_reply":"2026-02-12T04:54:09.665541Z"},"papermill":{"duration":0.014219,"end_time":"2026-02-03T18:37:38.427686","exception":false,"start_time":"2026-02-03T18:37:38.413467","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Prediction and Zip Submission**","metadata":{"papermill":{"duration":0.004017,"end_time":"2026-02-03T18:37:38.435768","exception":false,"start_time":"2026-02-03T18:37:38.431751","status":"completed"},"tags":[]}},{"cell_type":"code","source":"CFG[\"MODE\"] = \"quantile\"\nCFG[\"USE_FRANGI\"] = False\nCFG[\"HYST_BAND\"] = 0.0\n\nCFG[\"TTA_MODE\"] = \"full\"\nCFG[\"TTA_FLIP_AXES\"] = (1,2,3)\nCFG[\"TTA_ROT_KS\"] = (1,2,3)\nCFG[\"TTA_AGG\"] = \"mean\"\n\nCFG[\"Z_RADIUS\"] = 3\nCFG[\"XY_RADIUS\"] = 2\nCFG[\"DUST_MIN_SIZE\"] = 150\n\nCFG[\"LOGIT_TEMP\"] = 1.15\nCFG[\"PROB_GAMMA\"] = 0.90\n\nimage_id = test_df[\"id\"].iloc[0]\nvolume = load_volume(f\"{test_dir}/{image_id}.tif\")\nvolume = val_transformation(volume)\n\nwarmup_jit(swi, sample_volume=volume)   \nwith zipfile.ZipFile(zip_path, \"w\", compression=zipfile.ZIP_DEFLATED) as z:\n    for image_id in test_df[\"id\"]:\n        tif_path = f\"{test_dir}/{image_id}.tif\"\n\n        volume = load_volume(tif_path)\n        volume = val_transformation(volume)\n\n        mask = inference_pipelines(volume, swi)  \n\n        out_path = f\"{output_dir}/{image_id}.tif\"\n        tifffile.imwrite(out_path, mask.astype(np.uint8))\n\n        z.write(out_path, arcname=f\"{image_id}.tif\")\n        os.remove(out_path)\n\nprint(\"Submission ZIP:\", zip_path)\n","metadata":{"execution":{"iopub.status.busy":"2026-02-12T04:54:09.666927Z","iopub.execute_input":"2026-02-12T04:54:09.667244Z","iopub.status.idle":"2026-02-12T04:56:59.65969Z","shell.execute_reply.started":"2026-02-12T04:54:09.667223Z","shell.execute_reply":"2026-02-12T04:56:59.658937Z"},"papermill":{"duration":159.78353,"end_time":"2026-02-03T18:40:18.236665","exception":false,"start_time":"2026-02-03T18:37:38.453135","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import hashlib, copy, numpy as np\n\n# def mask_sig(m):\n#     m = np.ascontiguousarray(m.astype(np.uint8))\n#     return {\n#         \"sum\": int(m.sum()),\n#         \"md5\": hashlib.md5(m.tobytes()).hexdigest(),\n#     }\n\n# def run_with_cfg(cfg_updates):\n#     old = copy.deepcopy(CFG)\n#     CFG.update(cfg_updates)\n\n#     # run one volume\n#     image_id = test_df[\"id\"].iloc[0]\n#     vol = load_volume(f\"{test_dir}/{image_id}.tif\")\n#     vol = val_transformation(vol)\n#     m = inference_pipelines(vol, swi)\n\n#     sig = mask_sig(m)\n#     CFG.clear(); CFG.update(old)\n#     return m, sig\n\n# # Example: compare two configs\n# m1, s1 = run_with_cfg({\"HYST_BAND\": 0.0, \"Z_RADIUS\": 3, \"LOGIT_TEMP\": 1.0})\n# m2, s2 = run_with_cfg({\"HYST_BAND\": 0.05, \"Z_RADIUS\": 3, \"LOGIT_TEMP\": 1.0})\n\n# print(\"sig1\", s1)\n# print(\"sig2\", s2)\n# print(\"voxels different:\", int((m1 ^ m2).sum()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-12T04:56:59.660442Z","iopub.execute_input":"2026-02-12T04:56:59.660742Z","iopub.status.idle":"2026-02-12T05:01:45.207224Z","shell.execute_reply.started":"2026-02-12T04:56:59.660712Z","shell.execute_reply":"2026-02-12T05:01:45.20649Z"}},"outputs":[],"execution_count":null}]}