{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"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}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# CELL 1: PROTOBUF FIX — RUN THEN RESTART KERNEL\n# ============================================================\nimport os\nos.environ[\"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION\"] = \"python\"\n\n# Install compatible protobuf WITHOUT uninstalling (let pip resolve)\n!pip install protobuf==4.25.3 grpcio --quiet\n\n# Install offline packages\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 --find-links \"$var\" --quiet --no-deps\n\nprint(\"=\"*50)\nprint(\"✅ DONE! NOW: Kernel → Restart Kernel → Run Cell 2\")\nprint(\"=\"*50)\n\n\n# ============================================================\n# CELL 2: VESUVIUS SURFACE DETECTION (Run after restart)\n# ============================================================\nimport os\nos.environ[\"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION\"] = \"python\"\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\n\nimport keras\nfrom medicai.transforms import Compose, NormalizeIntensity\nfrom medicai.models import TransUNet\nfrom medicai.utils.inference import SlidingWindowInference\nimport numpy as np\nimport pandas as pd\nimport zipfile\nimport tifffile\nimport scipy.ndimage as ndi\nfrom skimage.morphology import remove_small_objects\n\nprint(f\"✅ Backend: {keras.config.backend()}, Version: {keras.__version__}\")\n\n# ============================================================\n# CONFIG\n# ============================================================\nroot_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)\n\ntest_df = pd.read_csv(f\"{root_dir}/test.csv\")\nprint(f\"📊 Test samples: {len(test_df)}\")\n\n# ============================================================\n# TRANSFORMATION\n# ============================================================\ndef val_transformation(image):\n    data = {\"image\": image}\n    pipeline = Compose([\n        NormalizeIntensity(keys=[\"image\"], nonzero=True, channel_wise=False)\n    ])\n    return pipeline(data)[\"image\"]\n\n# ============================================================\n# MODEL\n# ============================================================\ninput_shape = (160, 160, 160)\nkaggle_model_path = \"/kaggle/input/vsd-model/keras\"\n\nmodel = TransUNet(\n    input_shape=(160, 160, 160, 1),\n    encoder_name='seresnext50',\n    classifier_activation='softmax',\n    num_classes=3\n)\nmodel.load_weights(\n    f\"{kaggle_model_path}/transunet/3/transunet.seresnext50.160px.comboloss.weights.h5\"\n)\nprint(f\"🧠 Model params: {model.count_params() / 1e6:.2f}M\")\n\n# ============================================================\n# SLIDING WINDOW INFERENCE\n# ============================================================\nswi = SlidingWindowInference(\n    model,\n    num_classes=3,\n    roi_size=input_shape,\n    sw_batch_size=1,\n    mode='gaussian',\n    overlap=0.5\n)\n\ndef load_volume(path):\n    vol = tifffile.imread(path).astype(np.float32)\n    return vol[None, ..., None]\n\n# ============================================================\n# TTA (Test Time Augmentation)\n# ============================================================\ndef predict_with_tta(inputs, swi):\n    logits = [swi(inputs)]\n    \n    # Flip augmentations\n    for axis in [1, 2, 3]:\n        img_f = np.flip(inputs, axis=axis)\n        p = np.flip(swi(img_f), axis=axis)\n        logits.append(p)\n    \n    # Rotation augmentations\n    for k in [1, 2, 3]:\n        img_r = np.rot90(inputs, k=k, axes=(2, 3))\n        p = np.rot90(swi(img_r), k=-k, axes=(2, 3))\n        logits.append(p)\n    \n    return np.mean(logits, axis=0).argmax(-1).astype(np.uint8).squeeze()\n\n# ============================================================\n# POST PROCESSING\n# ============================================================\ndef build_anisotropic_struct(z_radius, xy_radius):\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        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, r + dy, r + 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    \n    depth, size = 2 * z + 1, 2 * r + 1\n    struct = np.zeros((depth, size, size), dtype=bool)\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[z + dz, r + dy, r + dx] = True\n    return struct\n\ndef topo_postprocess(probs, T_low=0.90, T_high=0.90, z_radius=1, xy_radius=0, dust_min_size=100):\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(strong, mask=weak, structure=struct_hyst)\n    \n    if not mask.any():\n        return np.zeros_like(probs, dtype=np.uint8)\n    \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    if dust_min_size > 0:\n        mask = remove_small_objects(mask.astype(bool), min_size=dust_min_size)\n    \n    return mask.astype(np.uint8)\n\n# ============================================================\n# INFERENCE PIPELINE\n# ============================================================\ndef inference_pipeline(volume):\n    probs = predict_with_tta(volume, swi)\n    return topo_postprocess(probs, T_low=0.50, T_high=0.90, z_radius=1, xy_radius=0, dust_min_size=100)\n\n# ============================================================\n# GENERATE SUBMISSION\n# ============================================================\nprint(\"\\n🔄 Processing test volumes...\")\n\nwith zipfile.ZipFile(zip_path, \"w\", compression=zipfile.ZIP_DEFLATED) as z:\n    for idx, image_id in enumerate(test_df[\"id\"]):\n        print(f\"  [{idx+1}/{len(test_df)}] Processing {image_id}...\")\n        \n        volume = load_volume(f\"{test_dir}/{image_id}.tif\")\n        volume = val_transformation(volume)\n        output = inference_pipeline(volume)\n        \n        out_path = f\"{output_dir}/{image_id}.tif\"\n        tifffile.imwrite(out_path, output.astype(np.uint8))\n        z.write(out_path, arcname=f\"{image_id}.tif\")\n        os.remove(out_path)\n\nprint(f\"\\n{'='*50}\")\nprint(f\"✅ SUBMISSION COMPLETE: {zip_path}\")\nprint(f\"{'='*50}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}