File size: 3,981 Bytes
f34db7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
Run COLMAP SfM (Structure-from-Motion) on each part's images using pycolmap.
Produces sparse reconstruction (cameras, images, points3D) needed for 3DGS.

If COLMAP fails (likely for textureless CAD parts with few views), we report
the failure clearly so we can fall back to an alternative pose estimator.
"""
import os
import sys
import shutil
import pycolmap
import numpy as np
from PIL import Image

PARTS_DIR = "data/parts"
PARTS = sorted(os.listdir(PARTS_DIR))

def run_colmap_for_part(part):
    input_dir = os.path.abspath(os.path.join(PARTS_DIR, part, "input"))
    workspace = os.path.abspath(os.path.join(PARTS_DIR, part, "colmap_ws"))
    db_path = os.path.join(workspace, "database.db")
    image_dir = os.path.join(workspace, "images")
    sparse_dir = os.path.join(workspace, "sparse", "0")

    # fresh workspace
    if os.path.exists(workspace):
        shutil.rmtree(workspace)
    os.makedirs(image_dir, exist_ok=True)
    os.makedirs(os.path.join(workspace, "sparse"), exist_ok=True)

    # copy images
    images = sorted([f for f in os.listdir(input_dir) if f.endswith(".png")])
    for img in images:
        shutil.copy(os.path.join(input_dir, img), os.path.join(image_dir, img))

    print(f"\n{'='*60}")
    print(f"PART: {part}  ({len(images)} images)")
    print(f"{'='*60}")

    # Step 1: feature extraction
    print("[1/4] Feature extraction...")
    try:
        pycolmap.extract_features(database_path=db_path, image_path=image_dir)
    except Exception as e:
        print(f"  FAILED: {e}")
        return False, str(e)

    # Step 2: matching
    print("[2/4] Feature matching (exhaustive)...")
    try:
        pycolmap.match_exhaustive(database_path=db_path)
    except Exception as e:
        print(f"  FAILED: {e}")
        return False, str(e)

    # Step 3: incremental SfM
    print("[3/4] Incremental mapping (SfM)...")
    try:
        os.makedirs(sparse_dir, exist_ok=True)
        reconstruction = pycolmap.incremental_mapping(
            database_path=db_path,
            image_path=image_dir,
            output_path=os.path.join(workspace, "sparse"),
            options=pycolmap.IncrementalPipelineOptions(
                min_num_matches=5,
                min_focal_length_ratio=0.1,
                max_focal_length_ratio=10.0,
            ),
        )
    except Exception as e:
        print(f"  FAILED: {e}")
        return False, str(e)

    if reconstruction is None or len(reconstruction) == 0:
        print("  FAILED: No reconstruction produced (0 models)")
        return False, "No reconstruction produced"

    # Check how many images were registered
    rec = reconstruction[0]
    n_reg = sum(1 for im in rec.images.values() if im.has_pose)
    n_points = len(rec.points3D)
    n_cameras = len(rec.cameras)
    print(f"[4/4] Reconstruction: {n_reg}/{len(images)} images registered, {n_points} 3D points, {n_cameras} cameras")

    if n_reg < 3:
        print(f"  WARNING: only {n_reg} images registered - insufficient for 3DGS")
        return False, f"Only {n_reg} images registered"

    # Export to text + PLY for inspection
    rec.write(sparse_dir)
    pycolmap.export_points3D(
        output_path=os.path.join(workspace, "sparse", "points3D.ply"),
        database_path=db_path,
        image_path=image_dir,
        output_type=pycolmap.OutputType.PLY,
    )
    print(f"  Saved sparse model to {sparse_dir}")
    return True, f"{n_reg}/{len(images)} registered, {n_points} points"


def main():
    results = {}
    for part in PARTS:
        input_dir = os.path.join(PARTS_DIR, part, "input")
        if not os.path.isdir(input_dir):
            continue
        ok, msg = run_colmap_for_part(part)
        results[part] = (ok, msg)

    print(f"\n{'='*60}")
    print("COLMAP SUMMARY")
    print(f"{'='*60}")
    for part, (ok, msg) in results.items():
        status = "OK" if ok else "FAILED"
        print(f"  {part}: {status} - {msg}")

    return results

if __name__ == "__main__":
    main()