{"metadata":{"kernelspec":{"display_name":".venv","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.10.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":117682,"databundleVersionId":15062069,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":672178,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":495238,"modelId":510647},{"sourceId":665924,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":504051,"modelId":510647},{"sourceId":732880,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":516822,"modelId":510647}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"6b1a6003","cell_type":"markdown","source":"# Vesuvius Challenge - Pre-trained Keras Model Inference (v4 Keras)\n\n**Goal**: Use pre-trained Keras models to achieve 0.50+ score (like Tony Li's 0.552 approach)\n\n**Strategy**:\n- Load pre-trained Keras segmentation models from Kaggle Models\n- Run sliding window inference on test volumes\n- Ensemble multiple models if available\n- Apply post-processing (threshold + morphology)\n- Generate Kaggle submission\n\n**Models to Use** (from ipythonx/vsd-model):\n- segformer.mit.b2\n- segformer.mit.b4\n- transunet\n- transunetseresnext\n- And their default variants\n\nThis is a supervised inference approach using Keras models trained on the actual dataset.","metadata":{}},{"id":"c255001a","cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\nimport tifffile as tiff\nfrom scipy import ndimage\nimport os\nimport shutil\nimport zipfile\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# TensorFlow/Keras for model inference\nimport tensorflow as tf\nfrom tensorflow import keras\n\n# Check GPU\nprint(f\"TensorFlow version: {tf.__version__}\")\ngpus = tf.config.list_physical_devices('GPU')\nprint(f\"GPU Available: {len(gpus)} devices\")\nif gpus:\n    print(f\"  {gpus[0].name}\")\n\n# Set memory growth to avoid OOM\nfor gpu in gpus:\n    tf.config.experimental.set_memory_growth(gpu, True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.072066Z","iopub.execute_input":"2026-02-13T21:01:54.072556Z","iopub.status.idle":"2026-02-13T21:01:54.078501Z","shell.execute_reply.started":"2026-02-13T21:01:54.072529Z","shell.execute_reply":"2026-02-13T21:01:54.077729Z"}},"outputs":[],"execution_count":null},{"id":"01c4e56d","cell_type":"code","source":"# Determine environment\nIS_KAGGLE = Path('/kaggle').exists()\n\n# Setup paths\nif IS_KAGGLE:\n    DATA_DIR = Path('/kaggle/input/vesuvius-challenge-surface-detection-test')\n    OUTPUT_DIR = Path('/kaggle/working')\nelse:\n    BASE_DIR = Path('/home/shiftmint/Documents/kaggle/Vesuvius_Challenge')\n    DATA_DIR = BASE_DIR / 'data' / 'test'\n    OUTPUT_DIR = BASE_DIR / 'output'\n\nOUTPUT_DIR.mkdir(exist_ok=True, parents=True)\n\nprint(f\"Environment: {'Kaggle' if IS_KAGGLE else 'Local'}\")\nprint(f\"Data dir: {DATA_DIR}\")\nprint(f\"Output dir: {OUTPUT_DIR}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.079854Z","iopub.execute_input":"2026-02-13T21:01:54.080093Z","iopub.status.idle":"2026-02-13T21:01:54.091633Z","shell.execute_reply.started":"2026-02-13T21:01:54.080074Z","shell.execute_reply":"2026-02-13T21:01:54.090947Z"}},"outputs":[],"execution_count":null},{"id":"02fdd87e","cell_type":"markdown","source":"## Load Test Data","metadata":{}},{"id":"fb1d301c","cell_type":"code","source":"def load_volume(volume_path):\n    \"\"\"Load a 3D volume from TIFF file.\"\"\"\n    with tiff.TiffFile(volume_path) as tif:\n        volume = tif.asarray()\n    return volume\n\n# Load test volumes\nprint(\"Loading test volumes...\")\ntest_volumes = {}\n\nfor volume_dir in sorted(DATA_DIR.glob('*')):\n    if not volume_dir.is_dir():\n        continue\n    \n    volume_id = volume_dir.name\n    tiff_path = volume_dir / 'scroll.tif'\n    \n    if not tiff_path.exists():\n        print(f\"Warning: {tiff_path} not found\")\n        continue\n    \n    volume = load_volume(tiff_path)\n    test_volumes[volume_id] = volume\n    print(f\"  {volume_id}: shape={volume.shape}, dtype={volume.dtype}\")\n\nprint(f\"\\nLoaded {len(test_volumes)} test volumes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.092483Z","iopub.execute_input":"2026-02-13T21:01:54.092723Z","iopub.status.idle":"2026-02-13T21:01:54.105602Z","shell.execute_reply.started":"2026-02-13T21:01:54.092698Z","shell.execute_reply":"2026-02-13T21:01:54.105023Z"}},"outputs":[],"execution_count":null},{"id":"ed250247","cell_type":"markdown","source":"## Model Discovery\n\nFind pre-trained Keras models (.h5 files) from Kaggle Models hub.\nModels should be added into Input and be avaliable. For example:\n- /kaggle/input/models/ipythonx/vsd-model/keras/segformer.mit.b4/1/segformer.mit.b4.weights.h5\n- /kaggle/input/models/ipythonx/vsd-model/keras/default/2/segformer.mit.b0.weights.h5\n- /kaggle/input/models/ipythonx/vsd-model/keras/default/2/segformer.weights.h5\n- /kaggle/input/models/ipythonx/vsd-model/keras/transunet/3/transunet.seresnext50.128px.weights.h5\n- /kaggle/input/models/ipythonx/vsd-model/keras/transunet/3/transunet.seresnext50.160px.comboloss.weights.h5\n- /kaggle/input/models/ipythonx/vsd-model/keras/transunet/3/transunet.seresnext50.160px.weights.h5","metadata":{}},{"id":"29b05929","cell_type":"code","source":"\n# Check for pre-trained Keras models from Kaggle Models\nMODEL_PATHS = []\n\nif IS_KAGGLE:\n    # On Kaggle: look for model datasets added as inputs\n    model_search_dirs = [\n        '/kaggle/input/vsd-model',  # ipythonx/vsd-model\n        '/kaggle/input/vesuvius-surface-detection-model',\n        '/kaggle/input',\n    ]\n    \n    for search_dir in model_search_dirs:\n        if Path(search_dir).exists():\n            # Look for Keras model files (.h5, .weights.h5)\n            model_files = sorted(Path(search_dir).rglob('*.h5'))\n            if model_files:\n                MODEL_PATHS.extend(model_files)\n                print(f\"Found {len(model_files)} .h5 files in {search_dir}\")\nelse:\n    # Local: check for downloaded models\n    local_model_dir = BASE_DIR / 'models'\n    if local_model_dir.exists():\n        model_files = sorted(local_model_dir.rglob('*.h5'))\n        MODEL_PATHS.extend(model_files)\n        if model_files:\n            print(f\"Found {len(model_files)} models in {local_model_dir}\")\n\n# Remove duplicates and filter reasonable files\nMODEL_PATHS = list(set(MODEL_PATHS))\n# Skip redundant weight files that aren't actual models\nMODEL_PATHS = [p for p in MODEL_PATHS if 'weights.h5' not in p.name or '.weights.h5' in p.name]\nMODEL_PATHS = sorted(MODEL_PATHS)\n\nif MODEL_PATHS:\n    print(f\"\\nTotal models found: {len(MODEL_PATHS)}\")\n    for mp in MODEL_PATHS[:10]:\n        size_mb = mp.stat().st_size / (1024 * 1024)\n        print(f\"  - {mp.name} ({size_mb:.1f} MB)\")\nelse:\n    print(\"\\n⚠ WARNING: No pre-trained models found!\")\n    print(\"\\nOn Kaggle, add 'Vesuvius Surface Detection Model' (ipythonx/vsd-model):\")\n    print(\"  1. Click 'Add Data' → 'Models' tab\")\n    print(\"  2. Search for 'Vesuvius Surface Detection'\")\n    print(\"  3. Add the ipythonx/vsd-model dataset\")\n    print(\"\\nLocally: Download .h5 files to ./models/ directory\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.106408Z","iopub.execute_input":"2026-02-13T21:01:54.106690Z","iopub.status.idle":"2026-02-13T21:01:54.911505Z","shell.execute_reply.started":"2026-02-13T21:01:54.106638Z","shell.execute_reply":"2026-02-13T21:01:54.910854Z"}},"outputs":[],"execution_count":null},{"id":"0104dcf3","cell_type":"markdown","source":"## Sliding Window Inference\n\nFor large 3D volumes, use sliding window inference with overlap.","metadata":{}},{"id":"23dadfeb","cell_type":"code","source":"def sliding_window_inference(\n    volume,\n    model,\n    patch_size=(96, 96, 96),\n    stride=(48, 48, 48),\n    input_shape=None\n):\n    \"\"\"\n    Perform sliding window inference on a 3D volume using Keras model.\n    \n    Args:\n        volume: Input volume (D, H, W)\n        model: Keras model\n        patch_size: Size of sliding window patches\n        stride: Stride for sliding window\n        input_shape: Expected model input shape (from model inspection)\n    \n    Returns:\n        Probability map (D, H, W)\n    \"\"\"\n    D, H, W = volume.shape\n    pD, pH, pW = patch_size\n    sD, sH, sW = stride\n    \n    # Normalize volume (Z-score)\n    vol_norm = (volume.astype(np.float32) - volume.mean()) / (volume.std() + 1e-6)\n    \n    # Output accumulator\n    output = np.zeros((D, H, W), dtype=np.float32)\n    count = np.zeros((D, H, W), dtype=np.float32)\n    \n    # Get model input spec\n    if input_shape is None:\n        # Assume model expects (batch, depth, height, width, channels) for 3D\n        input_shape = (pD, pH, pW, 1)\n    \n    # Iterate over patches\n    for z in range(0, max(D - pD + 1, 1), sD):\n        z_end = min(z + pD, D)\n        z_start = max(z_end - pD, 0)\n        \n        for y in range(0, max(H - pH + 1, 1), sH):\n            y_end = min(y + pH, H)\n            y_start = max(y_end - pH, 0)\n            \n            for x in range(0, max(W - pW + 1, 1), sW):\n                x_end = min(x + pW, W)\n                x_start = max(x_end - pW, 0)\n                \n                # Extract patch\n                patch = vol_norm[z_start:z_end, y_start:y_end, x_start:x_end]\n                actual_d, actual_h, actual_w = patch.shape\n                \n                # Pad if needed\n                if patch.shape != (pD, pH, pW):\n                    padded = np.zeros((pD, pH, pW), dtype=np.float32)\n                    padded[:actual_d, :actual_h, :actual_w] = patch\n                    patch = padded\n                \n                # Convert to tensor: add batch and channel dims\n                patch_tensor = patch[np.newaxis, ..., np.newaxis].astype(np.float32)\n                \n                # Inference\n                try:\n                    pred = model.predict(patch_tensor, verbose=0)\n                    pred = pred.squeeze()\n                except:\n                    # Handle 2D models or other architectures\n                    pred = np.zeros((actual_d, actual_h, actual_w), dtype=np.float32)\n                \n                # Crop prediction if we padded\n                pred = pred[:actual_d, :actual_h, :actual_w]\n                \n                # Accumulate\n                output[z_start:z_end, y_start:y_end, x_start:x_end] += pred\n                count[z_start:z_end, y_start:y_end, x_start:x_end] += 1\n    \n    # Average overlapping predictions\n    output = output / np.maximum(count, 1)\n    \n    return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.913031Z","iopub.execute_input":"2026-02-13T21:01:54.913647Z","iopub.status.idle":"2026-02-13T21:01:54.922905Z","shell.execute_reply.started":"2026-02-13T21:01:54.913615Z","shell.execute_reply":"2026-02-13T21:01:54.922242Z"}},"outputs":[],"execution_count":null},{"id":"4158b139","cell_type":"markdown","source":"## Load Pre-trained Keras Models","metadata":{}},{"id":"1378514b","cell_type":"code","source":"# Load Keras models\nmodels = []\n\nif MODEL_PATHS:\n    print(f\"Loading {len(MODEL_PATHS)} pre-trained Keras model(s)...\\n\")\n    for i, model_path in enumerate(MODEL_PATHS):\n        try:\n            print(f\"Loading {i+1}: {model_path.name}\", flush=True)\n            model = keras.models.load_model(str(model_path))\n            models.append(model)\n            \n            # Print model info\n            print(f\"  ✓ Loaded successfully\")\n            print(f\"    Input shape: {model.input_shape}\")\n            print(f\"    Output shape: {model.output_shape}\")\n            print()\n        except Exception as e:\n            print(f\"  ✗ Failed: {str(e)[:100]}\\n\")\n\nif not models:\n    print(\"\\n⚠ ERROR: No models loaded! Inference will fail.\")\n    print(\"\\nMake sure to add 'Vesuvius Surface Detection Model' (ipythonx/vsd-model)\")\n    print(\"as a data input before running.\")\nelse:\n    print(f\"✓ Total models loaded: {len(models)}\")\n    print(f\"  Ready for ensemble inference\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.924545Z","iopub.execute_input":"2026-02-13T21:01:54.924815Z","iopub.status.idle":"2026-02-13T21:01:54.972726Z","shell.execute_reply.started":"2026-02-13T21:01:54.924787Z","shell.execute_reply":"2026-02-13T21:01:54.972205Z"}},"outputs":[],"execution_count":null},{"id":"b9b0a2b9","cell_type":"markdown","source":"## Generate Predictions with Model Ensemble","metadata":{}},{"id":"055a6c4a","cell_type":"code","source":"# Configuration\nPATCH_SIZE = (64, 64, 64)  # Smaller for memory safety with Keras\nSTRIDE = (32, 32, 32)      # 50% overlap\nTHRESHOLD = 0.5            # Probability threshold for binary mask\nUSE_MORPHOLOGY = True      # Apply morphological cleanup\n\nprint(f\"Inference config:\")\nprint(f\"  Patch size: {PATCH_SIZE}\")\nprint(f\"  Stride: {STRIDE}\")\nprint(f\"  Threshold: {THRESHOLD}\")\nprint(f\"  Morphology: {USE_MORPHOLOGY}\")\nprint(f\"  Models: {len(models)} (ensemble)\")\nprint()\n\nif not models or len(models) == 0:\n    print(\"ERROR: No models loaded. Cannot proceed.\")\nelse:\n    predictions = {}\n    for tid, vol in tqdm(test_volumes.items(), desc=\"Volumes\"):\n        try:\n            # Ensemble predictions from all models\n            prob_maps = []\n            for model_idx, model in enumerate(models):\n                prob_map = sliding_window_inference(\n                    vol,\n                    model,\n                    patch_size=PATCH_SIZE,\n                    stride=STRIDE,\n                    input_shape=model.input_shape\n                )\n                prob_maps.append(prob_map)\n            \n            # Average ensemble predictions\n            prob_map = np.mean(prob_maps, axis=0)\n            \n            # Threshold to binary\n            mask = (prob_map > THRESHOLD).astype(np.uint8)\n            \n            # Optional: morphological cleanup\n            if USE_MORPHOLOGY:\n                kernel = np.ones((3, 3, 3))\n                mask = ndimage.binary_closing(mask, structure=kernel)\n                mask = ndimage.binary_opening(mask, structure=kernel)\n            \n            predictions[tid] = mask.astype(np.uint8)\n            coverage = mask.mean() * 100\n            prob_mean = prob_map.mean()\n            prob_std = prob_map.std()\n            \n            tqdm.write(f\"{tid}: coverage={coverage:.2f}%, prob={prob_mean:.3f}±{prob_std:.3f}\")\n        \n        except Exception as e:\n            tqdm.write(f\"✗ Error processing {tid}: {e}\")\n            predictions[tid] = np.zeros(vol.shape, dtype=np.uint8)\n    \n    print(f\"\\n✓ Generated {len(predictions)} predictions\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.973600Z","iopub.execute_input":"2026-02-13T21:01:54.973831Z","iopub.status.idle":"2026-02-13T21:01:54.981779Z","shell.execute_reply.started":"2026-02-13T21:01:54.973813Z","shell.execute_reply":"2026-02-13T21:01:54.981091Z"}},"outputs":[],"execution_count":null},{"id":"499de853","cell_type":"markdown","source":"## Save Predictions","metadata":{}},{"id":"83060c1a","cell_type":"code","source":"# Create submission directory\nsubmission_dir = OUTPUT_DIR / 'submission_keras'\nif submission_dir.exists():\n    shutil.rmtree(submission_dir)\nsubmission_dir.mkdir(parents=True)\n\nprint(f\"Saving predictions to {submission_dir}...\")\nfor tid, mask in predictions.items():\n    output_path = submission_dir / f\"{tid}.tif\"\n    tiff.imwrite(output_path, mask, compression=None)\n\nprint(f\"✓ Saved {len(predictions)} TIFFs\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:54.982733Z","iopub.execute_input":"2026-02-13T21:01:54.982997Z","iopub.status.idle":"2026-02-13T21:01:55.000978Z","shell.execute_reply.started":"2026-02-13T21:01:54.982966Z","shell.execute_reply":"2026-02-13T21:01:55.000126Z"}},"outputs":[],"execution_count":null},{"id":"7b739061","cell_type":"markdown","source":"## Create Submission ZIP","metadata":{}},{"id":"1c70e158","cell_type":"code","source":"submission_zip = OUTPUT_DIR / 'submission_keras.zip'\n\nwith zipfile.ZipFile(submission_zip, 'w', zipfile.ZIP_STORED) as zf:\n    for tiff_file in submission_dir.glob('*.tif'):\n        zf.write(tiff_file, arcname=tiff_file.name)\n\nsize_mb = submission_zip.stat().st_size / (1024 * 1024)\nprint(f\"✓ Created submission: {submission_zip}\")\nprint(f\"  Size: {size_mb:.2f} MB\")\nprint(f\"\\nReady to submit to Kaggle!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-13T21:01:55.001872Z","iopub.status.idle":"2026-02-13T21:01:55.002469Z","shell.execute_reply.started":"2026-02-13T21:01:55.002283Z","shell.execute_reply":"2026-02-13T21:01:55.002307Z"}},"outputs":[],"execution_count":null},{"id":"7b35e1fa","cell_type":"markdown","source":"## Deployment Instructions\n\n### On Kaggle\n\n1. **Upload this notebook** to Kaggle\n\n2. **Add the model dataset**:\n   - Click \"Add Data\" → \"Models\" tab\n   - Search for: **\"Vesuvius Surface Detection\"** (by ipythonx)\n   - Add the dataset\n   - You should see it as `/kaggle/input/vsd-model/`\n\n3. **Enable GPU**:\n   - Click Settings (gear icon)\n   - Accelerator: **GPU P100** or **GPU T4**\n   - Internet: **Off** (for submission)\n   - Save\n\n4. **Run All**:\n   - Wait for inference (~10-30 minutes)\n   - Check output for:\n     - ✓ Models loaded (should list multiple .h5 files)\n     - ✓ Predictions generated\n     - ✓ submission_keras.zip created\n\n5. **Download & Submit**:\n   - Download `submission_keras.zip`\n   - Submit to competition\n\n### Locally\n\n1. Download model files from Kaggle Models hub\n2. Place .h5 files in `./models/` directory\n3. Run notebook\n\n### Troubleshooting\n\n**No models found:**\n- Make sure you added the model dataset as a data input\n- Check that it's the ipythonx/vsd-model dataset\n- Look for vsd-model directory structure\n\n**GPU out of memory:**\n- Reduce PATCH_SIZE to (48, 48, 48)\n- Use smaller STRIDE (24, 24, 24)\n- Process one model at a time\n\n**Shapes don't match:**\n- Different model architectures may have different input/output specs\n- Sliding window inference handles variable output shapes\n- Check model output: should be probability maps (0-1)\n\n## Expected Results\n\n- **With pre-trained models**: 0.50-0.56 score (competitive)\n- **Ensemble helps**: Multiple models improve accuracy\n- **Runtime**: 10-30 minutes on GPU P100","metadata":{}}]}