Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions gtsfm/configs/vggt_gt_covisibility.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Ablation: GT COLMAP/GLOMAP covisibility graph -> Metis Partitioner.
# Based on vggt_megaloc_phototourism.yaml but replaces MegaLoc retriever with GT covisibility.

# @package _global_
_target_: gtsfm.scene_optimizer.SceneOptimizer

loader:
_target_: gtsfm.loader.Olsson
dataset_dir: ??? # Required: set to the dataset root on the command line.
images_dir: null
max_resolution: 518 # VGGT recommended max resolution. Non editable. mode can be set in cluster optimizer config.

image_pairs_generator:
_target_: gtsfm.retriever.image_pairs_generator.ImagePairsGenerator
global_descriptor: null # No descriptor needed for GT covisibility
retriever:
_target_: gtsfm.retriever.ColmapCovisibility
colmap_data_dir: ??? # Path to COLMAP/GLOMAP sparse model dir (e.g. benchmarks/Gendarmenmarkt/sparse_glomap/0)
min_shared_points: 15
batch_size: 1

graph_partitioner:
_target_: gtsfm.graph_partitioner.Metis
min_cameras_to_partition: 12
max_cameras: 40
split_oversized_nodes: true

cluster_optimizer:
_target_: gtsfm.cluster_optimizer.Cacher
optimizer:
_target_: gtsfm.cluster_optimizer.cluster_vggt.ClusterVGGT
input_mode: crop
save_processed_image: false #for debug, save processed images in each subfolder
weights_path: null
conf_threshold: 0.1
max_num_points: 100000
tracking: true
tracking_max_query_pts: 512
tracking_query_frame_num: 3
tracking_use_all_frames_forward_only: true
keypoint_extractor: aliked+sp+sift
track_vis_thresh: 0.05
track_conf_thresh: 0.2
vggt_max_reproj_error: 14.0
post_ba_max_reproj_error: 3.0
min_triangulation_angle: 2.0
camera_type: PINHOLE
drop_outlier_after_camera_merging: true
drop_child_if_merging_fail: true
drop_camera_with_no_track: false
keep_all_cameras_in_merging: false #wether to keep all cameras in merging, otherwise cameras without track can be dropped
min_track_length: 3
ba_track_patch_grid_size: 14
seed: 42
plot_reprojection_histograms: true
run_bundle_adjustment_on_leaf: true
run_bundle_adjustment_on_parent: true
model_cache_key: null
store_pre_ba_result: true
ba_use_calibration_prior: false
ba_use_undistorted_camera_model: false
use_shared_calibration: false
use_gnc: true
gnc_loss: TLS

use_nonlinear_sim3_merging: true
max_track_correspondences_for_sim3: 150
scale_and_average_focal_length_in_merging: false
bridge_min_similarity: 0.0
bridge_top_k: 10
bridge_min_component_size: 3
merging_pre_ba_max_reproj_error: 14.0
merging_pre_ba_min_track_length: 3
merging_ba_use_calibration_prior: false
merging_use_gnc: false
metric_constructed_only: true
4 changes: 4 additions & 0 deletions gtsfm/retriever/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
# Short-name exports for retriever classes.
#
# Usage (Hydra/Python):
# _target_: gtsfm.retriever.ColmapCovisibility
# _target_: gtsfm.retriever.Exhaustive
# _target_: gtsfm.retriever.JointSimilaritySequential
# _target_: gtsfm.retriever.Sequential
# _target_: gtsfm.retriever.Similarity

from .colmap_covisibility_retriever import ColmapCovisibilityRetriever
from .exhaustive_retriever import ExhaustiveRetriever
from .joint_similarity_sequential_retriever import JointSimilaritySequentialRetriever
from .sequential_retriever import SequentialRetriever
from .similarity_retriever import SimilarityRetriever

ColmapCovisibility = ColmapCovisibilityRetriever
Exhaustive = ExhaustiveRetriever
JointSimilaritySequential = JointSimilaritySequentialRetriever
Sequential = SequentialRetriever
Similarity = SimilarityRetriever

