{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":71885,"databundleVersionId":8143495,"isSourceIdPinned":false,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":1485.608701,"end_time":"2025-09-29T08:34:31.655338","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-09-29T08:09:46.046637","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **IMC2024 Cylinder: Gaussian Splatting**\n\n\nhttps://www.kaggle.com/code/stpeteishii/imc2023-fountain-gaussian-splatting\n\nhttps://www.kaggle.com/code/stpeteishii/imc2024-cylinder-gaussian-splatting\n\n","metadata":{"papermill":{"duration":0.002721,"end_time":"2025-09-29T08:09:50.553226","exception":false,"start_time":"2025-09-29T08:09:50.550505","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"<div style=\"border:2px solid red; padding:10px; border-radius:5px; font-weight:bold;\">\n\n  **train.py tries to open image files named in images.txt (DSG_xxxx.JPG),** \n  \n  **but the actual filename is dsc_xxxx.png.**\n    \n</div>","metadata":{}},{"cell_type":"markdown","source":"## **Summary of Filename Discrepancy Handling Between Image Files and images.txt**\n\n## Problem Description\nThere is a filename discrepancy between:\n- **Image files in `/images` directory**: Actual image files with lowercase names and `.png` extension (e.g., `dsc_8232.png`)\n- **Filenames in `images.txt`**: Reference names with uppercase names and `.JPG` extension (e.g., `DSC_8232.JPG`)\n\n## Key Differences Identified\n\n### 1. Complete Case Sensitivity\n- **Image directory**: Contains `dsc_8232.png` (fully lowercase filename + lowercase extension)\n- **images.txt**: References `DSC_8232.JPG` (fully uppercase filename + uppercase extension)\n- **Example**: `dsc_8232.png` (actual) vs `DSC_8232.JPG` (reference)\n\n### 2. Format Differences\n- **Source**: PNG format images with lowercase names\n- **Reference**: JPG format in camera data with uppercase names\n\n## Solution Implementation\n\n### 1. Complete Case-Insensitive File Mapping\n```python\n# Create mapping between lowercase basenames and actual filenames\nfile_mapping = {}\nfor actual_file in actual_files:  # e.g., 'dsc_8232.png'\n    base_actual = os.path.splitext(actual_file)[0].lower()  # 'dsc_8232'\n    file_mapping[base_actual] = actual_file  # Maps to 'dsc_8232.png'\n```\n\n### 2. Filename Resolution Process\n```python\n# For each reference name from images.txt (e.g., 'DSC_8232.JPG')\nimg_file_ref = 'DSC_8232.JPG'  # Reference from images.txt\nbase_ref = os.path.splitext(img_file_ref)[0].lower()  # 'dsc_8232'\next_ref = os.path.splitext(img_file_ref)[1]  # '.JPG'\n\n# Find the actual filename using lowercase mapping\nactual_file = file_mapping.get(base_ref)  # Returns 'dsc_8232.png'\n```\n\n### 3. Format Conversion with Case Handling\n```python\nif actual_file:  # Found matching file (e.g., 'dsc_8232.png')\n    src_path = f\"{src_images}/{actual_file}\"  # Source: 'dsc_8232.png'\n    dst_path = f\"{data_dir}/images/{img_file_ref}\"  # Destination: 'DSC_8232.JPG'\n\n    # PNG to JPG conversion\n    if actual_file.lower().endswith('.png'):\n        img = Image.open(src_path)\n        # Color mode conversion (RGBA/P -> RGB)\n        if img.mode in ('RGBA', 'LA', 'P'):\n            rgb_img = Image.new('RGB', img.size, (255, 255, 255))\n            rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)\n            img = rgb_img\n        img.save(dst_path, 'JPEG', quality=95)\n```\n\n## Complete Transformation Pipeline\n\n### Step-by-Step Process:\n1. **Input**: Reference name from `images.txt` → `DSC_8232.JPG`\n2. **Normalize**: Extract basename and convert to lowercase → `dsc_8232`\n3. **Lookup**: Find actual file in mapping → returns `dsc_8232.png`\n4. **Convert**: PNG → JPG format with proper color handling\n5. **Output**: Save with reference filename → `DSC_8232.JPG`\n\n### Visual Transformation:\n```\nimages.txt reference: \"DSC_8232.JPG\"\n                    ↓ (lowercase basename)\nLookup key:          \"dsc_8232\" \n                    ↓ (file mapping)\nActual file:         \"dsc_8232.png\"\n                    ↓ (PNG→JPG conversion)\nOutput file:         \"DSC_8232.JPG\"\n```\n\n## Critical Matching Logic\n\n### File Mapping Creation:\n```python\n# Source directory contains: ['dsc_8232.png', 'dsc_8233.png', ...]\nactual_files = os.listdir(src_images)  # All lowercase PNGs\nfile_mapping = {\n    'dsc_8232': 'dsc_8232.png',\n    'dsc_8233': 'dsc_8233.png',\n    # ...\n}\n```\n\n### Reference Processing:\n```python\n# images.txt contains: ['DSC_8232.JPG', 'DSC_8233.JPG', ...]\nfor img_file_ref in common_files:  # Uppercase JPG references\n    base_ref = img_file_ref.split('.')[0].lower()  # 'dsc_8232'\n    actual_file = file_mapping.get(base_ref)  # Finds 'dsc_8232.png'\n```\n\n## Results Achieved\n- **Successful matches**: 36 files resolved despite case differences\n- **Format conversions**: All 36 PNG files converted to JPG\n- **Case handling**: Lowercase source files matched to uppercase references\n- **Pipeline success**: Gaussian Splatting trained successfully with converted data\n\nThis approach handles the complete case discrepancy (both filename and extension) while maintaining the reference naming convention required by the SfM pipeline, ensuring seamless integration with Gaussian Splatting.","metadata":{}},{"cell_type":"code","source":"file_path = '/kaggle/input/image-matching-challenge-2024/train/transp_obj_glass_cylinder/sfm/images.txt' \nline_count = 0\n\nwith open(file_path, 'r', encoding='utf-8') as file:\n    for line in file:\n        print(line.strip()) \n        line_count += 1\n        if line_count >= 5:\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T11:33:44.095553Z","iopub.execute_input":"2025-10-01T11:33:44.095751Z","iopub.status.idle":"2025-10-01T11:33:44.120625Z","shell.execute_reply.started":"2025-10-01T11:33:44.095725Z","shell.execute_reply":"2025-10-01T11:33:44.119938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nGaussian Splatting Video Generator for Kaggle\nThis script processes images and camera data to create a Gaussian Splatting video\n\"\"\"\n\nimport os\nimport sys\nimport subprocess\nimport shutil\nfrom pathlib import Path\n\n# Configuration\nINPUT_PATH = '/kaggle/input/image-matching-challenge-2024/train/transp_obj_glass_cylinder'\nWORK_DIR = '/kaggle/working/gaussian_splatting'\nOUTPUT_DIR = '/kaggle/working/output'\n\ndef setup_environment():\n    \"\"\"Install required packages and clone Gaussian Splatting repository\"\"\"\n    print(\"Setting up environment...\")\n    \n    # Clone 3D Gaussian Splatting repository\n    if not os.path.exists(WORK_DIR):\n        print(\"Cloning Gaussian Splatting repository...\")\n        subprocess.run([\n            'git', 'clone', '--recursive',\n            'https://github.com/graphdeco-inria/gaussian-splatting.git',\n            WORK_DIR\n        ], check=True)\n    \n    os.chdir(WORK_DIR)\n    \n    # Install pip packages\n    print(\"Installing Python packages...\")\n    subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', 'torch', 'torchvision', \n                    'torchaudio', 'plyfile', 'tqdm', 'opencv-python', 'pillow'], check=True)\n    \n    # Build submodules\n    print(\"Building submodules...\")\n    subprocess.run([sys.executable, '-m', 'pip', 'install', 'submodules/diff-gaussian-rasterization'], \n                   check=True, cwd=WORK_DIR)\n    subprocess.run([sys.executable, '-m', 'pip', 'install', 'submodules/simple-knn'], \n                   check=True, cwd=WORK_DIR)\n\ndef convert_cameras_to_pinhole(input_file, output_file):\n    \"\"\"Convert camera models 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                    print(f\"Camera {cam_id}: model={model}, size={width}x{height}, params={len(params)}\")\n                    \n                    # Convert to PINHOLE (fx, fy, cx, cy)\n                    if model == \"PINHOLE\":\n                        f.write(line)\n                    elif model == \"SIMPLE_PINHOLE\":\n                        # SIMPLE_PINHOLE: f, cx, cy -> fx, fy, cx, cy\n                        f_val = params[0]\n                        cx = params[1]\n                        cy = params[2]\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {f_val} {f_val} {cx} {cy}\\n\")\n                        converted_count += 1\n                    elif model == \"SIMPLE_RADIAL\":\n                        # SIMPLE_RADIAL: f, cx, cy, k -> ignore distortion, use f, cx, cy\n                        f_val = params[0]\n                        cx = params[1]\n                        cy = params[2]\n                        # k = params[3]  # radial distortion, ignored for PINHOLE\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {f_val} {f_val} {cx} {cy}\\n\")\n                        converted_count += 1\n                    elif model == \"RADIAL\":\n                        # RADIAL: f, cx, cy, k1, k2 -> use f, cx, cy\n                        f_val = params[0]\n                        cx = params[1]\n                        cy = params[2]\n                        f.write(f\"{cam_id} PINHOLE {width} {height} {f_val} {f_val} {cx} {cy}\\n\")\n                        converted_count += 1\n                    elif model in [\"OPENCV\", \"OPENCV_FISHEYE\", \"FULL_OPENCV\"]:\n                        # Use fx, fy, cx, cy from parameters\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                        # Default: estimate from image size\n                        print(f\"Warning: Unknown model '{model}', using estimated parameters\")\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\")","metadata":{"execution":{"iopub.execute_input":"2025-09-29T08:09:50.566802Z","iopub.status.busy":"2025-09-29T08:09:50.56652Z","iopub.status.idle":"2025-09-29T08:34:30.400415Z","shell.execute_reply":"2025-09-29T08:34:30.399559Z"},"papermill":{"duration":1479.838375,"end_time":"2025-09-29T08:34:30.401821","exception":false,"start_time":"2025-09-29T08:09:50.563446","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ndef get_common_filenames_simple():\n    \"\"\"Get only the common filenames in a simple way.\"\"\"\n    \n    # Extract filenames from images.txt\n    txt_files = set()\n    with open('/kaggle/input/image-matching-challenge-2024/train/transp_obj_glass_cylinder/sfm/images.txt', 'r', encoding='utf-8') as f:\n        for line in f:\n            line = line.strip()\n            if line and not line.startswith('#'):\n                parts = line.split()\n                if parts:\n                    txt_files.add(parts[-1])\n    \n    # Get filenames from the images folder\n    image_files = set(os.listdir('/kaggle/input/image-matching-challenge-2024/train/transp_obj_glass_cylinder/images'))\n    \n    # Find common files\n    common_files = sorted(txt_files & image_files)\n    \n    # Display results\n    print(f\"Number of common files: {len(common_files)}\")\n    print(\"\\nList of common files:\")\n    for filename in common_files:\n        print(filename)\n    \n    # Save to a file\n    with open('common_files.txt', 'w', encoding='utf-8') as f:\n        for filename in common_files:\n            f.write(f\"{filename}\\n\")\n    \n    print(f\"\\nCommon filenames have been saved to 'common_files.txt'\")\n    return common_files\n\n# Execute the function\n#common_files = get_common_filenames_simple()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\nfrom PIL import Image\n\ndef prepare_colmap_data(common_files=None):\n    \"\"\"Convert SfM data to COLMAP format (targeting only common_files)\"\"\"\n    print(\"Preparing COLMAP data...\")\n\n    data_dir = f\"{WORK_DIR}/data/cylinder\"\n    os.makedirs(f\"{data_dir}/sparse/0\", exist_ok=True)\n    os.makedirs(f\"{data_dir}/images\", exist_ok=True)\n\n    # Create a case-insensitive file mapping\n    print(\"Creating case-insensitive file mapping...\")\n    src_images = f\"{INPUT_PATH}/images\"\n    if not os.path.exists(src_images):\n        print(f\"Warning: {src_images} not found, trying /images_full instead\")\n        src_images = f\"{INPUT_PATH}/images_full\"\n\n    if not os.path.exists(src_images):\n        raise FileNotFoundError(f\"Image directory not found: {src_images}\")\n\n    # Create a mapping between lowercase filenames and their actual names\n    actual_files = os.listdir(src_images)\n    file_mapping = {}\n    for actual_file in actual_files:\n        base_actual = os.path.splitext(actual_file)[0].lower()\n        file_mapping[base_actual] = actual_file\n\n    img_count = 0\n    skipped_count = 0\n    converted_count = 0\n\n    # If common_files is specified, process only those\n    if common_files:\n        print(f\"Processing only common files: {len(common_files)} files\")\n        for img_file_ref in common_files:  # img_file_ref is the reference name from cameras.txt (e.g., DSC_8236.JPG)\n            # Convert the reference name to lowercase to find the actual file\n            base_ref = os.path.splitext(img_file_ref)[0].lower()  # dsc_8236\n            ext_ref = os.path.splitext(img_file_ref)[1]  # .JPG\n\n            # Find the actual filename\n            actual_file = file_mapping.get(base_ref)\n\n            if actual_file:\n                src_path = f\"{src_images}/{actual_file}\"\n                # The output filename remains the reference name (e.g., DSC_8236.JPG)\n                dst_path = f\"{data_dir}/images/{img_file_ref}\"\n\n                # Check if PNG to JPG conversion is needed\n                if actual_file.lower().endswith('.png') and ext_ref.lower() in ['.jpg', '.jpeg']:\n                    try:\n                        img = Image.open(src_path)\n                        # Convert RGBA to RGB if necessary\n                        if img.mode in ('RGBA', 'LA', 'P'):\n                            rgb_img = Image.new('RGB', img.size, (255, 255, 255))\n                            if img.mode == 'P':\n                                img = img.convert('RGBA')\n                            rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)\n                            img = rgb_img\n                        elif img.mode != 'RGB':\n                            img = img.convert('RGB')\n\n                        img.save(dst_path, 'JPEG', quality=95)\n                        converted_count += 1\n                        img_count += 1\n                        print(f\"Converted: {actual_file} -> {img_file_ref}\")\n                    except Exception as e:\n                        print(f\"Error converting {src_path}: {e}\")\n                        skipped_count += 1\n                else:\n                    # Normal copy (filename is changed to the reference name)\n                    shutil.copy(src_path, dst_path)\n                    img_count += 1\n                    print(f\"Copied: {actual_file} -> {img_file_ref}\")\n            else:\n                print(f\"Warning: Source file not found for reference: {img_file_ref} (looked for {base_ref}.*)\")\n                skipped_count += 1\n    else:\n        # Original logic (process all images)\n        print(\"No common files specified, processing all images\")\n        for img_file in os.listdir(src_images):\n            if img_file.lower().endswith(('.jpeg', '.jpg', '.png')):\n                src_path = f\"{src_images}/{img_file}\"\n\n                # PNG to JPG conversion if needed\n                if img_file.lower().endswith('.png'):\n                    try:\n                        base_name = os.path.splitext(img_file)[0]\n                        # Standardize the output filename to uppercase\n                        dst_path = f\"{data_dir}/images/{base_name.upper()}.JPG\"\n\n                        img = Image.open(src_path)\n                        if img.mode in ('RGBA', 'LA', 'P'):\n                            rgb_img = Image.new('RGB', img.size, (255, 255, 255))\n                            if img.mode == 'P':\n                                img = img.convert('RGBA')\n                            rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)\n                            img = rgb_img\n                        elif img.mode != 'RGB':\n                            img = img.convert('RGB')\n\n                        img.save(dst_path, 'JPEG', quality=95)\n                        converted_count += 1\n                    except Exception as e:\n                        print(f\"Error converting {img_file}: {e}\")\n                        continue\n                else:\n                    # Standardize the filename to uppercase and copy\n                    base_name = os.path.splitext(img_file)[0]\n                    ext = os.path.splitext(img_file)[1]\n                    dst_path = f\"{data_dir}/images/{base_name.upper()}{ext.upper()}\"\n                    shutil.copy(src_path, dst_path)\n\n                img_count += 1\n\n    print(f\"Processed {img_count} images from {src_images}\")\n    if converted_count > 0:\n        print(f\"Converted {converted_count} PNG images to JPG\")\n    if skipped_count > 0:\n        print(f\"Skipped {skipped_count} files (not found in source)\")\n\n    # Filter images.txt (using common_files)\n    print(\"Filtering images.txt to include only common files...\")\n    if common_files:\n        filter_images_file(\n            f\"{INPUT_PATH}/sfm/images.txt\",\n            f\"{data_dir}/sparse/0/images.txt\",\n            common_files\n        )\n    else:\n        shutil.copy(f\"{INPUT_PATH}/sfm/images.txt\", f\"{data_dir}/sparse/0/images.txt\")\n\n    # Convert camera data\n    print(\"Converting camera data to PINHOLE format...\")\n    convert_cameras_to_pinhole(\n        f\"{INPUT_PATH}/sfm/cameras.txt\",\n        f\"{data_dir}/sparse/0/cameras.txt\"\n    )\n\n    # Copy 3D point data\n    shutil.copy(f\"{INPUT_PATH}/sfm/points3D.txt\", f\"{data_dir}/sparse/0/points3D.txt\")\n\n    print(f\"Data prepared at: {data_dir}\")\n\n    # Debug: Check generated files\n    print(\"\\n=== Generated files check ===\")\n    images_dir = f\"{data_dir}/images\"\n    if os.path.exists(images_dir):\n        files = sorted(os.listdir(images_dir))[:5]\n        print(f\"First 5 files in {images_dir}:\")\n        for f in files:\n            print(f\"  {f}\")\n\n    return data_dir","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def filter_images_file(input_images_path, output_images_path, valid_filenames):\n    \"\"\"\n    Filters images.txt to only include the specified filenames.\n    \"\"\"\n    valid_filenames_set = set(valid_filenames)\n    \n    with open(input_images_path, 'r', encoding='utf-8') as f_in:\n        lines = f_in.readlines()\n    \n    filtered_lines = []\n    i = 0\n    while i < len(lines):\n        line = lines[i].strip()\n        \n        # Keep comment and blank lines as they are\n        if not line or line.startswith('#'):\n            filtered_lines.append(lines[i])\n            i += 1\n            continue\n        \n        # Extract filename from data line\n        parts = line.split()\n        if parts:\n            filename = parts[-1]\n            \n            # Keep the lines only if the filename is in the valid list\n            if filename in valid_filenames_set:\n                filtered_lines.append(lines[i])\n                \n                # Also add the next line (2D point data)\n                if i + 1 < len(lines):\n                    next_line = lines[i + 1].strip()\n                    if next_line and not next_line.startswith('#'):\n                        filtered_lines.append(lines[i + 1])\n                        i += 1  # Skip the next line\n        \n        i += 1\n    \n    # Write the filtered content\n    with open(output_images_path, 'w', encoding='utf-8') as f_out:\n        f_out.writelines(filtered_lines)\n    \n    original_count = len([l for l in lines if l.strip() and not l.strip().startswith('#')]) // 2\n    filtered_count = len([l for l in filtered_lines if l.strip() and not l.strip().startswith('#')]) // 2\n    \n    print(f\"Filtered images.txt: {original_count} -> {filtered_count} images\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_gaussian_splatting(data_dir, iterations=3000):\n    \"\"\"Train Gaussian Splatting model\"\"\"\n    print(f\"Training Gaussian Splatting model for {iterations} iterations...\")\n    \n    model_path = f\"{WORK_DIR}/output/cylinder\"\n    \n    cmd = [\n        sys.executable, 'train.py',\n        '-s', data_dir,\n        '-m', model_path,\n        '--iterations', str(iterations),\n        '--eval'\n    ]\n    \n    subprocess.run(cmd, cwd=WORK_DIR, check=True)\n    \n    return model_path\n\ndef render_video(model_path, output_video_path, iteration=3000):\n    \"\"\"Render video from trained model\"\"\"\n    print(\"Rendering video...\")\n    \n    # Render images - render both train and test cameras\n    cmd = [\n        sys.executable, 'render.py',\n        '-m', model_path,\n        '--iteration', str(iteration)\n    ]\n    \n    print(f\"Running render command: {' '.join(cmd)}\")\n    subprocess.run(cmd, cwd=WORK_DIR, check=True)\n    \n    # Create video from rendered images using ffmpeg\n    print(\"Creating video from rendered frames...\")\n    \n    # Find the correct render directory - check multiple possible locations\n    possible_dirs = [\n        f\"{model_path}/test/ours_{iteration}/renders\",\n        f\"{model_path}/test/ours_{iteration}/gt\",\n        f\"{model_path}/train/ours_{iteration}/renders\",\n        f\"{model_path}/render/ours_{iteration}/renders\"\n    ]\n    \n    render_dir = None\n    for test_dir in possible_dirs:\n        if os.path.exists(test_dir):\n            render_dir = test_dir\n            print(f\"Found render directory: {render_dir}\")\n            break\n    \n    if not render_dir:\n        print(f\"Warning: Render directories not found. Searched:\")\n        for d in possible_dirs:\n            print(f\"  - {d}\")\n        \n        # Try to explore what directories exist\n        test_path = f\"{model_path}/test\"\n        if os.path.exists(test_path):\n            subdirs = os.listdir(test_path)\n            print(f\"Available directories in {test_path}: {subdirs}\")\n            for subdir in subdirs:\n                full_path = os.path.join(test_path, subdir)\n                if os.path.isdir(full_path):\n                    contents = os.listdir(full_path)\n                    print(f\"  {subdir}/: {contents}\")\n                    if 'renders' in contents:\n                        render_dir = os.path.join(full_path, 'renders')\n                        print(f\"Using: {render_dir}\")\n                        break\n    \n    if render_dir and os.path.exists(render_dir):\n        render_imgs = sorted([f for f in os.listdir(render_dir) if f.endswith('.png')])\n        \n        if render_imgs:\n            print(f\"Found {len(render_imgs)} rendered images\")\n            # Create video with ffmpeg\n            subprocess.run([\n                'ffmpeg', '-y',\n                '-framerate', '30',\n                '-pattern_type', 'glob',\n                '-i', f\"{render_dir}/*.png\",\n                '-c:v', 'libx264',\n                '-pix_fmt', 'yuv420p',\n                '-crf', '18',\n                output_video_path\n            ], check=True)\n            \n            print(f\"Video saved to: {output_video_path}\")\n            return True\n        else:\n            print(\"Warning: No rendered PNG images found!\")\n            return False\n    else:\n        print(f\"Error: Could not find render directory\")\n        return False\n\ndef create_gif_and_display(video_path, gif_path):\n    \"\"\"Convert MP4 to GIF and display in notebook\"\"\"\n    print(\"Creating animated GIF...\")\n    \n    # Convert MP4 to GIF using ffmpeg\n    subprocess.run([\n        'ffmpeg', '-y',\n        '-i', video_path,\n        '-vf', 'setpts=8*PTS,fps=10,scale=720:-1:flags=lanczos',\n        '-loop', '0',\n        gif_path\n    ], check=True)\n\n    if os.path.exists(gif_path):\n        size_mb = os.path.getsize(gif_path) / (1024 * 1024)\n        print(f\"GIF created: {gif_path} ({size_mb:.2f} MB)\")\n        \n        # Display animated GIF in notebook\n        #print(\"\\nDisplaying animation:\")\n        #from IPython.display import Image\n        #Image(open(gif_path, 'rb').read())\n        \n        return True\n\n    return False\n\n\nimport os\nfrom PIL import Image\n\ndef convert_images_to_jpg(input_dir, output_dir):\n    \"\"\"Converts PNG images to JPG format.\"\"\"\n    os.makedirs(output_dir, exist_ok=True)\n    converted = []\n    \n    for img_file in sorted(os.listdir(input_dir)):\n        if img_file.lower().endswith('.png'):\n            input_path = os.path.join(input_dir, img_file)\n            # Convert to .JPG (uppercase)\n            output_filename = os.path.splitext(img_file)[0] + '.JPG'\n            output_path = os.path.join(output_dir, output_filename)\n            \n            try:\n                img = Image.open(input_path)\n                # Convert RGBA to RGB\n                if img.mode in ('RGBA', 'LA', 'P'):\n                    rgb_img = Image.new('RGB', img.size, (255, 255, 255))\n                    if img.mode == 'P':\n                        img = img.convert('RGBA')\n                    rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)\n                    img = rgb_img\n                elif img.mode != 'RGB':\n                    img = img.convert('RGB')\n                \n                img.save(output_path, 'JPEG', quality=95)\n                converted.append(output_filename)\n                print(f\"✓ {img_file} -> {output_filename}\")\n            except Exception as e:\n                print(f\"✗ Error converting {img_file}: {e}\")","metadata":{"execution":{"iopub.execute_input":"2025-09-29T08:09:50.566802Z","iopub.status.busy":"2025-09-29T08:09:50.56652Z","iopub.status.idle":"2025-09-29T08:34:30.400415Z","shell.execute_reply":"2025-09-29T08:34:30.399559Z"},"papermill":{"duration":1479.838375,"end_time":"2025-09-29T08:34:30.401821","exception":false,"start_time":"2025-09-29T08:09:50.563446","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    \"\"\"Main execution function\"\"\"\n    print(\"=\"*60)\n    print(\"Gaussian Splatting Video Generator\")\n    print(\"=\"*60)\n    \n    try:\n        # Step 1: Setup environment\n        setup_environment()\n        \n        # Step 2: Prepare data\n        common_files = get_common_filenames_simple()\n        data_dir = prepare_colmap_data(common_files)\n\n      \n        # Step 3: Train model (reduce iterations for faster processing on Kaggle)\n        model_path = train_gaussian_splatting(data_dir, iterations=3000)\n        \n        # Step 4: Render video\n        os.makedirs(OUTPUT_DIR, exist_ok=True)\n        output_video = f\"{OUTPUT_DIR}/gaussian_splatting_cylinder.mp4\"\n        success = render_video(model_path, output_video, iteration=3000)\n        \n        if success:\n            print(\"=\"*60)\n            print(f\"SUCCESS! Video generated at: {output_video}\")\n            print(\"=\"*60)\n            \n            # Display video info\n            if os.path.exists(output_video):\n                size_mb = os.path.getsize(output_video) / (1024 * 1024)\n                print(f\"Video size: {size_mb:.2f} MB\")\n            \n            # Step 5: Create and display GIF\n            output_gif = f\"{OUTPUT_DIR}/gaussian_splatting_cylinder.gif\"\n            create_gif_and_display(output_video, output_gif)\n            \n        else:\n            print(\"=\"*60)\n            print(\"WARNING: Rendering completed but no video was generated.\")\n            print(\"The model was trained successfully. Check the output directories.\")\n            print(\"=\"*60)\n        \n    except Exception as e:\n        print(f\"ERROR: {str(e)}\")\n        import traceback\n        traceback.print_exc()\n\nif __name__ == \"__main__\":\n    main()","metadata":{"execution":{"iopub.execute_input":"2025-09-29T08:09:50.566802Z","iopub.status.busy":"2025-09-29T08:09:50.56652Z","iopub.status.idle":"2025-09-29T08:34:30.400415Z","shell.execute_reply":"2025-09-29T08:34:30.399559Z"},"papermill":{"duration":1479.838375,"end_time":"2025-09-29T08:34:30.401821","exception":false,"start_time":"2025-09-29T08:09:50.563446","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\ngc.collect()","metadata":{"execution":{"iopub.execute_input":"2025-09-29T08:34:30.485358Z","iopub.status.busy":"2025-09-29T08:34:30.48495Z","iopub.status.idle":"2025-09-29T08:34:30.543466Z","shell.execute_reply":"2025-09-29T08:34:30.542669Z"},"papermill":{"duration":0.107852,"end_time":"2025-09-29T08:34:30.545179","exception":false,"start_time":"2025-09-29T08:34:30.437327","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gif_path='/kaggle/working/output/gaussian_splatting_cylinder.gif'\nfrom IPython.display import Image\nImage(open(gif_path, 'rb').read())","metadata":{"execution":{"iopub.execute_input":"2025-09-29T08:34:30.602727Z","iopub.status.busy":"2025-09-29T08:34:30.602485Z","iopub.status.idle":"2025-09-29T08:34:30.774166Z","shell.execute_reply":"2025-09-29T08:34:30.772912Z"},"papermill":{"duration":0.335421,"end_time":"2025-09-29T08:34:30.91022","exception":false,"start_time":"2025-09-29T08:34:30.574799","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}}]}