{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":49349,"databundleVersionId":5447706,"isSourceIdPinned":false,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Photogrammetry from images of IMC2023","metadata":{}},{"cell_type":"markdown","source":"### 🔹 Common Use Cases in 3D Mapping\n\n1. **Point Clouds**: Output from LiDAR scans or stereo vision.\n2. **Meshes**: Triangulated surfaces from depth sensors or SLAM.\n3. **Photogrammetry**: 3D models created from multiple 2D images.\n","metadata":{}},{"cell_type":"code","source":"!pip install open3d --q\n!pip install plotly --q\n!pip install pyntcloud --q\n!apt-get install colmap\n!apt-get install -y cuda","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-06-12T10:11:43.623383Z","iopub.execute_input":"2025-06-12T10:11:43.623687Z","iopub.status.idle":"2025-06-12T10:12:43.037741Z","shell.execute_reply.started":"2025-06-12T10:11:43.62366Z","shell.execute_reply":"2025-06-12T10:12:43.036518Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import open3d as o3d\nimport numpy as np\nimport pandas as pd\nimport os\nimport sys\nimport re\nfrom glob import glob\nimport time\nimport random\nimport seaborn as sns\nimport xml.etree.ElementTree as ET\nfrom mpl_toolkits.mplot3d import Axes3D\nfrom pyntcloud import PyntCloud\nimport plotly.graph_objs as go\nfrom matplotlib.pyplot import cm\nimport matplotlib.pyplot as plt \nfrom plotly.offline import iplot, init_notebook_mode\nimport subprocess\nfrom PIL import Image\n\ninit_notebook_mode(connected=True)\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2025-06-12T10:12:43.038912Z","iopub.execute_input":"2025-06-12T10:12:43.039183Z","iopub.status.idle":"2025-06-12T10:12:48.261246Z","shell.execute_reply.started":"2025-06-12T10:12:43.039155Z","shell.execute_reply":"2025-06-12T10:12:48.26036Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_dir0 = \"/kaggle/input/image-matching-challenge-2023/train/urban/kyiv-puppet-theater/images_full_set\"\nimage_dir = \"images\"\nn_images = 100\nk = 1\n\nos.makedirs(image_dir, exist_ok=True)\n\nvalid_extensions = {'.png', '.jpg', '.jpeg', '.bmp', '.gif'}\nimage_files = [f for f in os.listdir(image_dir0) if os.path.splitext(f)[1].lower() in valid_extensions]\n\nselected_images = random.sample(image_files, min(n_images, len(image_files)))\n\nfor filename in selected_images:\n    image_path = os.path.join(image_dir0, filename)\n    img = Image.open(image_path)\n    w, h = img.size\n    print(w//k, h//k)\n    resized_img = img.resize((w // k, h // k))\n    output_path = os.path.join(image_dir, filename)\n    resized_img.save(output_path)\n\nprint(f\"{len(selected_images)} images resized and saved to '{image_dir}'\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_cols = 4\nn_rows = (n_images + n_cols - 1) // n_cols ##len(image_files)\n\nplt.figure(figsize=(15, 5 * n_rows))\n\nfor i, img_file in enumerate(selected_images):\n    img_path = os.path.join(image_dir, img_file)\n    img = Image.open(img_path)\n\n    plt.subplot(n_rows, n_cols, i + 1)\n    plt.imshow(img)\n    plt.title(img_file)\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create ply file from multiple images","metadata":{}},{"cell_type":"markdown","source":"'LIBGL_ALWAYS_SOFTWARE': '1',\n'GALLIUM_DRIVER': 'llvmpipe'","metadata":{}},{"cell_type":"code","source":"import os\nimport subprocess\nimport sys\n\ndef setup_headless_environment():\n    \"\"\"Set environment variables for running COLMAP in a headless environment\"\"\"\n    env_vars = {\n        'QT_QPA_PLATFORM': 'offscreen',\n        'DISPLAY': '',\n        'QT_QPA_FONTDIR': '/usr/share/fonts',\n        'MESA_GL_VERSION_OVERRIDE': '3.3',\n        'MESA_GLSL_VERSION_OVERRIDE': '330',\n\n    }\n    \n    for key, value in env_vars.items():\n        os.environ[key] = value\n    \n    print(\"🔧 Headless environment setup complete\")\n    return env_vars\n\ndef install_dependencies():\n    \"\"\"Install necessary dependencies\"\"\"\n    print(\"📦 Installing dependencies...\")\n    \n    try:\n        subprocess.run([\"apt-get\", \"update\", \"-qq\"], check=True, capture_output=True)\n        subprocess.run([\n            \"apt-get\", \"install\", \"-y\", \"-qq\",\n            \"mesa-utils\",\n            \"libgl1-mesa-glx\",\n            \"libegl1-mesa\",\n            \"libxrandr2\",\n            \"libxss1\",\n            \"libxcursor1\",\n            \"libxcomposite1\",\n            \"libasound2\",\n            \"libxi6\",\n            \"libxtst6\"\n        ], check=True, capture_output=True)\n        \n        print(\"✅ Dependencies installed successfully\")\n        return True\n    except subprocess.CalledProcessError as e:\n        print(f\"❌ Dependency installation failed: {e}\")\n        return False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-12T10:12:48.484961Z","iopub.status.idle":"2025-06-12T10:12:48.485369Z","shell.execute_reply.started":"2025-06-12T10:12:48.485161Z","shell.execute_reply":"2025-06-12T10:12:48.48518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import subprocess\nimport os\nimport sys\n\ndef diagnose_cuda_environment():\n    \"\"\"Comprehensive CUDA environment diagnosis for P100.\"\"\"\n    print(\"🔍 Diagnosing CUDA environment for P100...\")\n    \n    # Check NVIDIA driver\n    try:\n        result = subprocess.run([\"nvidia-smi\"], capture_output=True, text=True)\n        if result.returncode == 0:\n            print(\"✅ NVIDIA driver detected:\")\n            print(result.stdout)\n        else:\n            print(\"❌ nvidia-smi failed\")\n            return False\n    except FileNotFoundError:\n        print(\"❌ nvidia-smi not found - NVIDIA drivers not installed\")\n        return False\n    \n    # Check CUDA toolkit\n    try:\n        result = subprocess.run([\"nvcc\", \"--version\"], capture_output=True, text=True)\n        if result.returncode == 0:\n            print(\"✅ CUDA toolkit detected:\")\n            print(result.stdout)\n        else:\n            print(\"❌ nvcc not found\")\n    except FileNotFoundError:\n        print(\"⚠️ nvcc not found - CUDA toolkit may not be installed\")\n    \n    # Check CUDA runtime\n    cuda_paths = [\n        \"/usr/local/cuda/lib64/libcudart.so\",\n        \"/usr/lib/x86_64-linux-gnu/libcudart.so\",\n        \"/opt/cuda/lib64/libcudart.so\"\n    ]\n    \n    cuda_found = False\n    for path in cuda_paths:\n        if os.path.exists(path):\n            print(f\"✅ CUDA runtime found: {path}\")\n            cuda_found = True\n            break\n    \n    if not cuda_found:\n        print(\"❌ CUDA runtime library not found\")\n    \n    # Check environment variables\n    print(\"\\n🔧 CUDA Environment Variables:\")\n    cuda_vars = [\"CUDA_HOME\", \"CUDA_ROOT\", \"LD_LIBRARY_PATH\", \"PATH\"]\n    for var in cuda_vars:\n        value = os.environ.get(var, \"Not set\")\n        print(f\"{var}: {value}\")\n    \n    return True\n\ndef setup_cuda_environment():\n    \"\"\"Setup CUDA environment for COLMAP.\"\"\"\n    print(\"\\n🔧 Setting up CUDA environment...\")\n    \n    # Common CUDA paths\n    cuda_paths = [\n        \"/usr/local/cuda\",\n        \"/opt/cuda\",\n        \"/usr/local/cuda-11.8\",\n        \"/usr/local/cuda-12.0\"\n    ]\n    \n    cuda_path = None\n    for path in cuda_paths:\n        if os.path.exists(path):\n            cuda_path = path\n            break\n    \n    if cuda_path:\n        print(f\"✅ Found CUDA installation: {cuda_path}\")\n        \n        # Set environment variables\n        os.environ[\"CUDA_HOME\"] = cuda_path\n        os.environ[\"CUDA_ROOT\"] = cuda_path\n        \n        # Update PATH\n        cuda_bin = os.path.join(cuda_path, \"bin\")\n        if cuda_bin not in os.environ.get(\"PATH\", \"\"):\n            os.environ[\"PATH\"] = f\"{cuda_bin}:{os.environ.get('PATH', '')}\"\n        \n        # Update LD_LIBRARY_PATH\n        cuda_lib = os.path.join(cuda_path, \"lib64\")\n        current_ld_path = os.environ.get(\"LD_LIBRARY_PATH\", \"\")\n        if cuda_lib not in current_ld_path:\n            os.environ[\"LD_LIBRARY_PATH\"] = f\"{cuda_lib}:{current_ld_path}\"\n        \n        return True\n    else:\n        print(\"❌ CUDA installation not found\")\n        return False\n\ndef install_cuda_dependencies():\n    \"\"\"Install CUDA dependencies if missing.\"\"\"\n    print(\"\\n📦 Installing CUDA dependencies...\")\n    \n    commands = [\n        [\"apt-get\", \"update\"],\n        [\"apt-get\", \"install\", \"-y\", \"nvidia-cuda-toolkit\"],\n        [\"apt-get\", \"install\", \"-y\", \"libcuda1\"],\n        [\"apt-get\", \"install\", \"-y\", \"nvidia-cuda-dev\"]\n    ]\n    \n    for cmd in commands:\n        try:\n            result = subprocess.run(cmd, capture_output=True, text=True)\n            if result.returncode != 0:\n                print(f\"⚠️ Command failed: {' '.join(cmd)}\")\n                print(result.stderr)\n        except Exception as e:\n            print(f\"⚠️ Error running {' '.join(cmd)}: {e}\")\n\ndef check_colmap_cuda_support():\n    \"\"\"Check if COLMAP was compiled with CUDA support.\"\"\"\n    print(\"\\n🔍 Checking COLMAP CUDA support...\")\n    \n    try:\n        # Check COLMAP version and features\n        result = subprocess.run([\"colmap\", \"-h\"], capture_output=True, text=True)\n        if result.returncode == 0:\n            if \"CUDA\" in result.stdout or \"GPU\" in result.stdout:\n                print(\"✅ COLMAP appears to have CUDA support\")\n                return True\n            else:\n                print(\"❌ COLMAP may not be compiled with CUDA support\")\n                return False\n    except Exception as e:\n        print(f\"❌ Error checking COLMAP: {e}\")\n        return False\n\ndef run_dense_reconstruction_with_cuda_fix(dense_dir, sparse_model_path, image_dir, output_ply, timeout=3600):\n    \"\"\"Run dense reconstruction with CUDA environment fixes.\"\"\"\n    print(\"\\n💠 Running dense reconstruction with CUDA fixes...\")\n    \n    # Diagnose and setup CUDA\n    if not diagnose_cuda_environment():\n        print(\"❌ CUDA environment issues detected\")\n        return False\n    \n    if not setup_cuda_environment():\n        print(\"❌ Failed to setup CUDA environment\")\n        return False\n    \n    if not check_colmap_cuda_support():\n        print(\"⚠️ COLMAP CUDA support uncertain\")\n        # Continue anyway, but with additional options\n    \n    # Step 1: Image Undistortion\n    if not run_colmap_command([\n        \"colmap\", \"image_undistorter\",\n        \"--image_path\", image_dir,\n        \"--input_path\", sparse_model_path,\n        \"--output_path\", dense_dir,\n        \"--output_type\", \"COLMAP\",\n        \"--max_image_size\", \"512\"\n    ], \"Image undistortion\"):\n        return False\n    \n    # Step 2: Patch Match Stereo with CUDA fixes\n    print(\"\\n🚀 Running Patch Match Stereo with CUDA...\")\n    \n    # Try with explicit GPU device specification\n    stereo_commands = [\n        # Standard command\n        [\n            \"colmap\", \"patch_match_stereo\",\n            \"--workspace_path\", dense_dir,\n            \"--workspace_format\", \"COLMAP\",\n            \"--PatchMatchStereo.geom_consistency\", \"1\",\n            \"--PatchMatchStereo.max_image_size\", \"512\",\n            \"--PatchMatchStereo.window_radius\", \"5\",\n            \"--PatchMatchStereo.gpu_index\", \"0\"\n        ],\n        # With explicit CUDA device\n        [\n            \"colmap\", \"patch_match_stereo\",\n            \"--workspace_path\", dense_dir,\n            \"--workspace_format\", \"COLMAP\",\n            \"--PatchMatchStereo.geom_consistency\", \"1\",\n            \"--PatchMatchStereo.max_image_size\", \"512\",\n            \"--PatchMatchStereo.window_radius\", \"5\",\n            \"--PatchMatchStereo.gpu_index\", \"0\",\n            \"--PatchMatchStereo.cache_size\", \"32\"\n        ],\n        # Reduced parameters for P100 compatibility\n        [\n            \"colmap\", \"patch_match_stereo\",\n            \"--workspace_path\", dense_dir,\n            \"--workspace_format\", \"COLMAP\",\n            \"--PatchMatchStereo.max_image_size\", \"512\",\n            \"--PatchMatchStereo.window_radius\", \"3\",\n            \"--PatchMatchStereo.gpu_index\", \"0\"\n        ]\n    ]\n    \n    # Set CUDA device\n    os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0\"\n    \n    success = False\n    for i, cmd in enumerate(stereo_commands):\n        print(f\"🔄 Attempting stereo command variant {i+1}...\")\n        if run_colmap_command(cmd, f\"Patch Match Stereo (attempt {i+1})\", timeout=timeout):\n            success = True\n            break\n        else:\n            print(f\"❌ Attempt {i+1} failed\")\n    \n    if not success:\n        print(\"❌ All Patch Match Stereo attempts failed\")\n        return False\n    \n    # Step 3: Stereo Fusion\n    if not run_colmap_command([\n        \"colmap\", \"stereo_fusion\",\n        \"--workspace_path\", dense_dir,\n        \"--workspace_format\", \"COLMAP\",\n        \"--output_path\", output_ply,\n        \"--StereoFusion.max_image_size\", \"512\"\n    ], \"Stereo Fusion\"):\n        return False\n    \n    return True","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_colmap_command(command, description, timeout=3600):\n    \"\"\"Enhanced run_colmap_command with better error handling.\"\"\"\n    print(f\"\\n🔄 {description}...\")\n    print(f\"Command: {' '.join(command)}\")\n    \n    try:\n        # Set environment for CUDA\n        env = os.environ.copy()\n        env[\"CUDA_VISIBLE_DEVICES\"] = \"0\"\n        \n        result = subprocess.run(\n            command,\n            capture_output=True,\n            text=True,\n            timeout=timeout,\n            env=env\n        )\n        \n        if result.returncode == 0:\n            print(f\"✅ {description} completed successfully\")\n            if result.stdout:\n                print(\"Output:\", result.stdout[:500])  # First 500 chars\n            return True\n        else:\n            print(f\"❌ {description} failed (return code: {result.returncode})\")\n            if result.stderr:\n                print(\"Error:\", result.stderr)\n            if result.stdout:\n                print(\"Output:\", result.stdout)\n            return False\n            \n    except subprocess.TimeoutExpired:\n        print(f\"⏰ {description} timed out after {timeout} seconds\")\n        return False\n    except Exception as e:\n        print(f\"❌ {description} failed with exception: {e}\")\n        return False","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def check_directory_structure(workspace_dir, image_dir):\n    \"\"\"Check the directory structure\"\"\"\n    print(\"\\n📁 Checking directory structure:\")\n    \n    if not os.path.exists(image_dir):\n        print(f\"❌ Image directory not found: {image_dir}\")\n        return False\n    \n    images = [f for f in os.listdir(image_dir) \n              if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.tiff'))]\n    \n    if len(images) < 2:\n        print(f\"❌ Not enough images ({len(images)} found). At least 2 are required.\")\n        return False\n    \n    print(f\"✅ Found {len(images)} images\")\n    \n    print(\"📋 Sample images:\")\n    for i, img in enumerate(images[:5]):\n        img_path = os.path.join(image_dir, img)\n        size = os.path.getsize(img_path)\n        print(f\"  {i+1}. {img} ({size/1024:.1f} KB)\")\n    \n    os.makedirs(workspace_dir, exist_ok=True)\n    print(f\"✅ Workspace directory: {workspace_dir}\")\n    \n    return True\n\ndef check_sparse_reconstruction(sparse_dir):\n    \"\"\"Check the results of sparse reconstruction\"\"\"\n    print(\"\\n🔍 Checking sparse reconstruction results:\")\n    \n    model_dir = os.path.join(sparse_dir, \"0\")\n    required_files = [\"cameras.bin\", \"images.bin\", \"points3D.bin\"]\n    \n    if not os.path.exists(model_dir):\n        print(f\"❌ Model directory not found: {model_dir}\")\n        print(\"📋 Available directories:\")\n        if os.path.exists(sparse_dir):\n            for item in os.listdir(sparse_dir):\n                print(f\"  - {item}\")\n        return False\n    \n    missing_files = []\n    for file in required_files:\n        file_path = os.path.join(model_dir, file)\n        if not os.path.exists(file_path):\n            missing_files.append(file)\n        else:\n            size = os.path.getsize(file_path)\n            print(f\"✅ {file}: {size} bytes\")\n    \n    if missing_files:\n        print(f\"❌ Missing files: {missing_files}\")\n        return False\n    \n    return True\n\ndef run_feature_extraction_alternative(database_path, image_dir):\n    \"\"\"Try alternative feature extraction methods\"\"\"\n    print(\"\\n🔄 Trying alternative feature extraction...\")\n    \n    cpu_cmd = [\n        \"colmap\", \"feature_extractor\",\n        \"--database_path\", database_path,\n        \"--image_path\", image_dir,\n        \"--ImageReader.single_camera\", \"1\",\n        \"--SiftExtraction.use_gpu\", \"0\",\n        \"--SiftExtraction.max_image_size\", \"512\",\n        \"--SiftExtraction.max_num_features\", \"8192\"\n    ]\n    \n    if run_colmap_command(cpu_cmd, \"CPU feature extraction\"):\n        return True\n    \n    conservative_cmd = [\n        \"colmap\", \"feature_extractor\",\n        \"--database_path\", database_path,\n        \"--image_path\", image_dir,\n        \"--ImageReader.single_camera\", \"1\",\n        \"--SiftExtraction.use_gpu\", \"0\",\n        \"--SiftExtraction.max_image_size\", \"512\",\n        \"--SiftExtraction.max_num_features\", \"4096\",\n        \"--SiftExtraction.first_octave\", \"0\",\n        \"--SiftExtraction.num_octaves\", \"4\"\n    ]\n    \n    if run_colmap_command(conservative_cmd, \"Conservative feature extraction\"):\n        return True\n    \n    return False","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_dense_reconstruction(dense_dir, sparse_model_path, image_dir, output_ply, timeout=3600):\n    \"\"\"Run dense reconstruction steps with GPU support (CUDA required).\"\"\"\n    print(\"\\n💠 Running dense reconstruction (GPU-accelerated)...\")\n\n    # Step 1: Image Undistortion (can run in software mode)\n    if not run_colmap_command([\n        \"colmap\", \"image_undistorter\",\n        \"--image_path\", image_dir,\n        \"--input_path\", sparse_model_path,\n        \"--output_path\", dense_dir,\n        \"--output_type\", \"COLMAP\",\n        \"--max_image_size\", \"512\"\n    ], \"Image undistortion\"):\n        return False\n\n    # Step 2: Patch Match Stereo (requires CUDA)\n    print(\"\\n⚠️ Note: Patch Match Stereo requires CUDA. Disabling software rendering...\")\n    if not run_colmap_command([\n        \"colmap\", \"patch_match_stereo\",\n        \"--workspace_path\", dense_dir,\n        \"--workspace_format\", \"COLMAP\",\n        \"--PatchMatchStereo.geom_consistency\", \"1\",\n        \"--PatchMatchStereo.max_image_size\", \"512\",\n        \"--PatchMatchStereo.window_radius\", \"5\"\n    ], \"Patch Match Stereo (GPU)\", timeout=timeout, force_software_rendering=False):  # GPU mode\n        return False\n\n    # Step 3: Stereo Fusion (requires CUDA)\n    if not run_colmap_command([\n        \"colmap\", \"stereo_fusion\",\n        \"--workspace_path\", dense_dir,\n        \"--workspace_format\", \"COLMAP\",\n        \"--output_path\", output_ply,\n        \"--StereoFusion.max_image_size\", \"512\"\n    ], \"Stereo Fusion (GPU)\", force_software_rendering=False):  # GPU mode\n        return False\n\n    return True","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import subprocess\nimport os\nimport sys\nimport time\n\ndef run_colmap_command(command, description, timeout=3600):\n    \"\"\"Run COLMAP command with proper error handling.\"\"\"\n    print(f\"\\n🔄 {description}...\")\n    print(f\"Command: {' '.join(command)}\")\n    \n    try:\n        result = subprocess.run(\n            command,\n            capture_output=True,\n            text=True,\n            timeout=timeout\n        )\n        \n        if result.returncode == 0:\n            print(f\"✅ {description} completed successfully\")\n            if result.stdout:\n                # Show last few lines of output\n                lines = result.stdout.strip().split('\\n')\n                if len(lines) > 3:\n                    print(\"Output (last 3 lines):\")\n                    for line in lines[-3:]:\n                        print(f\"  {line}\")\n                else:\n                    print(\"Output:\", result.stdout)\n            return True\n        else:\n            print(f\"❌ {description} failed (return code: {result.returncode})\")\n            if result.stderr:\n                print(\"Error:\", result.stderr)\n            if result.stdout:\n                print(\"Output:\", result.stdout)\n            return False\n            \n    except subprocess.TimeoutExpired:\n        print(f\"⏰ {description} timed out after {timeout} seconds\")\n        return False\n    except Exception as e:\n        print(f\"❌ {description} failed with exception: {e}\")\n        return False\n\ndef check_directory_structure(workspace_dir, image_dir):\n    \"\"\"Check if directories exist and contain necessary files.\"\"\"\n    print(f\"\\n🔍 Checking directory structure...\")\n    \n    if not os.path.exists(image_dir):\n        print(f\"❌ Image directory not found: {image_dir}\")\n        return False\n    \n    image_files = [f for f in os.listdir(image_dir) \n                   if f.lower().endswith(('.jpg', '.jpeg', '.png', '.tiff', '.bmp'))]\n    \n    if len(image_files) < 2:\n        print(f\"❌ Need at least 2 images, found {len(image_files)}\")\n        return False\n    \n    print(f\"✅ Found {len(image_files)} images in {image_dir}\")\n    \n    os.makedirs(workspace_dir, exist_ok=True)\n    print(f\"✅ Workspace directory ready: {workspace_dir}\")\n    \n    return True\n\ndef run_cpu_feature_extraction(database_path, image_dir):\n    \"\"\"Run feature extraction with multiple fallback options.\"\"\"\n    print(\"\\n🔍 Running CPU-based feature extraction...\")\n    \n    # Try different feature extraction configurations\n    extraction_configs = [\n        # Standard SIFT with reduced parameters\n        {\n            \"cmd\": [\n                \"colmap\", \"feature_extractor\",\n                \"--database_path\", database_path,\n                \"--image_path\", image_dir,\n                \"--ImageReader.single_camera\", \"1\",\n                \"--SiftExtraction.use_gpu\", \"0\",\n                \"--SiftExtraction.max_image_size\", \"512\",\n                \"--SiftExtraction.max_num_features\", \"8192\"\n            ],\n            \"desc\": \"Standard SIFT (CPU)\"\n        },\n        # Reduced image size for faster processing\n        {\n            \"cmd\": [\n                \"colmap\", \"feature_extractor\",\n                \"--database_path\", database_path,\n                \"--image_path\", image_dir,\n                \"--ImageReader.single_camera\", \"1\",\n                \"--SiftExtraction.use_gpu\", \"0\",\n                \"--SiftExtraction.max_image_size\", \"512\",\n                \"--SiftExtraction.max_num_features\", \"4096\"\n            ],\n            \"desc\": \"Reduced SIFT (CPU)\"\n        },\n        # Minimal configuration\n        {\n            \"cmd\": [\n                \"colmap\", \"feature_extractor\",\n                \"--database_path\", database_path,\n                \"--image_path\", image_dir,\n                \"--SiftExtraction.use_gpu\", \"0\"\n            ],\n            \"desc\": \"Minimal SIFT (CPU)\"\n        }\n    ]\n    \n    for config in extraction_configs:\n        if run_colmap_command(config[\"cmd\"], config[\"desc\"], timeout=3600):\n            return True\n        print(f\"⚠️ {config['desc']} failed, trying next configuration...\")\n    \n    return False\n\ndef run_cpu_only_pipeline():\n    \"\"\"Complete CPU-only COLMAP pipeline.\"\"\"\n    print(\"🚀 COLMAP 3D Reconstruction Pipeline (CPU-Only)\")\n    \n    # Setup directories\n    workspace_dir = \"project/workspace\"\n    image_dir = \"/kaggle/input/image-matching-challenge-2023/train/urban/kyiv-puppet-theater/images_full_set\"\n    database_path = os.path.join(workspace_dir, \"database.db\")\n    sparse_dir = os.path.join(workspace_dir, \"sparse\")\n    output_ply = os.path.join(workspace_dir, \"sparse_reconstruction.ply\")\n    output_txt = os.path.join(workspace_dir, \"cameras.txt\")\n    \n    # Check prerequisites\n    if not check_directory_structure(workspace_dir, image_dir):\n        print(\"❌ Precondition check failed\")\n        return False\n    \n    print(f\"\\n🚀 Starting CPU-only COLMAP pipeline\")\n    print(f\"Image directory: {image_dir}\")\n    print(f\"Workspace: {workspace_dir}\")\n    print(f\"Output PLY: {output_ply}\")\n    \n    # Step 1: Create database\n    if not run_colmap_command([\n        \"colmap\", \"database_creator\",\n        \"--database_path\", database_path\n    ], \"Database creation\"):\n        return False\n    \n    # Step 2: Feature extraction (CPU-only)\n    if not run_cpu_feature_extraction(database_path, image_dir):\n        print(\"❌ All feature extraction methods failed\")\n        return False\n    \n    # Step 3: Feature matching (CPU-only)\n    if not run_colmap_command([\n        \"colmap\", \"exhaustive_matcher\",\n        \"--database_path\", database_path,\n        \"--SiftMatching.guided_matching\", \"1\",\n        \"--SiftMatching.use_gpu\", \"0\",\n        \"--SiftMatching.max_ratio\", \"0.8\",\n        \"--SiftMatching.max_distance\", \"0.7\"\n    ], \"Feature matching (CPU)\"):\n        return False\n    \n    # Step 4: Structure-from-Motion\n    os.makedirs(sparse_dir, exist_ok=True)\n    if not run_colmap_command([\n        \"colmap\", \"mapper\",\n        \"--database_path\", database_path,\n        \"--image_path\", image_dir,\n        \"--output_path\", sparse_dir,\n        \"--Mapper.ba_refine_focal_length\", \"0\",\n        \"--Mapper.ba_refine_principal_point\", \"0\",\n        \"--Mapper.init_min_tri_angle\", \"4\",\n        \"--Mapper.multiple_models\", \"0\",\n        \"--Mapper.extract_colors\", \"1\"\n    ], \"Structure-from-Motion\"):\n        return False\n    \n    # Check if sparse reconstruction was successful\n    sparse_model_path = os.path.join(sparse_dir, \"0\")\n    if not os.path.exists(sparse_model_path):\n        print(\"❌ Sparse reconstruction failed - no model generated\")\n        return False\n    \n    # Count reconstructed points\n    points_file = os.path.join(sparse_model_path, \"points3D.txt\")\n    if os.path.exists(points_file):\n        with open(points_file, 'r') as f:\n            lines = [line for line in f if not line.startswith('#')]\n            num_points = len(lines)\n        print(f\"✅ Sparse reconstruction successful: {num_points} 3D points\")\n    \n    # Step 5: Convert sparse model to PLY format\n    if not run_colmap_command([\n        \"colmap\", \"model_converter\",\n        \"--input_path\", sparse_model_path,\n        \"--output_path\", output_ply,\n        \"--output_type\", \"PLY\"\n    ], \"Sparse model to PLY conversion\"):\n        return False\n    \n    # Step 6: Also export in text format for inspection\n    if not run_colmap_command([\n        \"colmap\", \"model_converter\",\n        \"--input_path\", sparse_model_path,\n        \"--output_path\", os.path.dirname(output_txt),\n        \"--output_type\", \"TXT\"\n    ], \"Export to text format\"):\n        print(\"⚠️ Text export failed, but PLY export succeeded\")\n    \n    # Final checks and summary\n    print(f\"\\n🎉 CPU-only pipeline complete!\")\n    print(f\"✅ Sparse reconstruction PLY: {output_ply}\")\n    \n    if os.path.exists(output_ply):\n        size = os.path.getsize(output_ply)\n        print(f\"📊 Output file size: {size / (1024*1024):.2f} MB\")\n        \n        # Quick PLY file analysis\n        try:\n            with open(output_ply, 'r') as f:\n                header = f.read(1000)\n                if 'vertex' in header:\n                    # Extract vertex count from PLY header\n                    for line in header.split('\\n'):\n                        if line.startswith('element vertex'):\n                            vertex_count = line.split()[-1]\n                            print(f\"📊 Point cloud contains {vertex_count} vertices\")\n                            break\n        except:\n            pass\n        \n        print(f\"✅ Reconstruction successful! You can view the PLY file in MeshLab, CloudCompare, or similar tools.\")\n        return True\n    else:\n        print(\"❌ Output PLY file was not generated\")\n        return False\n\ndef analyze_reconstruction_quality(workspace_dir):\n    \"\"\"Analyze the quality of the sparse reconstruction.\"\"\"\n    print(\"\\n📊 Analyzing reconstruction quality...\")\n    \n    sparse_model_path = os.path.join(workspace_dir, \"sparse\", \"0\")\n    \n    files_to_check = [\n        (\"cameras.txt\", \"Camera parameters\"),\n        (\"images.txt\", \"Image poses\"), \n        (\"points3D.txt\", \"3D points\")\n    ]\n    \n    for filename, description in files_to_check:\n        filepath = os.path.join(sparse_model_path, filename)\n        if os.path.exists(filepath):\n            with open(filepath, 'r') as f:\n                lines = [line for line in f if not line.startswith('#') and line.strip()]\n                print(f\"✅ {description}: {len(lines)} entries\")\n        else:\n            print(f\"❌ {description}: file not found\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    \"\"\"Main function to run the pipeline.\"\"\"\n    success = run_cpu_only_pipeline()\n    \n    if success:\n        analyze_reconstruction_quality(\"project/workspace\")\n        print(\"\\n🎯 Next steps:\")\n        print(\"   1. Download the PLY file for viewing\")\n        print(\"   2. Use MeshLab or CloudCompare to visualize\")\n        print(\"   3. Consider mesh generation from point cloud if needed\")\n    \n    return success\n\nif __name__ == \"__main__\":\n    # Option 1: Simply call main() without sys.exit()\n    success = main()\n    print(f\"\\n{'✅ Pipeline completed successfully!' if success else '❌ Pipeline failed.'}\")\n    ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualize ply","metadata":{}},{"cell_type":"code","source":"# Load Sample the PLY file\nply_file = \"./project/workspace/sparse_reconstruction.ply\"  # Replace with your file path\ncloud = PyntCloud.from_file(ply_file)\n\n# Print point cloud information\nprint(cloud)\n\n# Convert to Pandas DataFrame\ndf = cloud.points\nUTM_OFFSET = np.array([627285, 4841948, 0])\ndf[['x', 'y', 'z']] -= UTM_OFFSET\ndf.head()\n\nprint(df.columns.tolist())","metadata":{"execution":{"iopub.status.busy":"2025-06-12T10:12:48.488712Z","iopub.status.idle":"2025-06-12T10:12:48.488985Z","shell.execute_reply.started":"2025-06-12T10:12:48.488863Z","shell.execute_reply":"2025-06-12T10:12:48.488875Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Extract RGB values and normalize them to [0, 1] range\nrgb_colors = df[['red', 'green', 'blue']].values / 255.0\nrgb_colors = (rgb_colors - rgb_colors.min()) / (rgb_colors.max() - rgb_colors.min())\n\n# Create a 3D scatter plot\nfig = plt.figure(figsize=(10, 7))\nax = fig.add_subplot(111, projection='3d')\n\n# Scatter plot with RGB colors\nsc = ax.scatter(\n    df['x'],\n    df['y'],\n    df['z'],\n    c=rgb_colors,\n    s=1,  # Adjust point size\n    marker='o'\n)\n\n# Set labels\nax.set_xlabel('X')\nax.set_ylabel('Y')\nax.set_zlabel('Z')\n\nplt.title('3D Point Cloud')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-06-12T10:12:48.490079Z","iopub.status.idle":"2025-06-12T10:12:48.490445Z","shell.execute_reply.started":"2025-06-12T10:12:48.49022Z","shell.execute_reply":"2025-06-12T10:12:48.490233Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_3D(sampled_df):\n    # Extract RGB values and normalize them to [0, 1] range\n    #rgb_colors = sampled_df[['red', 'green', 'blue']].values / 255.0\n\n    # Create a 3D scatter plot\n    fig = go.Figure()\n    fig.add_trace(go.Scatter3d(\n        x=sampled_df['x'],\n        y=sampled_df['y'],\n        z=sampled_df['z'],\n        mode='markers',\n        marker=dict(\n            size=1,  # Adjust point size\n            color=rgb_colors,  # Set color using RGB values\n            opacity=0.8  # Adjust marker opacity if needed\n        )\n        ))\n\n    # Set labels\n    fig.update_layout(\n        scene=dict(\n            xaxis_title='X',\n            yaxis_title='Y',\n            zaxis_title='Z'),\n        title='3D Point Cloud')\n\n    fig.show(renderer='iframe')","metadata":{"execution":{"iopub.status.busy":"2025-06-12T10:12:48.492875Z","iopub.status.idle":"2025-06-12T10:12:48.493256Z","shell.execute_reply.started":"2025-06-12T10:12:48.493049Z","shell.execute_reply":"2025-06-12T10:12:48.493071Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(len(df))  # Check how many rows you have\nsampled_df = df.sample(n=min(100000, len(df)), random_state=42)\nplot_3D(sampled_df)","metadata":{"execution":{"iopub.status.busy":"2025-06-12T10:12:48.49452Z","iopub.status.idle":"2025-06-12T10:12:48.494915Z","shell.execute_reply.started":"2025-06-12T10:12:48.494721Z","shell.execute_reply":"2025-06-12T10:12:48.494742Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}