diff --git a/archive_old_feats.sh b/archive_old_feats.sh new file mode 100644 index 00000000..f250ff84 --- /dev/null +++ b/archive_old_feats.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Archive the old open-SuperPoint feature caches (and stale matches/sfm/results that +# depended on them) instead of deleting, then leave the run dirs clean for the +# corrected superpoint_mg_aachen re-run. +set -euo pipefail + +REPO=/home/ubuntu/work/MambaGlue/Hierarchical-Localization +cd "$REPO" + +STAMP=$(date +%Y%m%d_%H%M%S) +ARCHIVE="outputs/_archive_open_sp_${STAMP}" +mkdir -p "$ARCHIVE" +echo "Archiving stale artifacts to: $ARCHIVE" + +for tag in nc2 nc3 nc4 mg; do + src="outputs/aachen_v1.1_${tag}" + [[ -d "$src" ]] || { echo " (skip $tag: $src not found)"; continue; } + + dst="$ARCHIVE/aachen_v1.1_${tag}" + mkdir -p "$dst" + + # move anything tied to the old open-SuperPoint features: + # - the 1024/1600 open feature h5s + # - matches, sfm models, results, retrieval pairs built on top of them + # - the local-feature pairs are matcher-independent but cheap; archive too for a clean slate + shopt -s nullglob + for f in \ + "$src"/feats-superpoint-open-*.h5 \ + "$src"/matches-superpoint-*.h5 \ + "$src"/global-feats-*.h5 \ + "$src"/pairs-query-netvlad*.txt \ + "$src"/pairs-db-covis*.txt \ + "$src"/sfm_superpoint-open+* \ + "$src"/Aachen-v1.1_hloc_superpoint-open+*; do + echo " mv $f" + mv "$f" "$dst"/ + done + shopt -u nullglob +done + +echo "Done. Archived under $ARCHIVE" +echo "Remaining in run dirs (should be empty or only new artifacts):" +for tag in nc2 nc3 nc4 mg; do + echo "--- outputs/aachen_v1.1_${tag} ---" + ls -1 "outputs/aachen_v1.1_${tag}" 2>/dev/null || true +done \ No newline at end of file diff --git a/hloc/extract_features.py b/hloc/extract_features.py index ab9456a8..56a61ce5 100644 --- a/hloc/extract_features.py +++ b/hloc/extract_features.py @@ -146,6 +146,26 @@ "model": {"name": "megaloc"}, "preprocessing": {"resize_max": 1024}, }, + "superpoint_open_aachen": { + "output": "feats-superpoint-open-n4096-r1024", + "model": { + "name": "superpoint_open", + "nms_radius": 3, + "max_num_keypoints": 4096, + "detection_threshold": 0.005, + }, + "preprocessing": {"grayscale": True, "resize_max": 1024}, + }, + "superpoint_mg_aachen": { + "output": "feats-superpoint-mg-n2048-r1600", + "model": { + "name": "superpoint", # hloc's native MagicLeap SuperPoint + "nms_radius": 3, # matches training + "max_keypoints": 2048, # test-time budget; see note below + "keypoint_threshold": 0.0, # matches training's detection_threshold: 0.0 + }, + "preprocessing": {"grayscale": True, "resize_max": 1600}, + }, } diff --git a/hloc/extractors/superpoint_open.py b/hloc/extractors/superpoint_open.py new file mode 100644 index 00000000..df345fce --- /dev/null +++ b/hloc/extractors/superpoint_open.py @@ -0,0 +1,64 @@ +"""hloc extractor wrapper for glue-factory's *open* SuperPoint. + +Place this file at: Hierarchical-Localization/hloc/extractors/superpoint_open.py + +Why: glue-factory trains on the rpautrat open SuperPoint +(gluefactory.models.extractors.superpoint_open), but hloc ships the original +MagicLeap SuperPoint by default. Both are 256-d, but the descriptor +distributions differ, which biases your Aachen numbers. This wrapper runs the +exact SuperPoint your LateMambaGlue checkpoint was trained against. + +Requires glue-factory importable (`pip install -e .` in the glue-factory repo, +or uncomment the sys.path block). The open SuperPoint weights auto-download on +first instantiation, so the first run needs network access. +""" + +import sys +from pathlib import Path + +from omegaconf import OmegaConf + +from ..utils.base_model import BaseModel + +# --- If glue-factory is NOT pip-installed: --- +# GLUEFACTORY_ROOT = Path("/abs/path/to/glue-factory") +# sys.path.append(str(GLUEFACTORY_ROOT)) + +from gluefactory.models.extractors.superpoint_open import ( # noqa: E402 + SuperPoint as _GFSuperPoint, +) + + +class SuperPointOpen(BaseModel): + default_conf = { + "nms_radius": 3, + "max_num_keypoints": 4096, # localization wants more kpts than the 512/1024 used in training + "detection_threshold": 0.005, + "remove_borders": 4, + "descriptor_dim": 256, + "force_num_keypoints": False, # variable kpt count per image for localization + } + required_inputs = ["image"] + + def _init(self, conf): + gf_conf = OmegaConf.create({ + "nms_radius": conf["nms_radius"], + "max_num_keypoints": conf["max_num_keypoints"], + "detection_threshold": conf["detection_threshold"], + "remove_borders": conf["remove_borders"], + "descriptor_dim": conf["descriptor_dim"], + "force_num_keypoints": conf["force_num_keypoints"], + }) + self.net = _GFSuperPoint(gf_conf) + self.net.eval() + + def _forward(self, data): + # hloc passes a grayscale image [B, 1, H, W] in [0, 1]; glue-factory wants the same. + pred = self.net({"image": data["image"]}) + # glue-factory out: keypoints [B,N,2] (x,y), keypoint_scores [B,N], descriptors [B,N,D] + # hloc expects: keypoints [B,N,2], scores [B,N], descriptors [B,D,N] + return { + "keypoints": pred["keypoints"], + "scores": pred["keypoint_scores"], + "descriptors": pred["descriptors"].transpose(-1, -2).contiguous(), + } \ No newline at end of file diff --git a/hloc/match_features.py b/hloc/match_features.py index 679e81e9..3ba83b11 100644 --- a/hloc/match_features.py +++ b/hloc/match_features.py @@ -85,6 +85,17 @@ "output": "matches-adalam", "model": {"name": "adalam"}, }, + "superpoint+latemambaglue": { + "output": "matches-superpoint-latemambaglue", + "model": { + "name": "latemambaglue", + "features": "superpoint", + "checkpoint": "/home/ubuntu/work/MambaGlue/glue-factory/outputs/training/latemambaglueMDnc3/checkpoint_best.tar", + "n_layers": 9, + "n_cross_layers": 3, + "filter_threshold": 0.1, + }, + }, } diff --git a/hloc/matchers/latemambaglue.py b/hloc/matchers/latemambaglue.py new file mode 100644 index 00000000..5fb36bbe --- /dev/null +++ b/hloc/matchers/latemambaglue.py @@ -0,0 +1,101 @@ +"""hloc matcher wrapper for LateMambaGlue (trained in glue-factory). + +Place this file at: Hierarchical-Localization/hloc/matchers/latemambaglue.py + +Requires glue-factory to be importable. Easiest: from the glue-factory repo +root run `pip install -e .` so `import gluefactory` works. Otherwise uncomment +the sys.path block below and point it at your glue-factory checkout. +""" + +import sys +from pathlib import Path + +import torch +from omegaconf import OmegaConf + +from ..utils.base_model import BaseModel + +# --- If glue-factory is NOT pip-installed, expose it on sys.path: --- +#GLUEFACTORY_ROOT = Path("/home/ubuntu/work/MambaGlue/glue-factory") +#sys.path.append(str(GLUEFACTORY_ROOT)) + +from gluefactory.models.matchers.latemambaglue import ( # noqa: E402 + LateMambaGlue as _GFLateMambaGlue, +) + + +class LateMambaGlue(BaseModel): + default_conf = { + "checkpoint": None, # REQUIRED: path to glue-factory checkpoint_best.tar + "n_layers": 9, + "n_cross_layers": 3, # match the trained variant: nc2/nc3/nc4 -> 2/3/4 + "filter_threshold": 0.1, + "flash": True, + "checkpointed": False, # gradient checkpointing OFF at inference + "features": "superpoint", + } + # hloc feeds these keys to _forward (descriptors are [B, D, N] in hloc) + required_inputs = [ + "image0", "keypoints0", "scores0", "descriptors0", + "image1", "keypoints1", "scores1", "descriptors1", + ] + + def _init(self, conf): + model_conf = OmegaConf.create({ + "n_layers": conf["n_layers"], + "n_cross_layers": conf["n_cross_layers"], + "filter_threshold": conf["filter_threshold"], + "flash": conf["flash"], + "checkpointed": conf["checkpointed"], + }) + self.net = _GFLateMambaGlue(model_conf) + + ckpt = conf["checkpoint"] + assert ckpt is not None, "Set the 'checkpoint' field to your .tar file" + state = torch.load(ckpt, map_location="cpu") + sd = state["model"] if isinstance(state, dict) and "model" in state else state + + # glue-factory saves the whole two_view_pipeline; matcher weights are + # prefixed with "matcher." — strip it to get the bare matcher state dict. + matcher_sd = { + k[len("matcher."):]: v for k, v in sd.items() if k.startswith("matcher.") + } + if not matcher_sd: # already a bare matcher checkpoint + matcher_sd = sd + + missing, unexpected = self.net.load_state_dict(matcher_sd, strict=False) + if missing: + print(f"[latemambaglue] {len(missing)} missing keys, e.g. {missing[:3]}") + if unexpected: + print(f"[latemambaglue] {len(unexpected)} unexpected keys, e.g. {unexpected[:3]}") + self.net.eval() + + def _forward(self, data): + # hloc descriptors: [B, D, N] -> glue-factory wants [B, N, D] + desc0 = data["descriptors0"].transpose(-1, -2).contiguous().float() + desc1 = data["descriptors1"].transpose(-1, -2).contiguous().float() + + dev = data["keypoints0"].device + h0, w0 = data["image0"].shape[-2:] + h1, w1 = data["image1"].shape[-2:] + size0 = torch.tensor([[w0, h0]], dtype=torch.float32, device=dev) + size1 = torch.tensor([[w1, h1]], dtype=torch.float32, device=dev) + + gf_data = { + "keypoints0": data["keypoints0"].float(), + "keypoints1": data["keypoints1"].float(), + "descriptors0": desc0, + "descriptors1": desc1, + # provide image size in both common conventions so keypoint + # normalization is exact regardless of which one your forward reads + "image_size0": size0, + "image_size1": size1, + "view0": {"image_size": size0}, + "view1": {"image_size": size1}, + } + + pred = self.net(gf_data) + return { + "matches0": pred["matches0"], + "matching_scores0": pred["matching_scores0"], + } \ No newline at end of file diff --git a/hloc/matchers/mambaglue.py b/hloc/matchers/mambaglue.py new file mode 100644 index 00000000..4ff29eeb --- /dev/null +++ b/hloc/matchers/mambaglue.py @@ -0,0 +1,94 @@ +"""hloc matcher wrapper for glue-factory's original MambaGlue. + +Place at: Hierarchical-Localization/hloc/matchers/mambaglue.py + +This is the "normal MambaGlue" baseline -- a different class from LateMambaGlue +(no Context/Interaction split, no n_cross_layers), with its own checkpoint +(e.g. outputs/training/sp+mg_megadepth/checkpoint_best.tar). I/O handling is the +same as the LateMambaGlue wrapper since both follow glue-factory's matcher +convention. + +Requires glue-factory importable (`pip install -e .` in the glue-factory repo). +""" + +import sys +from pathlib import Path + +import torch +from omegaconf import OmegaConf + +from ..utils.base_model import BaseModel + +# --- If glue-factory is NOT pip-installed: --- +# GLUEFACTORY_ROOT = Path("/home/ubuntu/work/MambaGlue/glue-factory") +# sys.path.append(str(GLUEFACTORY_ROOT)) + +from gluefactory.models.matchers.mambaglue import ( # noqa: E402 + MambaGlue as _GFMambaGlue, +) + + +class MambaGlue(BaseModel): + default_conf = { + "checkpoint": None, # REQUIRED: path to glue-factory checkpoint_best.tar + "filter_threshold": 0.1, + "n_layers": None, # None => use MambaGlue's own default architecture + "features": "superpoint", + } + required_inputs = [ + "image0", "keypoints0", "scores0", "descriptors0", + "image1", "keypoints1", "scores1", "descriptors1", + ] + + def _init(self, conf): + # keep the override set minimal: only keys certain to be in MambaGlue's + # default_conf, so glue-factory's struct-checked merge can't reject them + model_conf = {"filter_threshold": conf["filter_threshold"]} + if conf.get("n_layers") is not None: + model_conf["n_layers"] = conf["n_layers"] + self.net = _GFMambaGlue(OmegaConf.create(model_conf)) + + ckpt = conf["checkpoint"] + assert ckpt is not None, "Set the 'checkpoint' field to your .tar file" + state = torch.load(ckpt, map_location="cpu") + sd = state["model"] if isinstance(state, dict) and "model" in state else state + + # glue-factory saves the two_view_pipeline; matcher weights prefixed "matcher." + matcher_sd = { + k[len("matcher."):]: v for k, v in sd.items() if k.startswith("matcher.") + } + if not matcher_sd: + matcher_sd = sd + + missing, unexpected = self.net.load_state_dict(matcher_sd, strict=False) + if missing: + print(f"[mambaglue] {len(missing)} missing keys, e.g. {missing[:3]}") + if unexpected: + print(f"[mambaglue] {len(unexpected)} unexpected keys, e.g. {unexpected[:3]}") + self.net.eval() + + def _forward(self, data): + desc0 = data["descriptors0"].transpose(-1, -2).contiguous().float() + desc1 = data["descriptors1"].transpose(-1, -2).contiguous().float() + + dev = data["keypoints0"].device + h0, w0 = data["image0"].shape[-2:] + h1, w1 = data["image1"].shape[-2:] + size0 = torch.tensor([[w0, h0]], dtype=torch.float32, device=dev) + size1 = torch.tensor([[w1, h1]], dtype=torch.float32, device=dev) + + gf_data = { + "keypoints0": data["keypoints0"].float(), + "keypoints1": data["keypoints1"].float(), + "descriptors0": desc0, + "descriptors1": desc1, + "image_size0": size0, + "image_size1": size1, + "view0": {"image_size": size0}, + "view1": {"image_size": size1}, + } + pred = self.net(gf_data) + return { + "matches0": pred["matches0"], + "matching_scores0": pred["matching_scores0"], + } \ No newline at end of file diff --git a/launch_aachen_parallel.sh b/launch_aachen_parallel.sh new file mode 100644 index 00000000..9578057f --- /dev/null +++ b/launch_aachen_parallel.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Launch nc2 / nc3 / nc4 / mambaglue Aachen runs in parallel: one tmux session + one GPU each. +# Detached tmux => jobs run on the server and survive SSH/internet drops. +# Usage: bash launch_aachen_parallel.sh +set -euo pipefail + +REPO=/home/ubuntu/work/MambaGlue/Hierarchical-Localization +CKPT_DIR=/home/ubuntu/work/MambaGlue/glue-factory/outputs/training +CONDA_SH=$HOME/miniconda3/etc/profile.d/conda.sh +ENV=hlocaachen + +mkdir -p "$REPO/logs" + +# spec = tag : gpu : matcher_name : n_cross_layers : checkpoint_path +# (leave n_cross_layers empty for mambaglue) +RUNS=( + "nc2:0:latemambaglue:2:$CKPT_DIR/latemambaglueMDnc2/checkpoint_best.tar" + "nc3:1:latemambaglue:3:$CKPT_DIR/latemambaglueMDnc3/checkpoint_best.tar" + "nc4:2:latemambaglue:4:$CKPT_DIR/latemambaglueMDnc4/checkpoint_best.tar" + "mg:3:mambaglue::$CKPT_DIR/sp+mg_megadepth/checkpoint_best.tar" +) + +for spec in "${RUNS[@]}"; do + IFS=":" read -r tag gpu mname ncl ckpt <<< "$spec" + session="aachen_${tag}" + + if [[ ! -f "$ckpt" ]]; then + echo "!! SKIP $tag: checkpoint not found -> $ckpt"; continue + fi + if tmux has-session -t "$session" 2>/dev/null; then + echo "!! SKIP $tag: tmux session '$session' already exists (kill it first)"; continue + fi + + ncl_arg="" + [[ -n "$ncl" ]] && ncl_arg="--n_cross_layers $ncl" + + cmd="source $CONDA_SH && conda activate $ENV && cd $REPO && \ +CUDA_VISIBLE_DEVICES=$gpu python run_aachen_lmg.py \ +--dataset datasets/aachen_v1.1 \ +--outputs outputs/aachen_v1.1_${tag} \ +--checkpoint $ckpt \ +--matcher_name $mname $ncl_arg --tag $tag \ +--num_covis 20 --num_loc 50 \ +2>&1 | tee logs/aachen_${tag}.log" + + tmux new-session -d -s "$session" + tmux send-keys -t "$session" "$cmd" Enter + echo "launched $session (GPU $gpu, matcher=$mname ${ncl:+nc=$ncl})" +done + +echo; echo "Sessions:"; tmux ls \ No newline at end of file diff --git a/run_aachen_latemambaglue.py b/run_aachen_latemambaglue.py new file mode 100644 index 00000000..804d7f1d --- /dev/null +++ b/run_aachen_latemambaglue.py @@ -0,0 +1,112 @@ +"""End-to-end Aachen Day-Night v1.1 visual localization + with open-SuperPoint + LateMambaGlue (trained in glue-factory). + +This mirrors hloc's stock hloc/pipelines/Aachen_v1_1/pipeline.py, with three +swaps from the SuperGlue default: + - extractor -> superpoint_open_aachen (matches glue-factory training) + - matcher -> superpoint+latemambaglue (your trained checkpoint) + - output names tagged so runs don't collide with a baseline run + +Place this at the hloc repo root (or anywhere `import hloc` resolves) and run: + + python run_aachen_latemambaglue.py \ + --dataset datasets/aachen_v1.1 \ + --outputs outputs/aachen_v1.1_latemambaglue \ + --num_covis 20 --num_loc 50 + +Prerequisites (see the chat for full setup): + 1. hloc/matchers/latemambaglue.py (wrapper, provided) + 2. hloc/extractors/superpoint_open.py (wrapper, provided) + 3. confs entries registered in hloc/match_features.py and + hloc/extract_features.py (provided) + 4. glue-factory importable (`pip install -e .` in the glue-factory repo) +""" + +import argparse +from pathlib import Path + +from hloc import ( + extract_features, + localize_sfm, + match_features, + pairs_from_covisibility, + pairs_from_retrieval, + triangulation, +) + + +def run(args): + dataset = args.dataset + images = dataset / "images_upright/" + sift_sfm = dataset / "3D-models/aachen_v_1_1" # COLMAP model shipped with v1.1 + + outputs = args.outputs + outputs.mkdir(exist_ok=True, parents=True) + sfm_pairs = outputs / f"pairs-db-covis{args.num_covis}.txt" + loc_pairs = outputs / f"pairs-query-netvlad{args.num_loc}.txt" + + # ---- the swaps vs. the stock SuperGlue pipeline ------------------------- + reference_sfm = outputs / "sfm_superpoint-open+latemambaglue" + results = ( + outputs + / f"Aachen-v1.1_hloc_superpoint-open+latemambaglue_netvlad{args.num_loc}.txt" + ) + + retrieval_conf = extract_features.confs["netvlad"] + feature_conf = extract_features.confs["superpoint_open_aachen"] # swap 1 + matcher_conf = match_features.confs["superpoint+latemambaglue"] # swap 2 + # ------------------------------------------------------------------------- + + # 1) Local features for every db + query image (open SuperPoint) + features = extract_features.main(feature_conf, images, outputs, as_half=True) + + # 2) Reference SfM: covisible db pairs -> LateMambaGlue match -> triangulate + pairs_from_covisibility.main(sift_sfm, sfm_pairs, num_matched=args.num_covis) + sfm_matches = match_features.main( + matcher_conf, sfm_pairs, feature_conf["output"], outputs + ) + triangulation.main( + reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches + ) + + # 3) Global descriptors (NetVLAD) -> top-k retrieval pairs, query vs db + global_descriptors = extract_features.main(retrieval_conf, images, outputs) + pairs_from_retrieval.main( + global_descriptors, + loc_pairs, + args.num_loc, + query_prefix="query", + db_model=reference_sfm, + ) + + # 4) Match query<->db with LateMambaGlue, then PnP + RANSAC localization + loc_matches = match_features.main( + matcher_conf, loc_pairs, feature_conf["output"], outputs + ) + localize_sfm.main( + reference_sfm, + dataset / "queries/*_time_queries_with_intrinsics.txt", + loc_pairs, + features, + loc_matches, + results, + covisibility_clustering=False, # standard for Aachen + ) + + print(f"\nDone.\nEstimated query poses: {results}") + print("Upload that file to https://www.visuallocalization.net/ for day/night accuracy.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument("--dataset", type=Path, default="datasets/aachen_v1.1") + parser.add_argument( + "--outputs", type=Path, default="outputs/aachen_v1.1_latemambaglue" + ) + parser.add_argument("--num_covis", type=int, default=20, + help="db image pairs for SfM triangulation") + parser.add_argument("--num_loc", type=int, default=50, + help="retrieved db pairs per query (50 = best Aachen results)") + run(parser.parse_args()) \ No newline at end of file diff --git a/run_aachen_lmg.py b/run_aachen_lmg.py new file mode 100644 index 00000000..ad926331 --- /dev/null +++ b/run_aachen_lmg.py @@ -0,0 +1,111 @@ +"""Parametrized Aachen Day-Night v1.1 runner for LateMambaGlue OR MambaGlue. + +Backward compatible with the earlier nc2/nc4 launches. Pick the matcher with +--matcher_name; pass --n_cross_layers only for latemambaglue. + +LateMambaGlue (nc4): + CUDA_VISIBLE_DEVICES=1 python run_aachen_lmg.py \ + --outputs outputs/aachen_v1.1_nc4 \ + --checkpoint .../latemambaglueMDnc4/checkpoint_best.tar \ + --matcher_name latemambaglue --n_cross_layers 4 --tag nc4 + +MambaGlue baseline: + CUDA_VISIBLE_DEVICES=2 python run_aachen_lmg.py \ + --outputs outputs/aachen_v1.1_mg \ + --checkpoint .../sp+mg_megadepth/checkpoint_best.tar \ + --matcher_name mambaglue --tag mg +""" + +import argparse +from pathlib import Path + +from hloc import ( + extract_features, + localize_sfm, + match_features, + pairs_from_covisibility, + pairs_from_retrieval, + triangulation, +) + + +def run(args): + dataset = args.dataset + images = dataset / "images_upright/" + sift_sfm = dataset / "3D-models/aachen_v_1_1" + + outputs = args.outputs + outputs.mkdir(exist_ok=True, parents=True) + name = args.matcher_name + sfm_pairs = outputs / f"pairs-db-covis{args.num_covis}.txt" + loc_pairs = outputs / f"pairs-query-netvlad{args.num_loc}.txt" + reference_sfm = outputs / f"sfm_superpoint-open+{name}-{args.tag}" + results = ( + outputs + / f"Aachen-v1.1_hloc_superpoint-open+{name}-{args.tag}_netvlad{args.num_loc}.txt" + ) + + retrieval_conf = extract_features.confs["netvlad"] + feature_conf = extract_features.confs["superpoint_mg_aachen"] + + model = { + "name": name, + "features": "superpoint", + "checkpoint": args.checkpoint, + "filter_threshold": args.filter_threshold, + } + if args.n_layers is not None: + model["n_layers"] = args.n_layers + if args.n_cross_layers is not None: + model["n_cross_layers"] = args.n_cross_layers + matcher_conf = {"output": f"matches-superpoint-{name}-{args.tag}", "model": model} + + print(f"[{args.tag}] matcher={name} ckpt={args.checkpoint} " + f"n_cross_layers={args.n_cross_layers}") + + features = extract_features.main(feature_conf, images, outputs, as_half=True) + + pairs_from_covisibility.main(sift_sfm, sfm_pairs, num_matched=args.num_covis) + sfm_matches = match_features.main( + matcher_conf, sfm_pairs, feature_conf["output"], outputs + ) + triangulation.main( + reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches + ) + + global_descriptors = extract_features.main(retrieval_conf, images, outputs) + pairs_from_retrieval.main( + global_descriptors, loc_pairs, args.num_loc, + query_prefix="query", db_model=reference_sfm, + ) + loc_matches = match_features.main( + matcher_conf, loc_pairs, feature_conf["output"], outputs + ) + localize_sfm.main( + reference_sfm, + dataset / "queries/*_time_queries_with_intrinsics.txt", + loc_pairs, + features, + loc_matches, + results, + covisibility_clustering=False, + ) + print(f"\n[{args.tag}] DONE -> {results}") + + +if __name__ == "__main__": + p = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + p.add_argument("--dataset", type=Path, default="datasets/aachen_v1.1") + p.add_argument("--outputs", type=Path, required=True) + p.add_argument("--checkpoint", type=str, required=True) + p.add_argument("--matcher_name", type=str, default="latemambaglue", + choices=["latemambaglue", "mambaglue"]) + p.add_argument("--n_cross_layers", type=int, default=None, + help="latemambaglue only; omit for mambaglue") + p.add_argument("--n_layers", type=int, default=None, + help="omit to use the matcher's own default") + p.add_argument("--filter_threshold", type=float, default=0.1) + p.add_argument("--tag", type=str, required=True) + p.add_argument("--num_covis", type=int, default=20) + p.add_argument("--num_loc", type=int, default=50) + run(p.parse_args()) \ No newline at end of file