__all__ = [
"ColmapCovisibility",
"Exhaustive",
"JointSimilaritySequential",
"Sequential",
Expand Down
110 changes: 110 additions & 0 deletions gtsfm/retriever/colmap_covisibility_retriever.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Retriever that builds a visibility graph from COLMAP ground-truth covisibility.

For each 3D point in a COLMAP reconstruction, all pairs of images that observe
that point are considered covisible. Useful for ablation studies.

Authors: GTSfM Authors
"""

from collections import defaultdict
from itertools import combinations
from pathlib import Path
from typing import List, Optional

import numpy as np

import gtsfm.utils.logger as logger_utils
import thirdparty.colmap.scripts.python.read_write_model as colmap_io
from gtsfm.products.visibility_graph import VisibilityGraph
from gtsfm.retriever.retriever_base import RetrieverBase

logger = logger_utils.get_logger()


class ColmapCovisibilityRetriever(RetrieverBase):
"""Retriever that extracts covisibility from a COLMAP reconstruction's 3D point tracks."""

def __init__(self, colmap_data_dir: str, min_shared_points: int = 1) -> None:
"""
Args:
colmap_data_dir: Path to directory containing COLMAP model files
(cameras.{txt,bin}, images.{txt,bin}, points3D.{txt,bin}).
min_shared_points: Minimum number of shared 3D points for two images
to be considered covisible.
"""
self._colmap_data_dir = colmap_data_dir
self._min_shared_points = min_shared_points

def __repr__(self) -> str:
return (
f"ColmapCovisibilityRetriever("
f"colmap_data_dir={self._colmap_data_dir}, "
f"min_shared_points={self._min_shared_points})"
)

def get_image_pairs(
self,
global_descriptors: Optional[List[np.ndarray]],
image_fnames: List[str],
plots_output_dir: Optional[Path] = None,
) -> VisibilityGraph:
"""Build visibility graph from COLMAP covisibility.

Args:
global_descriptors: Ignored (not needed for GT covisibility).
image_fnames: File names of images from the loader, in loader order.
plots_output_dir: Unused.

Returns:
List of (i, j) pairs where i < j, representing covisible image pairs.
"""
colmap_dir = Path(self._colmap_data_dir)
if (colmap_dir / "images.txt").exists():
ext = ".txt"
elif (colmap_dir / "images.bin").exists():
ext = ".bin"
else:
raise FileNotFoundError(f"No COLMAP images file found in {colmap_dir}")

_, images, points3d = colmap_io.read_model(path=str(colmap_dir), ext=ext)

# Map COLMAP image ID -> loader index via filename basename.
fname_to_loader_idx = {fname: idx for idx, fname in enumerate(image_fnames)}
colmap_id_to_loader_idx = {}
for colmap_img in images.values():
basename = Path(colmap_img.name).name
if basename in fname_to_loader_idx:
colmap_id_to_loader_idx[colmap_img.id] = fname_to_loader_idx[basename]

logger.info(
"Mapped %d / %d COLMAP images to loader indices.",
len(colmap_id_to_loader_idx),
len(images),
)

# Count shared 3D points per image pair.
pair_counts: defaultdict[tuple[int, int], int] = defaultdict(int)
for point3d in points3d.values():
loader_indices = set()
for img_id in point3d.image_ids:
lid = colmap_id_to_loader_idx.get(int(img_id))
if lid is not None:
loader_indices.add(lid)

for idx_i, idx_j in combinations(sorted(loader_indices), 2):
pair_counts[(idx_i, idx_j)] += 1

# Filter by minimum shared points.
pairs: VisibilityGraph = [
edge for edge, count in pair_counts.items() if count >= self._min_shared_points
]
pairs.sort()

logger.info(
"ColmapCovisibilityRetriever: %d pairs (min_shared_points=%d) from %d 3D points.",
len(pairs),
self._min_shared_points,
len(points3d),
)

return pairs
Loading
Loading