{"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":[{"sourceId":117682,"databundleVersionId":15062069,"sourceType":"competition"},{"sourceId":290917305,"sourceType":"kernelVersion"},{"sourceId":655294,"sourceType":"modelInstanceVersion","modelInstanceId":495238,"modelId":510647},{"sourceId":660383,"sourceType":"modelInstanceVersion","modelInstanceId":499479,"modelId":510647},{"sourceId":665589,"sourceType":"modelInstanceVersion","modelInstanceId":503784,"modelId":510647},{"sourceId":665924,"sourceType":"modelInstanceVersion","modelInstanceId":504051,"modelId":510647},{"sourceId":672178,"sourceType":"modelInstanceVersion","modelInstanceId":495238,"modelId":510647},{"sourceId":673516,"sourceType":"modelInstanceVersion","modelInstanceId":499479,"modelId":510647},{"sourceId":674747,"sourceType":"modelInstanceVersion","modelInstanceId":503784,"modelId":510647},{"sourceId":681152,"sourceType":"modelInstanceVersion","modelInstanceId":516822,"modelId":510647},{"sourceId":732880,"sourceType":"modelInstanceVersion","modelInstanceId":516822,"modelId":510647}],"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":"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.execute_input":"2026-02-03T18:36:41.211389Z","iopub.status.busy":"2026-02-03T18:36:41.21119Z","iopub.status.idle":"2026-02-03T18:36:50.913379Z","shell.execute_reply":"2026-02-03T18:36:50.912701Z"},"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":[]},"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.execute_input":"2026-02-03T18:36:50.923037Z","iopub.status.busy":"2026-02-03T18:36:50.922777Z","iopub.status.idle":"2026-02-03T18:37:17.10031Z","shell.execute_reply":"2026-02-03T18:37:17.099667Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:17.119815Z","iopub.status.busy":"2026-02-03T18:37:17.119024Z","iopub.status.idle":"2026-02-03T18:37:17.123204Z","shell.execute_reply":"2026-02-03T18:37:17.122635Z"},"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":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(f\"{root_dir}/test.csv\")\ntest_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:17.133798Z","iopub.status.busy":"2026-02-03T18:37:17.13316Z","iopub.status.idle":"2026-02-03T18:37:17.180528Z","shell.execute_reply":"2026-02-03T18:37:17.179674Z"},"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":[]},"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":"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.execute_input":"2026-02-03T18:37:17.199852Z","iopub.status.busy":"2026-02-03T18:37:17.199614Z","iopub.status.idle":"2026-02-03T18:37:17.203397Z","shell.execute_reply":"2026-02-03T18:37:17.202706Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:17.221145Z","iopub.status.busy":"2026-02-03T18:37:17.220948Z","iopub.status.idle":"2026-02-03T18:37:17.22403Z","shell.execute_reply":"2026-02-03T18:37:17.223531Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:17.233718Z","iopub.status.busy":"2026-02-03T18:37:17.233481Z","iopub.status.idle":"2026-02-03T18:37:17.237711Z","shell.execute_reply":"2026-02-03T18:37:17.237165Z"},"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":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = get_model()\nmodel.count_params() / 1e6","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:17.247402Z","iopub.status.busy":"2026-02-03T18:37:17.247187Z","iopub.status.idle":"2026-02-03T18:37:38.272079Z","shell.execute_reply":"2026-02-03T18:37:38.271454Z"},"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":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.instance_describe()","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:38.282907Z","iopub.status.busy":"2026-02-03T18:37:38.282313Z","iopub.status.idle":"2026-02-03T18:37:38.339944Z","shell.execute_reply":"2026-02-03T18:37:38.339207Z"},"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":[]},"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 = SlidingWindowInference(\n    model,\n    num_classes=3,\n    roi_size=input_shape,\n    sw_batch_size=1,\n    mode='gaussian',\n    overlap=0.48,\n)","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:38.358274Z","iopub.status.busy":"2026-02-03T18:37:38.357886Z","iopub.status.idle":"2026-02-03T18:37:38.361372Z","shell.execute_reply":"2026-02-03T18:37:38.360694Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:38.372422Z","iopub.status.busy":"2026-02-03T18:37:38.371701Z","iopub.status.idle":"2026-02-03T18:37:38.375339Z","shell.execute_reply":"2026-02-03T18:37:38.374832Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:38.394698Z","iopub.status.busy":"2026-02-03T18:37:38.394468Z","iopub.status.idle":"2026-02-03T18:37:38.399619Z","shell.execute_reply":"2026-02-03T18:37:38.398946Z"},"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":[]},"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.execute_input":"2026-02-03T18:37:38.418883Z","iopub.status.busy":"2026-02-03T18:37:38.418435Z","iopub.status.idle":"2026-02-03T18:37:38.426668Z","shell.execute_reply":"2026-02-03T18:37:38.426149Z"},"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":[]},"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":"def inference_pipelines(\n    volume,\n    T_low=0.30,\n    T_high=0.80,\n    z_radius=3,\n    xy_radius=2,\n    dust_min_size=120,\n):\n    probs = predict_with_tta(volume, swi)\n    final = topo_postprocess(\n        probs,\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 final","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:38.444586Z","iopub.status.busy":"2026-02-03T18:37:38.444362Z","iopub.status.idle":"2026-02-03T18:37:38.447917Z","shell.execute_reply":"2026-02-03T18:37:38.44735Z"},"papermill":{"duration":0.009289,"end_time":"2026-02-03T18:37:38.449071","exception":false,"start_time":"2026-02-03T18:37:38.439782","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with zipfile.ZipFile(\n    zip_path, \"w\", compression=zipfile.ZIP_DEFLATED\n) 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        output = inference_pipelines(volume) \n        \n        out_path = f\"{output_dir}/{image_id}.tif\"\n        tifffile.imwrite(out_path, output.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)","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:37:38.457995Z","iopub.status.busy":"2026-02-03T18:37:38.457795Z","iopub.status.idle":"2026-02-03T18:40:18.235366Z","shell.execute_reply":"2026-02-03T18:40:18.234557Z"},"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":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Sample View**","metadata":{"papermill":{"duration":0.012465,"end_time":"2026-02-03T18:40:18.262355","exception":false,"start_time":"2026-02-03T18:40:18.24989","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def plot_sample(x, y, sample_idx=0, max_slices=16):\n    img = np.squeeze(x[sample_idx])  # make (D, H, W)\n    mask = np.squeeze(y[sample_idx])  # make (D, H, W)\n    D = img.shape[0]\n\n    # Decide which slices to plot\n    step = max(1, D // max_slices)\n    slices = range(0, D, step)\n\n    n_slices = len(slices)\n    fig, axes = plt.subplots(2, n_slices, figsize=(3*n_slices, 6))\n\n    for i, s in enumerate(slices):\n        axes[0, i].imshow(img[s], cmap='gray')\n        axes[0, i].set_title(f\"Slice {s}\")\n        axes[0, i].axis('off')\n\n        axes[1, i].imshow(mask[s], cmap='gray')\n        axes[1, i].set_title(f\"Mask {s}\")\n        axes[1, i].axis('off')\n\n    plt.suptitle(f\"Sample {sample_idx}\")\n    plt.tight_layout()\n    plt.show()","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:40:18.287795Z","iopub.status.busy":"2026-02-03T18:40:18.287163Z","iopub.status.idle":"2026-02-03T18:40:18.293272Z","shell.execute_reply":"2026-02-03T18:40:18.292704Z"},"papermill":{"duration":0.019987,"end_time":"2026-02-03T18:40:18.294298","exception":false,"start_time":"2026-02-03T18:40:18.274311","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_sample(\n    volume.numpy(), output[None], sample_idx=0, max_slices=5\n)","metadata":{"execution":{"iopub.execute_input":"2026-02-03T18:40:18.321565Z","iopub.status.busy":"2026-02-03T18:40:18.321314Z","iopub.status.idle":"2026-02-03T18:40:19.553104Z","shell.execute_reply":"2026-02-03T18:40:19.552348Z"},"papermill":{"duration":1.25296,"end_time":"2026-02-03T18:40:19.560077","exception":false,"start_time":"2026-02-03T18:40:18.307117","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.018817,"end_time":"2026-02-03T18:40:19.598285","exception":false,"start_time":"2026-02-03T18:40:19.579468","status":"completed"},"tags":[]},"outputs":[],"execution_count":null}]}