{"metadata":{"kernelspec":{"display_name":"Python 3","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":49349,"databundleVersionId":5447706,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":20573.990788,"end_time":"2026-01-11T00:00:22.081506","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-01-10T18:17:28.090718","version":"2.6.0"},"colab":{"provenance":[],"gpuType":"T4"},"accelerator":"GPU"},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{"id":"JON4rYSEOzCg","outputId":"26f4a0c4-30e0-46e2-f9f9-c3365732d1ac"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Fountain: 2D Gaussian Splat w/biplet,colmap**\n### **biplet-colmap-2dgs-kg**","metadata":{"papermill":{"duration":0.002985,"end_time":"2026-01-10T18:17:32.170524","exception":false,"start_time":"2026-01-10T18:17:32.167539","status":"completed"},"tags":[],"id":"fb1f1fdc"}},{"cell_type":"markdown","source":"\n\n---\n\n## Overview: What is 3D Gaussian Splatting (3DGS)?\n\nTraditional methods like NeRF use a continuous neural network to represent a scene. In contrast, **3D Gaussian Splatting** is a point-based rendering technique. It represents a 3D scene using millions of overlapping, semi-transparent \"blobs\" (Gaussians).\n\nEach Gaussian is defined by:\n\n* **Position (Mean):** Where it is in space ().\n* **Covariance:** Its shape, size, and orientation (defined via scaling and rotation).\n* **Opacity ():** How transparent it is.\n* **Color (Spherical Harmonics):** How its color changes based on the viewing angle.\n\n**Why it’s a big deal:** It allows for **real-time rendering** (100+ FPS) and faster training times compared to NeRF because it uses \"splatting\"—rasterizing the Gaussians onto the 2D image plane using high-speed GPU kernels.\n\n---\n\n## Comparison of 3DGS Variants\n\nHere is the breakdown of the three specific variants you mentioned and how they differ from the original 3DGS.\n\n### 1. Scafford (Scaffold-GS)\n\n**Focus:** Efficiency and Sparse Data.\n\n* **The Difference:** While 3DGS places Gaussians somewhat randomly, Scaffold-GS uses a **sparse voxel grid (a \"scaffold\")** to guide the distribution.\n* **Key Features:**\n* **Anchor Points:** Gaussians are \"attached\" to anchor points within the grid.\n* **Neural Weighting:** It uses a small MLP to predict the properties of the Gaussians based on the local neighborhood.\n* **Storage:** It is significantly more storage-efficient than 3DGS because it doesn't need to save every attribute for every single Gaussian independently.\n\n\n* **Best For:** Large-scale scenes where memory usage is a concern.\n\n### 2. 2D Gaussian Splatting (2DGS)\n\n**Focus:** Surface Reconstruction and Consistency.\n\n* **The Difference:** Instead of 3D \"volumes,\" it uses **2D oriented disks (flat Gaussians)**.\n* **Key Features:**\n* **Thin Geometry:** By flattening the Gaussians, it forces the model to represent surfaces more accurately. Standard 3DGS often struggles with \"popping\" artifacts or fuzzy surfaces.\n* **Normal Mapping:** It provides well-defined surface normals, which makes it much easier to export the splat into a standard 3D mesh (like a .obj or .ply file).\n\n\n* **Best For:** When you need to turn your splat into a high-quality 3D mesh for gaming or CAD.\n\n### 3. Mip-Splatting\n\n**Focus:** Anti-aliasing and Multi-scale Rendering.\n\n* **The Difference:** 3DGS suffers from \"aliasing\" (jagged edges or flickering) when you zoom out or change resolutions. Mip-Splatting introduces **3D smoothing filters**.\n* **Key Features:**\n* **Frequency Constraint:** It limits the frequency of the Gaussians to match the pixel size of the image.\n* **Scale-Adaptive:** When you move the camera further away, the Gaussians are effectively \"blurred\" or filtered so they don't become smaller than a single pixel, preventing the \"salt-and-pepper\" noise seen in original 3DGS.\n\n\n* **Best For:** High-quality cinematics where the camera moves across different distances.\n\n---\n\n## Summary Table\n\n| Feature | 3DGS (Original) | Scaffold-GS | 2DGS | Mip-Splatting |\n| --- | --- | --- | --- | --- |\n| **Primitive** | 3D Ellipsoid | Anchored 3D Ellipsoid | 2D Thin Disk | Filtered 3D Ellipsoid |\n| **Main Advantage** | Speed & Simplicity | Memory Efficiency | Better Surfaces/Meshing | No Aliasing/Blurring |\n| **VRAM Usage** | High | **Low** | Medium | Medium |\n| **Visual Quality** | Great (except zoom) | Great | Sharp Surfaces | **Best** (consistent) |\n\n---\n\n","metadata":{}},{"cell_type":"code","source":"import os\nimport sys\nimport subprocess\nimport shutil\nfrom pathlib import Path\nimport cv2\nfrom PIL import Image\nimport glob\n\nIMAGE_PATH=\"/kaggle/input/competitions/image-matching-challenge-2023/train/haiper/fountain/images\"\nWORK_DIR = \"/kaggle/working/2d-gaussian-splatting\"\nOUTPUT_DIR = '/kaggle/working/output'\nCOLMAP_DIR = '/kaggle/working/colmap'","metadata":{"execution":{"iopub.execute_input":"2026-01-10T18:17:32.181455Z","iopub.status.busy":"2026-01-10T18:17:32.180969Z","iopub.status.idle":"2026-01-10T18:17:32.355942Z","shell.execute_reply":"2026-01-10T18:17:32.355229Z"},"papermill":{"duration":0.179454,"end_time":"2026-01-10T18:17:32.357275","exception":false,"start_time":"2026-01-10T18:17:32.177821","status":"completed"},"tags":[],"id":"22353010"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_cmd(cmd, check=True, capture=False, cwd=None): \n    \"\"\"Run command with better error handling\"\"\"\n    print(f\"Running: {' '.join(cmd)}\")\n    result = subprocess.run(\n        cmd,\n        capture_output=capture,\n        text=True,\n        check=False,\n        cwd=cwd \n    )\n    if check and result.returncode != 0:\n        print(f\"❌ Command failed with code {result.returncode}\")\n        if capture:\n            print(f\"STDOUT: {result.stdout}\")\n            print(f\"STDERR: {result.stderr}\")\n    return result\n\n\ndef setup_environment():\n    \"\"\"\n    Colab environment setup for Gaussian Splatting + LightGlue + pycolmap\n    Python 3.12 compatible version (v8)\n    \"\"\"\n\n    print(\"🚀 Setting up COLAB environment (v8 - Python 3.12 compatible)\")\n\n    WORK_DIR = \"2d-gaussian-splatting\"\n\n    # =====================================================================\n    # STEP 0: NumPy FIX (Python 3.12 compatible)\n    # =====================================================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"STEP 0: Fix NumPy (Python 3.12 compatible)\")\n    print(\"=\"*70)\n\n    # Python 3.12 requires numpy >= 1.26\n    run_cmd([sys.executable, \"-m\", \"pip\", \"uninstall\", \"-y\", \"numpy\"])\n    run_cmd([sys.executable, \"-m\", \"pip\", \"install\", \"numpy==1.26.4\"])\n\n    # sanity check\n    run_cmd([sys.executable, \"-c\", \"import numpy; print('NumPy:', numpy.__version__)\"])\n\n    # =====================================================================\n    # STEP 1: System packages (Colab)\n    # =====================================================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"STEP 1: System packages\")\n    print(\"=\"*70)\n\n    run_cmd([\"apt-get\", \"update\", \"-qq\"])\n    run_cmd([\n        \"apt-get\", \"install\", \"-y\", \"-qq\",\n        \"colmap\",\n        \"build-essential\",\n        \"cmake\",\n        \"git\",\n        \"libopenblas-dev\",\n        \"xvfb\"\n    ])\n\n    # virtual display (COLMAP / OpenCV safety)\n    os.environ[\"QT_QPA_PLATFORM\"] = \"offscreen\"\n    os.environ[\"DISPLAY\"] = \":99\"\n    subprocess.Popen(\n        [\"Xvfb\", \":99\", \"-screen\", \"0\", \"1024x768x24\"],\n        stdout=subprocess.DEVNULL,\n        stderr=subprocess.DEVNULL\n    )\n\n    # =====================================================================\n    # STEP 2: Clone 2D Gaussian Splatting\n    # =====================================================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"STEP 2: Clone Gaussian Splatting\")\n    print(\"=\"*70)\n\n    if not os.path.exists(WORK_DIR):\n        run_cmd([\n            \"git\", \"clone\", \"--recursive\",\n            \"https://github.com/hbb1/2d-gaussian-splatting.git\",\n            WORK_DIR\n        ])\n    else:\n        print(\"✓ Repository already exists\")\n\n    # =====================================================================\n    # STEP 3: Python packages (FIXED ORDER & VERSIONS)\n    # =====================================================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"STEP 3: Python packages (VERBOSE MODE)\")\n    print(\"=\"*70)\n\n    # ---- PyTorch (Colab CUDA対応) ----\n    print(\"\\n📦 Installing PyTorch...\")\n    run_cmd([\n        sys.executable, \"-m\", \"pip\", \"install\",\n        \"torch\", \"torchvision\", \"torchaudio\"\n    ])\n\n    # ---- Core utils ----\n    print(\"\\n📦 Installing core utilities...\")\n    run_cmd([\n        sys.executable, \"-m\", \"pip\", \"install\",\n        \"opencv-python\",\n        \"pillow\",\n        \"imageio\",\n        \"imageio-ffmpeg\",\n        \"plyfile\",\n        \"tqdm\",\n        \"tensorboard\"\n    ])\n\n    # ---- transformers (NumPy 1.26 compatible) ----\n    print(\"\\n📦 Installing transformers (NumPy 1.26 compatible)...\")\n    # Install transformers with proper dependencies\n    run_cmd([\n        sys.executable, \"-m\", \"pip\", \"install\",\n        \"transformers==4.40.0\"\n    ])\n\n    # ---- LightGlue stack (GITHUB INSTALL) ----\n    print(\"\\n📦 Installing LightGlue stack...\")\n\n    # Install kornia first\n    run_cmd([sys.executable, \"-m\", \"pip\", \"install\", \"kornia\"])\n\n    # Install h5py (sometimes needed)\n    run_cmd([sys.executable, \"-m\", \"pip\", \"install\", \"h5py\"])\n\n    # Install matplotlib (LightGlue dependency)\n    run_cmd([sys.executable, \"-m\", \"pip\", \"install\", \"matplotlib\"])\n\n    # Install pycolmap\n    run_cmd([sys.executable, \"-m\", \"pip\", \"install\", \"pycolmap\"])\n\n\n\n    # =====================================================================\n    # STEP 4: Detailed Verification\n    # =====================================================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"STEP 4: Detailed Verification\")\n    print(\"=\"*70)\n\n    # NumPy (verify version first)\n    print(\"\\n🔍 Testing NumPy...\")\n    try:\n        import numpy as np\n        print(f\"  ✓ NumPy: {np.__version__}\")\n    except Exception as e:\n        print(f\"  ❌ NumPy failed: {e}\")\n\n    # PyTorch\n    print(\"\\n🔍 Testing PyTorch...\")\n    try:\n        import torch\n        print(f\"  ✓ PyTorch: {torch.__version__}\")\n        print(f\"  ✓ CUDA available: {torch.cuda.is_available()}\")\n        if torch.cuda.is_available():\n            print(f\"  ✓ CUDA version: {torch.version.cuda}\")\n    except Exception as e:\n        print(f\"  ❌ PyTorch failed: {e}\")\n\n    # transformers\n    print(\"\\n🔍 Testing transformers...\")\n    try:\n        import transformers\n        print(f\"  ✓ transformers version: {transformers.__version__}\")\n        from transformers import AutoModel\n        print(f\"  ✓ AutoModel import: OK\")\n    except Exception as e:\n        print(f\"  ❌ transformers failed: {e}\")\n        print(f\"  Attempting detailed diagnosis...\")\n        result = run_cmd([\n            sys.executable, \"-c\",\n            \"import transformers; print(transformers.__version__)\"\n        ], capture=True)\n        print(f\"  Output: {result.stdout}\")\n        print(f\"  Error: {result.stderr}\")\n\n    # pycolmap\n    print(\"\\n🔍 Testing pycolmap...\")\n    try:\n        import pycolmap\n        print(f\"  ✓ pycolmap: OK\")\n    except Exception as e:\n        print(f\"  ❌ pycolmap failed: {e}\")\n\n    # kornia\n    print(\"\\n🔍 Testing kornia...\")\n    try:\n        import kornia\n        print(f\"  ✓ kornia: {kornia.__version__}\")\n    except Exception as e:\n        print(f\"  ❌ kornia failed: {e}\")\n\n    return WORK_DIR\n\n\nif __name__ == \"__main__\":\n    setup_environment()","metadata":{"execution":{"iopub.execute_input":"2026-01-10T18:17:32.363444Z","iopub.status.busy":"2026-01-10T18:17:32.363175Z","iopub.status.idle":"2026-01-10T18:22:43.720241Z","shell.execute_reply":"2026-01-10T18:22:43.719380Z"},"papermill":{"duration":311.361656,"end_time":"2026-01-10T18:22:43.721610","exception":false,"start_time":"2026-01-10T18:17:32.359954","status":"completed"},"tags":[],"id":"be6df249","outputId":"49ae4583-0a1e-4957-eba1-7e6ca8279e36","_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =====================================================================\n# STEP 4: Build 2D GS submodules (Reliable Method)\n# =====================================================================\nprint(\"\\n\" + \"=\"*70)\nprint(\"STEP 5: Build Gaussian Splatting submodules\")\nprint(\"=\"*70)\n\n# diff-surfel-rasterization\n\npath = os.path.join(WORK_DIR, \"submodules\", \"diff-surfel-rasterization\")\nurl = \"https://github.com/hbb1/diff-surfel-rasterization.git\"\nname = os.path.basename(path)\n\nprint(f\"\\n📦 Processing {name}...\")\n\nif not os.path.exists(path):\n    print(f\"  > Cloning {url}...\")\n    # Ensure the parent directory exists\n    os.makedirs(os.path.dirname(path), exist_ok=True)\n    run_cmd([\"git\", \"clone\", url, path])\nelse:\n    print(f\"  ✓ {name} already exists.\")\n\n# 2. setup.py install (Compilation)\nprint(f\"  > Compiling and Installing {name}...\")\nresult = run_cmd(\n    [sys.executable, \"setup.py\", \"install\"],\n    cwd=path,\n    check=False, # Do not stop on error\n    capture=True\n)\n\nif result.returncode != 0:\n    print(f\"❌ Failed to build {name}\")\n    print(\"--- STDERR ---\")\n    print(result.stderr)\nelse:\n    print(f\"✅ Successfully built {name}\")","metadata":{"id":"kLdJ-FeT-kQc","outputId":"fa637887-c7a9-45c0-fc4d-738456d119d9"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport sys\nimport shutil\nimport subprocess\n\n# --- Preparation: Environment Setup ---\nprint(\"Configuring build environment...\")\n# 1. Check CUDA compiler version\n!nvcc --version\n\n# 2. Install essential tools (Ninja improves build stability and speed)\n!pip install setuptools wheel ninja\n\n# 3. Setup environment variables (Explicitly specify CUDA paths)\nos.environ[\"CUDA_HOME\"] = \"/usr/local/cuda\"\nos.environ[\"PATH\"] = f'{os.environ[\"CUDA_HOME\"]}/bin:{os.environ[\"PATH\"]}'\nos.environ[\"LD_LIBRARY_PATH\"] = f'{os.environ[\"CUDA_HOME\"]}/lib64:{os.environ[\"LD_LIBRARY_PATH\"]}'\n# Limit parallel build jobs to prevent crashes due to out-of-memory (OOM) errors\nos.environ[\"MAX_JOBS\"] = \"2\"\n\ndef run_cmd(cmd, cwd=None, check=True):\n    \"\"\"Helper function to execute shell commands\"\"\"\n    return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, check=check)\n\ndef install_submodule(name, url, base_dir):\n    \"\"\"Install an individual submodule\"\"\"\n    print(f\"\\n{'='*70}\")\n    print(f\"Installing {name}\")\n    print(f\"{'='*70}\")\n\n    # Use absolute paths\n    path = os.path.abspath(os.path.join(base_dir, \"submodules\", name))\n    print(f\"  > Target path: {path}\")\n\n    # Step 1: Remove existing directory\n    if os.path.exists(path):\n        print(f\"  > Removing existing {name}...\")\n        shutil.rmtree(path)\n\n    # Step 2: Clone repository\n    print(f\"  > Cloning from {url}...\")\n    os.makedirs(os.path.dirname(path), exist_ok=True)\n    try:\n        run_cmd([\"git\", \"clone\", url, path])\n    except subprocess.CalledProcessError as e:\n        print(f\"❌ Failed to clone {name}\")\n        print(e.stderr)\n        return False\n\n    # Step 3: Verify files (Check for presence of spatial.cu, etc.)\n    print(f\"  > Verifying cloned files...\")\n    files = os.listdir(path)\n    print(f\"  > Files in {name}: {files[:10]}...\")\n\n    # Step 4: Initialize submodules for specific modules\n    if name == \"diff-surfel-rasterization\":\n        print(f\"  > Initializing GLM submodule...\")\n        run_cmd([\"git\", \"submodule\", \"update\", \"--init\", \"--recursive\"], cwd=path)\n\n    # Step 5: Clear build cache\n    build_dir = os.path.join(path, \"build\")\n    if os.path.exists(build_dir):\n        print(f\"  > Cleaning build cache...\")\n        shutil.rmtree(build_dir)\n\n    # Step 6: Installation\n    print(f\"  > Installing {name} (This may take a few minutes)...\")\n    # Pass current environment variables explicitly\n    current_env = os.environ.copy()\n\n    result = subprocess.run(\n        [sys.executable, \"-m\", \"pip\", \"install\", \"-e\", \".\", \"--no-build-isolation\", \"-v\"],\n        cwd=path,\n        env=current_env,\n        capture_output=True,\n        text=True\n    )\n\n    if result.returncode != 0:\n        print(f\"❌ Failed to install {name}\")\n        # C++/CUDA build errors often appear in stdout, so output both\n        print(\"\\n--- STDOUT (Build Logs) ---\")\n        stdout_lines = result.stdout.split('\\n')\n        print('\\n'.join(stdout_lines[-60:])) # Show the last 60 lines\n\n        print(\"\\n--- STDERR (Error Details) ---\")\n        print(result.stderr)\n        return False\n\n    print(f\"✅ Successfully installed {name}\")\n    return True\n\n# =====================================================================\n# STEP 4: Build 2D GS submodules\n# =====================================================================\nprint(\"\\n\" + \"=\"*70)\nprint(\"STEP 4: Build Gaussian Splatting submodules\")\nprint(\"=\"*70)\n\n# Use absolute path for Kaggle/Colab environments\nWORK_DIR = \"/kaggle/working/2d-gaussian-splatting\"\n\n# Install each submodule\n# simple-knn\nsuccess_knn = install_submodule(\n    \"simple-knn\",\n    \"https://github.com/tztechno/simple-knn.git\",\n    WORK_DIR\n)\n\n# Display Results\nprint(\"\\n\" + \"=\"*70)\nprint(\"Installation Summary\")\nprint(\"=\"*70)\nprint(f\"simple-knn: {'✅ Success' if success_knn else '❌ Failed'}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install trimesh","metadata":{"id":"-ZfMABILvydS","outputId":"17c09295-ca06-4677-b1c3-4faeae1a60fe"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def setup_2dgs_environment():\n    \"\"\"Set up the 2DGS environment (Complete Version)\"\"\"\n    print(\"Setting up 2DGS environment...\")\n\n    # Install all required packages\n    packages = [\n        'plyfile',\n        'mediapy',\n        'open3d',  # Added open3d\n    ]\n\n    for pkg in packages:\n        print(f\"Installing {pkg}...\")\n        subprocess.run(['pip', 'install', pkg], check=True)\n\n    # Clone the 2DGS repository\n    if not os.path.exists(WORK_DIR):\n        print(f\"Cloning 2DGS repository to {WORK_DIR}...\")\n        subprocess.run([\n            'git', 'clone', '--recursive',\n            'https://github.com/hbb1/2d-gaussian-splatting.git',\n            WORK_DIR\n        ], check=True)\n\n    # Initialize and update submodules\n    print(\"Updating submodules...\")\n    subprocess.run(['git', 'submodule', 'update', '--init', '--recursive'],\n                   cwd=WORK_DIR, check=True)\n\n    # Build internal 2DGS submodules\n    build_2dgs_submodules()\n\n    print(\"✅ 2DGS environment setup complete\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import subprocess\nresult = subprocess.run(\n    ['/usr/bin/python3', 'render.py',\n     '-m', '/kaggle/working/2d-gaussian-splatting/output/video',\n     '--iteration', '1000',\n     '--skip_test',\n     '--skip_train'],\n    cwd='/kaggle/working/2d-gaussian-splatting',\n    capture_output=True,\n    text=True\n)\n\nprint(\"=== STDOUT ===\")\nprint(result.stdout)\nprint(\"\\n=== STDERR ===\")\nprint(result.stderr)\nprint(f\"\\n=== EXIT CODE: {result.returncode} ===\")","metadata":{"id":"vRxNgRnypv0l","outputId":"5499a948-f78c-4ab4-9683-a428a36ef48a","_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport cv2\nimport numpy as np\nfrom PIL import Image\n\n# =========================================================\n# Utility: aspect ratio preserved + black padding\n# =========================================================\n\ndef normalize_image_sizes_biplet(input_dir, output_dir=None, size=1024, max_images=None):\n    \"\"\"\n    Generates two square crops (Left & Right or Top & Bottom)\n    from each image in a directory and returns the output directory\n    and the list of generated file paths.\n\n    Args:\n        input_dir: Input directory containing source images\n        output_dir: Output directory for processed images\n        size: Target square size (default: 1024)\n        max_images: Maximum number of SOURCE images to process (default: None = all images)\n    \"\"\"\n    if output_dir is None:\n        output_dir = 'output/images_biplet'\n    os.makedirs(output_dir, exist_ok=True)\n\n    print(f\"--- Step 1: Biplet-Square Normalization ---\")\n    print(f\"Generating 2 cropped squares (Left/Right or Top/Bottom) for each image...\")\n    print()\n\n    generated_paths = []\n    converted_count = 0\n    size_stats = {}\n\n    # Sort for consistent processing order\n    image_files = sorted([f for f in os.listdir(input_dir)\n                         if f.lower().endswith(('.jpg', '.jpeg', '.png'))])\n\n    if max_images is not None:\n        image_files = image_files[:max_images]\n        print(f\"Processing limited to {max_images} source images (will generate {max_images * 2} cropped images)\")\n\n    for img_file in image_files:\n        input_path = os.path.join(input_dir, img_file)\n        try:\n            img = Image.open(input_path)\n            original_size = img.size\n\n            # Tracking original aspect ratios\n            size_key = f\"{original_size[0]}x{original_size[1]}\"\n            size_stats[size_key] = size_stats.get(size_key, 0) + 1\n\n            # Generate 2 crops using the helper function\n            crops = generate_two_crops(img, size)\n            base_name, ext = os.path.splitext(img_file)\n\n            for mode, cropped_img in crops.items():\n                output_path = os.path.join(output_dir, f\"{base_name}_{mode}{ext}\")\n                cropped_img.save(output_path, quality=95)\n                generated_paths.append(output_path)\n\n            converted_count += 1\n            print(f\"  ✓ {img_file}: {original_size} → 2 square images generated\")\n\n        except Exception as e:\n            print(f\"  ✗ Error processing {img_file}: {e}\")\n\n    print(f\"\\nProcessing complete: {converted_count} source images processed\")\n    print(f\"Total output images: {len(generated_paths)}\")\n    print(f\"Original size distribution: {size_stats}\")\n\n    return output_dir, generated_paths\n\n\ndef generate_two_crops(img, size):\n    \"\"\"\n    Crops the image into a square and returns 2 variations\n    (Left/Right for landscape, Top/Bottom for portrait).\n    \"\"\"\n    width, height = img.size\n    crop_size = min(width, height)\n    crops = {}\n\n    if width > height:\n        # Landscape → Left & Right\n        positions = {\n            'left': 0,\n            'right': width - crop_size\n        }\n        for mode, x_offset in positions.items():\n            box = (x_offset, 0, x_offset + crop_size, crop_size)\n            crops[mode] = img.crop(box).resize(\n                (size, size),\n                Image.Resampling.LANCZOS\n            )\n\n    else:\n        # Portrait or Square → Top & Bottom\n        positions = {\n            'top': 0,\n            'bottom': height - crop_size\n        }\n        for mode, y_offset in positions.items():\n            box = (0, y_offset, crop_size, y_offset + crop_size)\n            crops[mode] = img.crop(box).resize(\n                (size, size),\n                Image.Resampling.LANCZOS\n            )\n\n    return crops\n","metadata":{"execution":{"iopub.execute_input":"2026-01-10T18:22:43.739411Z","iopub.status.busy":"2026-01-10T18:22:43.738855Z","iopub.status.idle":"2026-01-10T18:22:43.755664Z","shell.execute_reply":"2026-01-10T18:22:43.754865Z"},"papermill":{"duration":0.027297,"end_time":"2026-01-10T18:22:43.756758","exception":false,"start_time":"2026-01-10T18:22:43.729461","status":"completed"},"tags":[],"id":"b8690389"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_colmap_reconstruction(image_dir, colmap_dir):\n    \"\"\"Estimate camera poses and 3D point cloud with COLMAP\"\"\"\n    print(\"Running SfM reconstruction with COLMAP...\")\n\n    database_path = os.path.join(colmap_dir, \"database.db\")\n    sparse_dir = os.path.join(colmap_dir, \"sparse\")\n    os.makedirs(sparse_dir, exist_ok=True)\n\n    # Set environment variable\n    env = os.environ.copy()\n    env['QT_QPA_PLATFORM'] = 'offscreen'\n\n    # Feature extraction\n    print(\"1/4: Extracting features...\")\n    subprocess.run([\n        'colmap', 'feature_extractor',\n        '--database_path', database_path,\n        '--image_path', image_dir,\n        '--ImageReader.single_camera', '1',\n        '--ImageReader.camera_model', 'OPENCV',\n        '--SiftExtraction.use_gpu', '0'  # Use CPU\n    ], check=True, env=env)\n\n    # Feature matching\n    print(\"2/4: Matching features...\")\n    subprocess.run([\n        'colmap', 'exhaustive_matcher',  # Use sequential_matcher instead of exhaustive_matcher\n        '--database_path', database_path,\n        '--SiftMatching.use_gpu', '0'  # Use CPU\n    ], check=True, env=env)\n\n    # Sparse reconstruction\n    print(\"3/4: Sparse reconstruction...\")\n    subprocess.run([\n        'colmap', 'mapper',\n        '--database_path', database_path,\n        '--image_path', image_dir,\n        '--output_path', sparse_dir,\n        '--Mapper.ba_global_max_num_iterations', '20',  # Speed up\n        '--Mapper.ba_local_max_num_iterations', '10'\n    ], check=True, env=env)\n\n    # Export to text format\n    print(\"4/4: Exporting to text format...\")\n    model_dir = os.path.join(sparse_dir, '0')\n    if not os.path.exists(model_dir):\n        # Use the first model found\n        subdirs = [d for d in os.listdir(sparse_dir) if os.path.isdir(os.path.join(sparse_dir, d))]\n        if subdirs:\n            model_dir = os.path.join(sparse_dir, subdirs[0])\n        else:\n            raise FileNotFoundError(\"COLMAP reconstruction failed\")\n\n    subprocess.run([\n        'colmap', 'model_converter',\n        '--input_path', model_dir,\n        '--output_path', model_dir,\n        '--output_type', 'TXT'\n    ], check=True, env=env)\n\n    print(f\"COLMAP reconstruction complete: {model_dir}\")\n    return model_dir\n\n\ndef convert_cameras_to_pinhole(input_file, output_file):\n    \"\"\"Convert camera model to PINHOLE format\"\"\"\n    print(f\"Reading camera file: {input_file}\")\n\n    with open(input_file, 'r') as f:\n        lines = f.readlines()\n\n    converted_count = 0\n    with open(output_file, 'w') as f:\n        for line in lines:\n            if line.startswith('#') or line.strip() == '':\n                f.write(line)\n            else:\n                parts = line.strip().split()\n                if len(parts) >= 4:\n                    cam_id = parts[0]\n                    model = parts[1]\n                    width = parts[2]\n                    height = parts[3]\n                    params = parts[4:]\n\n                    # Convert to PINHOLE format\n                    if model == \"PINHOLE\":\n                        f.write(line)\n                    elif model == \"OPENCV\":\n                        # OPENCV: fx, fy, cx, cy, k1, k2, p1, p2\n                        fx = params[0]\n                        fy = params[1]\n                        cx = params[2]\n                        cy = params[3]\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {fx} {fy} {cx} {cy}\\n\")\n                        converted_count += 1\n                    else:\n                        # Convert other models too\n                        fx = fy = max(float(width), float(height))\n                        cx = float(width) / 2\n                        cy = float(height) / 2\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {fx} {fy} {cx} {cy}\\n\")\n                        converted_count += 1\n                else:\n                    f.write(line)\n\n    print(f\"Converted {converted_count} cameras to PINHOLE format\")\n\n\ndef prepare_gaussian_splatting_data(image_dir, colmap_model_dir):\n    \"\"\"Prepare data for Gaussian Splatting\"\"\"\n    print(\"Preparing data for Gaussian Splatting...\")\n\n    data_dir = f\"{WORK_DIR}/data/video\"\n    os.makedirs(f\"{data_dir}/sparse/0\", exist_ok=True)\n    os.makedirs(f\"{data_dir}/images\", exist_ok=True)\n\n    # Copy images\n    print(\"Copying images...\")\n    img_count = 0\n    for img_file in os.listdir(image_dir):\n        if img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            shutil.copy(\n                os.path.join(image_dir, img_file),\n                f\"{data_dir}/images/{img_file}\"\n            )\n            img_count += 1\n    print(f\"Copied {img_count} images\")\n\n    # Convert and copy camera file to PINHOLE format\n    print(\"Converting camera model to PINHOLE format...\")\n    convert_cameras_to_pinhole(\n        os.path.join(colmap_model_dir, 'cameras.txt'),\n        f\"{data_dir}/sparse/0/cameras.txt\"\n    )\n\n    # Copy other files\n    for filename in ['images.txt', 'points3D.txt']:\n        src = os.path.join(colmap_model_dir, filename)\n        dst = f\"{data_dir}/sparse/0/{filename}\"\n        if os.path.exists(src):\n            shutil.copy(src, dst)\n            print(f\"Copied {filename}\")\n        else:\n            print(f\"Warning: {filename} not found\")\n\n    print(f\"Data preparation complete: {data_dir}\")\n    return data_dir\n\n\ndef run_colmap_reconstruction(image_dir, colmap_dir):\n    \"\"\"Estimate camera poses and 3D point cloud with COLMAP\"\"\"\n    print(\"Running SfM reconstruction with COLMAP...\")\n\n    database_path = os.path.join(colmap_dir, \"database.db\")\n    sparse_dir = os.path.join(colmap_dir, \"sparse\")\n    os.makedirs(sparse_dir, exist_ok=True)\n\n    # Set environment variable\n    env = os.environ.copy()\n    env['QT_QPA_PLATFORM'] = 'offscreen'\n\n    # Feature extraction\n    print(\"1/4: Extracting features...\")\n    subprocess.run([\n        'colmap', 'feature_extractor',\n        '--database_path', database_path,\n        '--image_path', image_dir,\n        '--ImageReader.single_camera', '1',\n        '--ImageReader.camera_model', 'OPENCV',\n        '--SiftExtraction.use_gpu', '0'  # Use CPU\n    ], check=True, env=env)\n\n    # Feature matching\n    print(\"2/4: Matching features...\")\n    subprocess.run([\n        'colmap', 'exhaustive_matcher',  # Use sequential_matcher instead of exhaustive_matcher\n        '--database_path', database_path,\n        '--SiftMatching.use_gpu', '0'  # Use CPU\n    ], check=True, env=env)\n\n    # Sparse reconstruction\n    print(\"3/4: Sparse reconstruction...\")\n    subprocess.run([\n        'colmap', 'mapper',\n        '--database_path', database_path,\n        '--image_path', image_dir,\n        '--output_path', sparse_dir,\n        '--Mapper.ba_global_max_num_iterations', '20',  # Speed up\n        '--Mapper.ba_local_max_num_iterations', '10'\n    ], check=True, env=env)\n\n    # Export to text format\n    print(\"4/4: Exporting to text format...\")\n    model_dir = os.path.join(sparse_dir, '0')\n    if not os.path.exists(model_dir):\n        # Use the first model found\n        subdirs = [d for d in os.listdir(sparse_dir) if os.path.isdir(os.path.join(sparse_dir, d))]\n        if subdirs:\n            model_dir = os.path.join(sparse_dir, subdirs[0])\n        else:\n            raise FileNotFoundError(\"COLMAP reconstruction failed\")\n\n    subprocess.run([\n        'colmap', 'model_converter',\n        '--input_path', model_dir,\n        '--output_path', model_dir,\n        '--output_type', 'TXT'\n    ], check=True, env=env)\n\n    print(f\"COLMAP reconstruction complete: {model_dir}\")\n    return model_dir\n\n\ndef convert_cameras_to_pinhole(input_file, output_file):\n    \"\"\"Convert camera model to PINHOLE format\"\"\"\n    print(f\"Reading camera file: {input_file}\")\n\n    with open(input_file, 'r') as f:\n        lines = f.readlines()\n\n    converted_count = 0\n    with open(output_file, 'w') as f:\n        for line in lines:\n            if line.startswith('#') or line.strip() == '':\n                f.write(line)\n            else:\n                parts = line.strip().split()\n                if len(parts) >= 4:\n                    cam_id = parts[0]\n                    model = parts[1]\n                    width = parts[2]\n                    height = parts[3]\n                    params = parts[4:]\n\n                    # Convert to PINHOLE format\n                    if model == \"PINHOLE\":\n                        f.write(line)\n                    elif model == \"OPENCV\":\n                        # OPENCV: fx, fy, cx, cy, k1, k2, p1, p2\n                        fx = params[0]\n                        fy = params[1]\n                        cx = params[2]\n                        cy = params[3]\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {fx} {fy} {cx} {cy}\\n\")\n                        converted_count += 1\n                    else:\n                        # Convert other models too\n                        fx = fy = max(float(width), float(height))\n                        cx = float(width) / 2\n                        cy = float(height) / 2\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {fx} {fy} {cx} {cy}\\n\")\n                        converted_count += 1\n                else:\n                    f.write(line)\n\n    print(f\"Converted {converted_count} cameras to PINHOLE format\")\n\n\ndef prepare_gaussian_splatting_data(image_dir, colmap_model_dir):\n    \"\"\"Prepare data for Gaussian Splatting\"\"\"\n    print(\"Preparing data for Gaussian Splatting...\")\n\n    data_dir = f\"{WORK_DIR}/data/video\"\n    os.makedirs(f\"{data_dir}/sparse/0\", exist_ok=True)\n    os.makedirs(f\"{data_dir}/images\", exist_ok=True)\n\n    # Copy images\n    print(\"Copying images...\")\n    img_count = 0\n    for img_file in os.listdir(image_dir):\n        if img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            shutil.copy(\n                os.path.join(image_dir, img_file),\n                f\"{data_dir}/images/{img_file}\"\n            )\n            img_count += 1\n    print(f\"Copied {img_count} images\")\n\n    # Convert and copy camera file to PINHOLE format\n    print(\"Converting camera model to PINHOLE format...\")\n    convert_cameras_to_pinhole(\n        os.path.join(colmap_model_dir, 'cameras.txt'),\n        f\"{data_dir}/sparse/0/cameras.txt\"\n    )\n\n    # Copy other files\n    for filename in ['images.txt', 'points3D.txt']:\n        src = os.path.join(colmap_model_dir, filename)\n        dst = f\"{data_dir}/sparse/0/{filename}\"\n        if os.path.exists(src):\n            shutil.copy(src, dst)\n            print(f\"Copied {filename}\")\n        else:\n            print(f\"Warning: {filename} not found\")\n\n    print(f\"Data preparation complete: {data_dir}\")\n    return data_dir\n\n\n\ndef train_gaussian_splatting(data_dir, iterations=7000,\n                            lambda_normal=0.05,\n                            lambda_dist=0,  \n                            depth_ratio=0):\n    \"\"\"\n    Training function for 2DGS\n    Args:\n        lambda_normal: Weight for normal consistency (Default: 0.05)\n        lambda_dist: Weight for depth distortion (Default: 0)  # ← Fixed name\n        depth_ratio: 0=mean depth, 1=median depth (Default: 0)\n    \"\"\"\n    model_path = f\"{WORK_DIR}/output/video\"\n    cmd = [\n        sys.executable, 'train.py',\n        '-s', data_dir,\n        '-m', model_path,\n        '--iterations', str(iterations),\n        '--lambda_normal', str(lambda_normal),\n        '--lambda_dist', str(lambda_dist),  # ← Fixed this!\n        '--depth_ratio', str(depth_ratio),\n        '--eval'\n    ]\n    \n    print(f\"Starting training for {iterations} iterations...\")\n    subprocess.run(cmd, cwd=WORK_DIR, check=True)\n    return model_path","metadata":{"execution":{"iopub.execute_input":"2026-01-10T18:22:43.772525Z","iopub.status.busy":"2026-01-10T18:22:43.772303Z","iopub.status.idle":"2026-01-10T18:22:43.790574Z","shell.execute_reply":"2026-01-10T18:22:43.789515Z"},"papermill":{"duration":0.027612,"end_time":"2026-01-10T18:22:43.791681","exception":false,"start_time":"2026-01-10T18:22:43.764069","status":"completed"},"tags":[],"id":"7acc20b6"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main_pipeline(image_dir, output_dir, square_size=1024, max_images=100):\n    \"\"\"Main execution function\"\"\"\n    try:\n        # Step 1: Image normalization and preprocessing\n        print(\"=\"*60)\n        print(\"Step 1: Normalizing and preprocessing images\")\n        print(\"=\"*60)\n\n        frame_dir = os.path.join(COLMAP_DIR, \"images\")\n        os.makedirs(frame_dir, exist_ok=True)\n\n        # Normalize images and save them directly to the COLMAP directory\n        num_processed = normalize_image_sizes_biplet(\n            input_dir=image_dir,\n            output_dir=frame_dir,  # Saving directly to colmap/images\n            size=square_size,\n            max_images=max_images\n        )\n\n        print(f\"Processed {num_processed} images\")\n\n        # Step 2: Estimate Camera Info with COLMAP\n        print(\"=\"*60)\n        print(\"Step 2: Running COLMAP reconstruction\")\n        print(\"=\"*60)\n        colmap_model_dir = run_colmap_reconstruction(frame_dir, COLMAP_DIR)\n\n        # Step 3: Prepare Data for Gaussian Splatting\n        print(\"=\"*60)\n        print(\"Step 3: Preparing Gaussian Splatting data\")\n        print(\"=\"*60)\n        data_dir = prepare_gaussian_splatting_data(frame_dir, colmap_model_dir)\n\n        # Step 4: Train Model\n        print(\"=\"*60)\n        print(\"Step 4: Training Gaussian Splatting model\")\n        print(\"=\"*60)\n\n        model_path = train_gaussian_splatting(\n            data_dir,\n            iterations=3000,\n            lambda_normal=0.05,\n            lambda_dist=0,      \n            depth_ratio=0\n        )\n\n        print(f\"Model trained at: {model_path}\")\n\n    except Exception as e:  \n        print(f\"❌ Pipeline failed: {e}\")\n        import traceback\n        traceback.print_exc()\n        return None\n\n\nif __name__ == \"__main__\":\n    IMAGE_DIR = \"/kaggle/input/competitions/image-matching-challenge-2023/train/haiper/fountain/images\"\n    OUTPUT_DIR = \"/kaggle/working/output\"\n    COLMAP_DIR = \"/kaggle/working/colmap\"\n\n    ply_path = main_pipeline(\n        image_dir=IMAGE_DIR,\n        output_dir=OUTPUT_DIR,\n        square_size=1024,\n        max_images=20\n    )\n","metadata":{"id":"fya3kv62NXM-","outputId":"e0aa12ee-eff2-4509-af22-509e905c8db3"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"id":"9GN6Eny2XsAd"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"id":"6RDKHigGWpaB"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"id":"JGbsfe-4WpNj"},"outputs":[],"execution_count":null}]}