Skip to content

Add FlexGlue feature matcher - #4908

Merged
sarlinpe merged 3 commits into
mainfrom
sarlinpe/flexglue
Nov 1, 2026
Merged

sarlinpe merged 3 commits into
mainfrom
sarlinpe/flexglue

Conversation

@sarlinpe

@sarlinpe sarlinpe commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR adds FlexGlue as a new feature matcher (FeatureMatcherType::FLEXGLUE), running ONNX models with ONNX Runtime.

  • FlexGlue matches the keypoints of any extractor through dense image features.
    • Each image is encoded once into a stride-8 feature map by a DINOv3-ConvNeXt descriptor at 512×512.
    • A transformer refines the feature maps of both images jointly.
    • Each keypoint regresses its correspondence in the other image, and keypoints are matched by mutual nearest neighbours.
  • FlexGlue also predicts a covisibility score for each pair. --FlexGlueMatching.min_covisibility rejects all matches of pairs below this score, which suppresses look-alike pairs (doppelgangers) without any ground truth.
  • The models stop early on pairs that are confidently not covisible (--FlexGlueMatching.early_stop_decay, see Early stopping). This was FlexGlue: early stopping of non-covisible pairs #4916, now folded into this PR.

Builds on #4921 (propagation of matching errors, needed since FlexGlue reads image files during matching), #4907 (max_num_features, used for the evaluation below), and #4924 (LoMa bf16 without capability probes, whose SelectONNXCudaDevice this PR uses), which are all merged.

Key changes

  1. Matcher (feature/flexglue.{h,cc}):

    • FlexGlueMatchingOptions: image_path (required, checked to exist; the matcher commands have no global --image_path, so project files must set FlexGlueMatching.image_path), filter_threshold (0.1), max_distance_px (3), min_covisibility (0.5; 0 disables the pair rejection), early_stop_decay (0.5; 1 disables early stopping), feature_cache_size (128), the model paths, and use_fp16 (default on).
    • CreateFlexGlueFeatureMatcher(). On CUDA GPUs with compute capability ≥ 7.0 (checked with CudaDeviceHasComputeCapability(), which replaces CudaDeviceSupportsBf16() from Fix LoMa bf16 fallback on CUDA by loading it like any other model #4924 and is shared with the bf16 LoMa gate) it uses the matcher with float16 transformer layers, decided up front as for the bf16 LoMa models in Fix LoMa bf16 fallback on CUDA by loading it like any other model #4924, and falls back to float32 if the fp16 model fails to initialize. On older GPUs and on the CPU it uses float32.
    • Since the dense features depend on the pixels, the matcher reads the images during matching from image_path.
      • To support this, FeatureMatcher::Image now carries the image name.
      • Images are resized with a bilinear resize identical to PyTorch.
      • Dense features are cached in a colmap::LRUCache keyed by image name, so each image is decoded and described once, also for direct pycolmap FeatureMatcher.match calls.
    • The thresholds are inputs of the ONNX graph: matches are filtered and pairs rejected inside the model.
    • Per-pair covisibility scores are logged with GLOG_vmodule=flexglue=2.
    • On the CPU, a single matching worker runs the models with all num_threads, instead of one copy of the models and cache per thread.
    • Descriptors are not required: the matching workers only check and load them if the matcher uses them (FeatureMatchingOptions::UsesDescriptors()), so databases with keypoints only (e.g., written by pycolmap or imported) are matched too.
    • Guided matching is not supported and is rejected by FeatureMatchingOptions::Check() before any model is loaded.
  2. Models: hosted in the FlexGlue v0.1 release and downloaded and verified (sha256) automatically:

    • flexglue_descriptor.onnx
    • flexglue_matcher_early_stop.onnx
    • flexglue_matcher_early_stop_fp16.onnx

    They are exported with onnx/export.py from ONNX export: optional early stopping of non-covisible pairs cvg/FlexGlue#7 (on Cannot import custom features #5 and Panoramic Camera model #6): fused attention and GELU operators, and ONNX If nodes for early stopping after layers 2, 4, 6 and 8. early_stop_decay is a required input. FlexGlue's onnx/check.py (run in its CI) checks them against the PyTorch reference: fp32 matches exactly, fp16 within at most 2 matches per pair; with early stopping, both stop at the same layers.

  3. automatic_reconstructor --feature flexglue preset:

    • Extracts the same DaD keypoints as the LoMa presets, using the lightweight LOMA_B128 extractor, whose descriptors FlexGlue ignores.
    • Matches them with FlexGlue, with image_path set automatically.
  4. Options, pycolmap, GUI and docs:

    • --FlexGlueMatching.* options.
    • pycolmap.FlexGlueMatchingOptions and FeatureMatchingOptions.flexglue.
    • A FlexGlue tab in the feature matching widget.
    • A section in doc/features.rst.
  5. Error handling:

  6. Tests:

    • feature/flexglue_test: option checks, matching of a translated image pair, rejection of all matches by min_covisibility and identical matches with and without early stopping (both behind COLMAP_TEST_HEAVY, since they load a second model), empty keypoints, missing images, and unsupported guided matching.
    • util/cuda_test: CudaDeviceHasComputeCapability.
    • The FlexGlue tests pass no descriptors.
colmap exhaustive_matcher --database_path db.db --FeatureMatching.type FLEXGLUE \
    --FlexGlueMatching.image_path images/  # min_covisibility 0.5 by default
colmap automatic_reconstructor --image_path images/ --workspace_path out/ --feature flexglue

Evaluation

Note

Provenance of the numbers below (AUC tables and matching speed): they were measured with the first export of the matchers (cvg/FlexGlue#5: no fused GELU, no early stopping), i.e., with early stopping disabled. The shipped early-stop models with early_stop_decay = 1 give the same matches in fp32 and differ by at most 2 fp16 matches per pair (3245 vs. 3240 matches on the 7 FlexGlue asset images), so these results hold for early_stop_decay = 1. The effect of the default early_stop_decay = 0.5 is in Early stopping.

Settings

  • Benchmark: benchmark/reconstruction/evaluate.py on ETH3D DSLR (25 scenes, 898 images), with exhaustive matching, GT camera priors, the incremental mapper and --quality high.
  • Keypoints: both methods use the same DaD keypoints, capped with --max_num_features {2048, 4096, 8192}. Every image gets exactly that many keypoints.
    • LoMa = DeDoDe-G descriptors + LoMa-B matcher.
    • FlexGlue = fp16 matcher.
  • Metric: scene-averaged AUC of the relative pose error (%), mean ± std over seeds 1–3. Seeds re-run verification and mapping on the same raw matches.
  • Pair filters:
    • GT covis: the benchmark default. Verified pairs that share fewer than 5 GT 3D points are deleted before mapping, which acts as an oracle against wrong pairs.
    • none: --no-filter_covisibility.
    • FlexGlue covis: --no-filter_covisibility with --FlexGlueMatching.min_covisibility 0.8, so no GT is used. It was evaluated by emptying the matches of pairs whose logged covisibility score is below 0.8. On a full run, this is bit-identical to matching with min_covisibility=0.8.

Results (all 25 scenes)

#kpts Matcher Pair filter [email protected]° AUC@1° AUC@5° AUC@10°
2048 LoMa GT covis 68.46 ± 0.10 80.69 ± 0.09 93.14 ± 0.02 94.95 ± 0.01
2048 FlexGlue GT covis 68.29 ± 0.27 80.33 ± 0.35 92.72 ± 0.52 94.56 ± 0.52
2048 LoMa none 64.17 ± 0.61 75.49 ± 0.64 87.67 ± 0.57 89.84 ± 0.55
2048 FlexGlue none 65.35 ± 0.05 77.60 ± 0.33 91.25 ± 0.11 93.35 ± 0.03
2048 FlexGlue FlexGlue covis 70.16 ± 0.15 83.00 ± 0.16 96.07 ± 0.15 97.99 ± 0.11
4096 LoMa GT covis 71.43 ± 0.30 82.51 ± 0.23 93.68 ± 0.23 95.31 ± 0.23
4096 FlexGlue GT covis 70.97 ± 0.13 82.16 ± 0.04 93.45 ± 0.01 95.11 ± 0.01
4096 LoMa none 67.21 ± 0.38 78.34 ± 0.65 90.22 ± 0.43 92.03 ± 0.42
4096 FlexGlue none 65.78 ± 0.24 77.77 ± 0.20 90.51 ± 0.07 92.49 ± 0.03
4096 FlexGlue FlexGlue covis 72.19 ± 0.15 84.14 ± 0.10 96.22 ± 0.03 98.05 ± 0.01
8192 LoMa GT covis 71.80 ± 0.12 82.63 ± 0.07 93.60 ± 0.03 95.19 ± 0.02
8192 FlexGlue GT covis 71.46 ± 0.15 82.45 ± 0.08 93.55 ± 0.01 95.16 ± 0.00
8192 LoMa none 67.66 ± 0.97 78.52 ± 1.52 89.68 ± 2.13 91.33 ± 2.17
8192 FlexGlue none 65.67 ± 0.23 77.19 ± 0.45 89.63 ± 0.90 91.71 ± 0.71
8192 FlexGlue FlexGlue covis 72.43 ± 0.18 84.26 ± 0.12 96.30 ± 0.10 98.09 ± 0.08
  • Matching quality (GT covis oracle):
    • LoMa and FlexGlue are on par. LoMa is ahead by 0.2–0.5 [email protected]°, and AUC@5°/@10° are within 0.1 at 4k/8k.
    • Both gain about +3 [email protected]° from 2k to 4k, and only +0.4 from 4k to 8k.
  • No pair filter:
    • Both methods lose 3–6 [email protected]° due to wrong (look-alike) pairs.
    • FlexGlue is more robust than LoMa at 2k but less robust at 4k/8k, where more keypoints give more matches on look-alike pairs.
  • FlexGlue covisibility filter:
    • Rejecting pairs with a covisibility score below 0.8 gives the best accuracy of all configurations at every number of keypoints, with no GT:

      • +4.8 / +6.4 / +6.8 [email protected]° over FlexGlue without a filter;
      • +6.0 / +5.0 / +4.8 over LoMa without a filter.
    • It is also above both methods with the GT covis oracle, by 1.7 / 0.8 / 0.6 [email protected]° over LoMa and by 2.5–3 points at AUC@5°/@10°.

    • With min_covisibility=0.5, the default, the gain is slightly smaller, but still well above no filter:

      #kpts [email protected]° AUC@1° AUC@5° AUC@10°
      2048 69.49 ± 1.09 82.13 ± 1.12 95.19 ± 1.07 97.29 ± 0.87
      4096 71.18 ± 0.21 83.43 ± 0.10 96.05 ± 0.01 97.96 ± 0.02

IMC 2023: choice of the default min_covisibility

ETH3D alone is a single, high-overlap DSLR benchmark, so the default threshold was chosen after a second evaluation on IMC 2023:

  • 7 scenes, 261 internet photos, with real look-alike pairs.
  • Uncalibrated, with no GT covisibility filtering, as is standard for IMC.
  • 2048 DaD keypoints, seeds 1–3.
  • The 0.5 / 0.8 runs re-match with the real option.
Config [email protected]° @1° @5° @10° Registered (of 261) Pairs with matches
LoMa 25.53 ± 1.09 46.16 ± 1.85 76.22 ± 1.51 82.27 ± 0.51 257 9082 / 10935
FlexGlue, min_covisibility=0 25.47 ± 2.02 46.35 ± 2.25 77.84 ± 2.06 84.70 ± 1.29 259 6931 / 10935
FlexGlue, min_covisibility=0.5 32.94 ± 0.59 54.50 ± 0.38 81.26 ± 0.24 85.61 ± 0.21 255 5247 / 10935
FlexGlue, min_covisibility=0.8 33.07 ± 0.86 54.33 ± 0.51 80.79 ± 0.41 85.00 ± 0.39 254 3847 / 10935

Per-scene AUC@5° (mean over seeds):

Config bike chairs cyprus dioscuri fountain kyiv-puppet-theater wall
LoMa 87.8 88.1 81.1 74.7 95.6 39.0 67.3
FlexGlue, min_covisibility=0 87.6 87.5 83.1 84.6 95.5 45.6 61.1
FlexGlue, min_covisibility=0.5 87.6 87.6 84.2 86.2 95.5 52.5 75.3
FlexGlue, min_covisibility=0.8 87.6 87.4 81.0 87.6 95.5 51.4 75.0
  • Without rejection, FlexGlue and LoMa are on par at [email protected]° (25.5). FlexGlue is better at AUC@5°/@10° (+1.6 / +2.4), mostly on dioscuri.
  • Rejecting pairs helps a lot on IMC too: +7.5 [email protected]° for both thresholds over no rejection, mostly on wall, kyiv-puppet-theater and dioscuri. That is +7.4 [email protected]° and +5.0 AUC@5° over LoMa.
  • 0.5 and 0.8 are on par: within one seed std at [email protected]°. 0.5 is slightly better at AUC@5°/@10° (+0.5 / +0.6) and registers one more image.
  • 0.8 loses accuracy on cyprus (81.0 vs. 84.2 at AUC@5°), where it rejects true pairs.
  • The default is therefore min_covisibility=0.5. It gets most of the gain of 0.8 on ETH3D (above) and rejects fewer true pairs: it keeps 95% of the GT-covisible ETH3D pairs, vs. 92% for 0.8.

Matching speed

Measured on an otherwise idle machine with an A100 and 12 CPU cores:

  • Exhaustive matching without geometric verification on 3 ETH3D DSLR scenes (delivery_area, terrains, office; 112 images, 2132 pairs), with the keypoints and descriptors of the evaluation.
  • ms/pair = total matching wall time / number of pairs, including model loading and, for FlexGlue, image decoding and the dense descriptor.
  • Each configuration was run twice; the runs agree within 0.5%.
#kpts LoMa-B FlexGlue fp16 FlexGlue: matcher network only FlexGlue vs. LoMa
2048 27.8 ms 55.8 ms 33.6 ms 2.0× slower
4096 64.4 ms 59.1 ms 36.9 ms 1.09× faster
8192 188.8 ms 68.2 ms 45.7 ms 2.8× faster
  • LoMa attends over the keypoints, so its cost grows superlinearly with their number.

  • FlexGlue's transformer runs on 2×4096 dense tokens at a fixed resolution. Keypoints only enter the correspondence read-out and the mutual nearest-neighbour step, so its cost barely depends on the number of keypoints.

  • Breakdown of FlexGlue's 55.8 ms/pair at 2k:

    Stage Per image Amortized per pair
    Matcher network – 33.6 ms
    JPEG decoding (24 MP) 277 ms 14.6 ms
    Dense descriptor 45 ms 2.4 ms
    Other (model loading, database I/O, host↔device copies) – ~5 ms
  • LoMa's descriptors are computed during extraction and are not included above. In comparison, the LOMA_B128 extraction used by the FlexGlue preset takes about 30% less time than LOMA_B.

  • fp32 and CPU: on CUDA, the fp32 matcher takes ~97 ms/pair at 2k. The CPU is supported but slow (~5 s/pair), so a GPU is strongly recommended.

Early stopping

The models stop early on pairs that are confidently not covisible:

  • After transformer layers 2, 4, 6 and 8 (of 9), a pair is rejected without running the remaining layers and the readout if its covisibility score is below min_covisibility * (1 - early_stop_decay)^(9 - layer).
  • FlexGlueMatching.early_stop_decay defaults to 0.5. 1 disables it, as does min_covisibility = 0.
  • The checks are ONNX If nodes built on the exported graph (ONNX export: optional early stopping of non-covisible pairs cvg/FlexGlue#7).

Note

Baseline: the timings in this section were measured with #4915 (GPU feature cache) on top. #4915 saves about 2 ms per pair whether or not a pair stops, so it shifts both columns equally.

Do the ONNX If nodes actually skip the remaining blocks? Yes.

fp16, N = 2048, ONNX Runtime 1.23, A100, device I/O:

  • A pair that stops after layer 1 takes 3.5 ms instead of 32 ms. After layer 2, it takes 6.8 ms.
  • Each layer costs about 3.3 ms, and nothing after the stop runs.

Costs

  • Pairs that run all layers:
    • GPU time: +0.3 ms per pair for the 4 checks (the covisibility heads).
    • Wall time: about +0.2 ms per check, because ONNX Runtime needs each condition on the host and synchronizes.
    • The fused GELU of the new models partly offsets this. Pairs that never stop take +0.6 ms at 2k (54.5 vs. 53.8 ms/pair, +1%) and +0.3 ms at 8k.
  • GPU memory: the subgraphs add about 0.55 GB of ONNX Runtime arena memory at 2k (2.2 → 2.8 GB) and practically none at 8k (5.8 GB).
  • IMC 2023 at min_covisibility = 0.5: few pairs stop (4.4%), so the matcher time grows by 2%.

How much do we gain?

Setup:

  • 3 ETH3D DSLR scenes, 2132 exhaustive pairs, on an idle A100, with FlexGlue: cache the dense features on the GPU #4915.
  • Total matching time / pairs, including image decoding and the descriptor.
  • For each scene, the fastest of 3 (2k) or 2 (8k) repeats.
  • "No early stop" is early_stop_decay=1.
#kpts min_covisibility No early stop early_stop_decay=0.5 Pairs with matches
2048 0.5 (default) 54.5 ms 51.3 ms (−5.7%) identical (1101)
2048 0.8 54.3 ms 50.3 ms (−7.3%) identical (1007)
8192 0.5 (default) 66.6 ms 60.5 ms (−9.2%) identical (1247)
8192 0.8 66.6 ms 58.0 ms (−12.9%) identical (1083)

The gain is larger at 8k, because the readout that a stop skips costs more with more keypoints.

Over all exhaustive pairs (per-layer covisibility scores of every pair, with the time per layer measured above):

Data min_covisibility Pairs stopped early Rejected pairs that would pass the final threshold Matcher time
ETH3D DSLR (22569 pairs) 0.5 35.0% 0.18% (6 of 10288 GT-covisible pairs) −9%
ETH3D DSLR 0.8 42.4% 0.08% (4 GT-covisible) −13%
IMC 2023 (10935 pairs) 0.5 4.4% 0.02% +2%
IMC 2023 0.8 13.8% 0% 0%
  • The gain depends on the share of non-covisible pairs. In IMC's covisible pairs, the scores only drop in the late layers.
  • Lower decays stop more often but start rejecting true pairs:
    • decay 0.3: −18% to −23% matcher time on ETH3D, losing 1.8% of the kept pairs;
    • decay 0.2: about −30%, losing 4–5%.
    • decay 0.5 is a safe default.

Future improvements to make FlexGlue faster

Status of these items:

Together, early stopping and #4915 make matching 8.3% faster at 2k keypoints (56.0 → 51.3 ms/pair) and 11.7% faster at 8k (68.4 → 60.5 ms/pair) than the first version of this PR, with the default options.

  1. Hide image decoding (~15 ms/pair, about 25% of the matching time at 2k).
    • Decode the next images in a prefetch thread, overlapping with matching.
    • And/or decode JPEGs at reduced resolution (libjpeg DCT scaling), since the descriptor only needs 512×512.
  2. Keep the dense features on the GPU (about −10 ms/pair).
    • Use ONNX Runtime IOBinding with a device-resident feature cache, instead of copying the two 4 MB feature maps host→device for every pair.
  3. Early stopping for non-covisible pairs.
    • Export the per-layer covisibility heads, e.g. by splitting the matcher into per-layer sessions, and stop after a few layers when the score is low.
    • About 45% of exhaustive ETH3D pairs score below 0.5.
    • The PyTorch implementation already supports this (early_stop_decay).
  4. Batch several pairs per ONNX Runtime call and/or use ORT CUDA graphs with static shapes, i.e. keypoints padded to fixed lengths. This removes the launch overhead of ~1700 kernels per pair.
  5. Fold the Linear biases into fused MatMul+Add patterns (FusedMatMul), to drop the remaining extra GEMM kernels.
  6. Optionally export variants with fewer transformer layers (n_layers), for a linear speed/accuracy trade-off.

@sarlinpe
sarlinpe added this pull request to stack #4909 October 27, 2026 22:22
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue branch 2 times, most recently from 512e64a to b3a3b57 Compare October 28, 2026 02:15
@sarlinpe
sarlinpe removed this pull request from stack #4909 October 28, 2026 07:45
@sarlinpe
sarlinpe added this pull request to stack #4917 October 28, 2026 07:46
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue branch 2 times, most recently from 109fed5 to 98af90c Compare October 29, 2026 05:56
@sarlinpe
sarlinpe removed this pull request from stack #4917 October 29, 2026 05:57
@sarlinpe
sarlinpe changed the base branch from sarlinpe/max-num-features to sarlinpe/matching-error-propagation October 29, 2026 05:57
@sarlinpe
sarlinpe added this pull request to stack #4922 October 29, 2026 05:57
@sarlinpe
sarlinpe removed this pull request from stack #4922 October 29, 2026 23:28
@sarlinpe
sarlinpe added this pull request to stack #4925 October 29, 2026 23:28
ahojnnes pushed a commit that referenced this pull request Oct 30, 2026
)

Fixes #4787. Supersedes #4895.

## Problem
With `--LomaMatching.use_bf16 1` on CUDA, the bf16 LoMa matcher always
fails to initialize and falls back to fp32:

```
Failed to initialize the bf16 LoMa matcher (This session contains graph nodes that are assigned to the default CPU EP, but fallback to CPU EP has been explicitly disabled by the user.); falling back to fp32
```

The bf16 models are loaded as capability probes, which set
`session.disable_cpu_ep_fallback`. But ONNX Runtime deliberately runs
small shape computations on the CPU, whatever the precision of the
model: its CUDA `Shape`/`Size` kernels write their output to host
memory, and `GetCpuPreferredNodes` then moves their int64 consumers
(`Gather`, `Unsqueeze`, `Concat`, `Cast`, `Reshape`, ...) to the CPU EP.
Tensors in bf16 or fp16 are never moved. The dynamic-shape matcher
graphs contain such nodes, so the probe always rejects them. The fp32
models are not loaded as probes, so they run with the same CPU nodes.

## Fix
Load the bf16 models like any other model, and decide up front whether
to use them:
- The bf16 models are only used with the CUDA execution provider, on
GPUs with compute capability 8.0 (Ampere) or newer. Otherwise, the fp32
models are loaded directly, without first downloading and loading the
bf16 models.
- On older GPUs, creating a bf16 session can succeed while inference
fails, which the fallback on initialization failures would not catch.
- On the CPU, `use_bf16` is now a no-op instead of trying to load the
bf16 models.
- The bf16 models are loaded as regular `ONNXModel`s, with a fallback to
fp32 if initialization throws an `Ort::Exception`. This removes
`ONNXModel`'s `is_capability_probe`, whose only user was LoMa.
- New `CudaDeviceSupportsBf16()` queries the device. It returns false if
the device cannot be queried, and on HIP builds.
- New `SelectONNXCudaDevice()` resolves `gpu_index` to the device that
`ONNXModel` runs on, shared with the bf16 check.
- Updated the option docs (C++, pycolmap, `doc/features.rst`).

This is the same pattern as the fp16 FlexGlue matcher in #4908: reduced
precision only on CUDA, a regular session, and a fallback to fp32 on
initialization failures.

### Comparison with #4895
#4895 keeps the strict probe and instead inspects the EP graph
assignment (ORT >= 1.24) against an allowlist of CPU operators. This PR
avoids:
- the allowlist, which is brittle: which nodes ORT moves to the CPU
depends on data flow, not on the operator type. For example, FlexGlue's
matchers run `Reshape` on the CPU, which the allowlist rejects. Any
rejected node silently falls back to fp32 again.
- the `ORT_API_VERSION >= 24` gating; the fix also works with older ORT.
- downloading and loading the bf16 models on GPUs without bf16 support
before rejecting them.
- ~80 lines of hand-encoded ONNX test models.

Removing the strict probe barely weakens the check: the bf16 tensors
themselves are never moved to the CPU, and the CPU EP has almost no bf16
compute kernels, so a bf16 op without a CUDA kernel still fails at
initialization and falls back to fp32.

## Tests
- `util/cuda_test`: `CudaDeviceSupportsBf16` returns false for invalid
devices and, with a GPU, leaves no CUDA error.
- `util/onnx_test`: `SelectONNXCudaDevice`, and an invalid model throws
the `Ort::Exception` that the LoMa fallback catches.
- `feature/loma_test`, `util/onnx_test`, `util/cuda_test` (also with
`CUDA_VISIBLE_DEVICES=""`) pass locally (CUDA + ONNX build).
- Not yet run end-to-end with the CUDA EP: ORT 1.30's CUDA EP does not
load on my machine (older system CUDA runtime). The bf16 LoMa-B matcher
with CPU shape nodes was validated in #4895 (64/64 and 128/128
self-matches on an RTX 3060).

**Stack:** the FlexGlue stack builds on this PR: #4907, #4921, #4908,
#4915. #4908 selects its fp16 matcher the same way, with a new
`CudaDeviceHasTensorCores()` (compute capability ≥ 7.0) next to
`CudaDeviceSupportsBf16()`. #4915 uses `SelectONNXCudaDevice()` for the
GPU feature cache.

@ahojnnes ahojnnes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline comments on the remaining review findings. The matcher core, fp16 fallback, and single-worker CPU change look sound; these are the fixes and clarifications still needed. Landing also depends on the stack (#4921, #4907) and CI.

Comment thread src/colmap/feature/flexglue.cc Resolved
Comment thread src/colmap/feature/flexglue.cc Resolved
Comment thread src/colmap/feature/flexglue.cc Resolved
Comment thread src/colmap/feature/flexglue_test.cc Resolved
Comment thread doc/features.rst Resolved
Base automatically changed from sarlinpe/matching-error-propagation to main October 30, 2026 12:09
ahojnnes pushed a commit that referenced this pull request Oct 30, 2026
…4907)

## Summary

Adds a `max_num_features` option to `automatic_reconstructor` and to the
reconstruction benchmark, to evaluate a feature type with a different
number of keypoints than its default. The first use is evaluating
learned matchers at 2k/4k/8k keypoints.

### Changes
- **`AutomaticReconstructionController::Options::max_num_features`**
(default `-1`, off).
- When positive, it overrides `max_num_features` of the SIFT, ALIKED and
LoMa extraction options.
- It is applied after the quality preset, which otherwise sets the
number of SIFT features.
- **`colmap automatic_reconstructor --max_num_features N`.**
- **Benchmark:** `benchmark/reconstruction/evaluate.py
--max_num_features N` forwards the option to every
`automatic_reconstructor` call (`evaluation/utils.py`).

### Validation
- With `--feature loma --max_num_features {2048, 4096, 8192}`, all 898
images of ETH3D DSLR have exactly 2048 / 4096 / 8192 keypoints.
- With the option unset, behaviour is unchanged.
- `controllers/` tests pass.

Part of the FlexGlue stack: #4924, then this PR, #4921, #4908 and #4915.
#4908 uses it for its evaluation.
ahojnnes pushed a commit that referenced this pull request Oct 30, 2026
…4921)

Stacked on #4907. This PR comes before #4908 (FlexGlue matcher), which
reads image files and loads large models while matching, and therefore
needs errors to propagate.

## Problem
An exception thrown in a feature matching worker escapes its `Run()`,
and `Thread::RunFunc` does not catch it, so the process calls
`std::terminate`. For example:
- an unreadable or missing image (FlexGlue, #4908);
- a matcher model that fails to download or load;
- unexpected features, e.g. descriptors of the wrong dimension;
- a corrupt database, e.g. a keypoint blob in an unsupported format,
read by the verifier.

`std::terminate` stops `colmap exhaustive_matcher`, but it also kills
the Python interpreter in pycolmap and the whole GUI, with no exception
and no clean shutdown.

## Changes
- **`FeatureMatcherWorker` and the verifier worker** catch the exception
of a pair and store it in the new `FeatureMatcherData::error`.
- Each worker's per-pair body, `FeatureMatcherWorker::MatchPair()` and
`VerifierWorker::Verify()`, runs entirely inside the `try`. A throw
therefore never ends a worker thread, so the controller never waits
forever for that pair's output.
  - Jobs with an error pass through the remaining workers untouched.
- **`FeatureMatcherController::Match()`** rethrows the first error.
- It first drops the pairs queued for matching. `JobQueue::Clear()` now
returns how many jobs it cleared and wakes waiters.
- Jobs queued for verification are kept, since some of them carry
existing matches that were already deleted from the database.
- It then waits for the pairs in flight and writes their results. The
run therefore stops quickly, and the controller stays usable afterwards.
- **`GeometricVerifierController::Verify()`** rethrows the first error
after processing all pairs, whose matches stay in the database.
- **`FeatureMatcherController::Setup()`** rethrows the error of a worker
that failed to set up, e.g., because its matcher model cannot be loaded.
- The worker has finished, so its `Wait()` returns at once and rethrows.
  - The destructor's later `Wait()` then no longer throws.
- **`Thread`** captures exceptions of `Run()`, logs them (`Thread
failed: …`), and `Wait()` rethrows them, once, in the waiting thread.
- This is needed because the controllers that call
`FeatureMatcherController` are themselves threads, run with
`Start()`/`Wait()` by the CLI and with `PyWait` by pycolmap.
- A thread that fails before signaling its setup unblocks
`CheckValidSetup()` instead of blocking it forever.
- **Result:**
  - pycolmap raises a Python exception.
  - The GUI (`ThreadControlWidget`) shows the error in a dialog.
  - The CLI stops with the error, as before.

**Behavior change:** a thread whose owner never calls `Wait()` now logs
the exception instead of terminating the process. The feature matching
controllers wait on their failed workers in `Setup()`, so their
destructors do not rethrow.

## Tests
- `FeatureMatcherController.MatchPropagatesMatcherErrors`:
- SIFT descriptors of the wrong dimension make the matcher throw for
every pair with one image.
- The error message comes out of `Match()`, and no matches are written
for those pairs.
  - The controller then still matches the other pairs.
- `FeatureMatcherController.SetupPropagatesMatcherCreationErrors`: a
LightGlue model path that does not exist makes `Setup()` throw, and the
controller destructs cleanly.
- `GeometricVerifierController.VerifyPropagatesErrors`: keypoints in an
unsupported format make the verifier throw. `Verify()` rethrows instead
of hanging, and the other pairs are verified.
- `Thread.WaitRethrowsException`.
- **Mutation check:** without the `Setup()` fix the creation test
terminates, and without the verifier `try` the verifier test hangs.
- `util/threading_test`, `controllers/feature_matching_utils_test` and
`controllers/feature_matching_test` pass. With #4908 on top:
- an empty image folder raises `Failed to read image: …` to the caller;
  - nonexistent model paths raise the ONNX Runtime error to the caller;
  - neither calls `std::terminate`.

This addresses review comments on the FlexGlue integration.
@sarlinpe sarlinpe added the full-ci Run Windows and remaining pycolmap CI label Oct 30, 2026
@sarlinpe
sarlinpe requested a review from ahojnnes October 30, 2026 22:20
@sarlinpe

sarlinpe commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Final end-to-end evaluation at stack head (#4908 + #4915)

Full evaluation of the complete stack at head (801552b5: #4908 + #4915 GPU dense-feature caching + shipped v0.1 early-stop ONNX models) across all scenes of ETH3D DSLR and IMC 2023 at 2048 and 4096 keypoints.

Setup

  • Hardware: 1× NVIDIA A100-SXM4-40GB GPU, 12 CPU cores.
  • Pipeline: benchmark/reconstruction/evaluate.py (--mapper incremental --quality high), exhaustive matching, DaD keypoints (--max_num_features {2048, 4096}), seeds 1–3 (mean ± std).
    • ETH3D DSLR: all 25 scenes (898 images, 22,569 exhaustive pairs), GT camera priors.
    • IMC 2023: all 7 scenes (261 images, 10,935 exhaustive pairs; heritage/dioscuri accounts for 139 images / 9,591 pairs), uncalibrated.
  • Configurations:
    • LoMa: DeDoDe-G + LOMA_B, with GT covis (--filter_covisibility) or none (--no-filter_covisibility).
    • FlexGlue (GT covis / none): fp16 matcher with min_covisibility = 0 (early stopping inactive), evaluated with --filter_covisibility or --no-filter_covisibility.
    • FlexGlue (FlexGlue covis): --no-filter_covisibility with the PR defaults (min_covisibility = 0.5, early_stop_decay = 0.5; no GT used).
  • Timings:
    • Matching (ms/pair): total exhaustive_matcher wall time / num_pairs over all scenes, run one scene at a time on an idle machine with --FeatureMatching.skip_geometric_verification 1. Includes per-scene model loading and, for FlexGlue, image decoding and dense descriptor extraction (LoMa's descriptors are computed during feature extraction and not included).
    • Mapper (s/scene): incremental mapper wall time per scene (--extraction 0 --matching 0 --sparse 1 --dense 0, excluding feature extraction, matching, and two-view geometric verification), averaged over scenes and seeds 1–3 (--num_parallel_scenes 3 --num_threads 12, i.e. up to 3 scenes in parallel × 4 threads; multiply by 25 on ETH3D or 7 on IMC 2023 for total dataset mapper time).

ETH3D DSLR (25 scenes, 898 images, 22,569 pairs)

#kpts Matcher Pair filter [email protected]° AUC@1° AUC@5° AUC@10° Reg. Matching (ms/pair) Mapper (s/scene)
2048 LoMa GT covis 68.37 ± 0.09 80.65 ± 0.10 93.10 ± 0.09 94.91 ± 0.07 894 27.5 55.5
2048 LoMa none 64.14 ± 0.83 75.88 ± 0.91 88.58 ± 0.96 90.64 ± 0.92 898 27.5 66.4
2048 FlexGlue GT covis 68.66 ± 0.21 80.72 ± 0.12 93.12 ± 0.04 94.94 ± 0.03 894 49.5 43.5
2048 FlexGlue none 65.02 ± 0.39 77.05 ± 0.39 90.48 ± 0.48 92.57 ± 0.51 898 49.5 54.2
2048 FlexGlue FlexGlue covis 69.99 ± 0.36 82.81 ± 0.31 96.06 ± 0.09 98.00 ± 0.05 898 46.0 45.7
4096 LoMa GT covis 71.50 ± 0.02 82.46 ± 0.04 93.51 ± 0.03 95.13 ± 0.02 894 64.3 85.2
4096 LoMa none 67.11 ± 0.32 78.34 ± 0.52 90.33 ± 0.39 92.16 ± 0.41 898 64.3 111.1
4096 FlexGlue GT covis 70.82 ± 0.14 82.07 ± 0.10 93.43 ± 0.02 95.10 ± 0.01 894 52.2 100.8
4096 FlexGlue none 66.12 ± 0.22 78.02 ± 0.14 90.61 ± 0.08 92.59 ± 0.09 898 52.2 114.0
4096 FlexGlue FlexGlue covis 71.56 ± 0.50 83.70 ± 0.28 96.12 ± 0.04 97.99 ± 0.02 898 47.2 84.3

IMC 2023 (7 scenes, 261 images, 10,935 pairs)

#kpts Matcher Pair filter [email protected]° AUC@1° AUC@5° AUC@10° Reg. Matching (ms/pair) Mapper (s/scene)
2048 LoMa GT covis 25.96 ± 1.98 46.28 ± 1.85 73.38 ± 1.10 78.45 ± 1.10 249 27.0 279.6
2048 LoMa none 23.74 ± 3.54 43.78 ± 4.03 74.46 ± 4.01 80.77 ± 3.32 256 27.0 154.9
2048 FlexGlue GT covis 28.00 ± 3.29 49.04 ± 3.66 79.10 ± 1.92 84.32 ± 1.47 258 37.2 100.0
2048 FlexGlue none 28.00 ± 1.98 49.64 ± 1.47 79.16 ± 0.63 84.21 ± 1.24 259 37.2 111.3
2048 FlexGlue FlexGlue covis 32.28 ± 1.16 54.03 ± 0.88 80.76 ± 0.58 85.07 ± 0.58 255 37.0 92.2
4096 LoMa GT covis 33.16 ± 1.88 53.02 ± 0.89 78.31 ± 0.63 82.40 ± 1.06 254 63.4 158.2
4096 LoMa none 30.49 ± 2.48 51.99 ± 1.96 79.59 ± 1.20 83.73 ± 1.16 258 63.4 162.6
4096 FlexGlue GT covis 27.75 ± 3.58 48.28 ± 4.14 77.31 ± 3.51 82.96 ± 2.40 261 40.7 243.9
4096 FlexGlue none 28.14 ± 3.20 49.66 ± 3.51 79.26 ± 1.56 84.34 ± 0.93 261 40.7 208.0
4096 FlexGlue FlexGlue covis 31.82 ± 0.47 54.26 ± 0.51 82.85 ± 0.78 87.58 ± 1.22 259 40.1 202.4

Takeaways

  • Accuracy (FlexGlue covis): Without using any ground truth, FlexGlue's default covisibility rejection (min_covisibility = 0.5, early_stop_decay = 0.5) gives the highest pose accuracy across both datasets and keypoint counts (except IMC 2023 4k [email protected]°, where LoMa, GT covis is +1.3 pts higher while FlexGlue covis leads by +1.2 / +4.5 / +5.2 pts at AUC@1°/@5°/@10°).
    • Over FlexGlue, none: +5.0 / +5.4 [email protected]° and +5.6 / +5.5 AUC@5° on ETH3D (2k / 4k); +4.3 / +3.7 [email protected]° and +1.6 / +3.6 AUC@5° on IMC 2023.
    • Over LoMa, none: +5.9 / +4.5 [email protected]° and +7.5 / +5.8 AUC@5° on ETH3D (2k / 4k); +8.5 / +1.3 [email protected]° and +6.3 / +3.3 AUC@5° on IMC 2023.
    • It also outperforms the GT covis oracle on both datasets, where sparse GT tracks drop valid low-overlap pairs (e.g. 894 vs. 898 registered images on ETH3D, and incomplete GT tracks on IMC chairs/dioscuri).
  • Matching speed & scaling:
    • Doubling keypoints from 2048 → 4096 barely affects FlexGlue (46.0 → 47.2 ms/pair on ETH3D, 37.0 → 40.1 ms/pair on IMC 2023), because the dense descriptor and transformer operate on fixed-resolution feature maps. LoMa scales superlinearly (27.5 → 64.3 ms/pair on ETH3D, 27.0 → 63.4 ms/pair on IMC 2023), making FlexGlue 1.36×–1.58× faster per pair at 4k even with image decoding and dense feature extraction included in FlexGlue's wall time.
    • Early stopping (early_stop_decay = 0.5) reduces FlexGlue's matching time by ~7% at 2k (49.5 → 46.0 ms/pair) and ~10% at 4k (52.2 → 47.2 ms/pair) on ETH3D. On IMC 2023, heritage/dioscuri represents 88% of all pairs (9,591 / 10,935) and has high visual overlap across views, so few pairs stop early (−1%).
  • Mapper time: Rejecting look-alike pairs with FlexGlue covis also speeds up incremental mapping relative to FlexGlue, none (−16% at 2k and −26% at 4k on ETH3D; −17% at 2k and −3% at 4k on IMC 2023) and matches or beats LoMa across ETH3D (2k & 4k) and IMC 2023 (2k). On IMC 2023 at 4k, FlexGlue registers more dioscuri images with denser tracks (138–139 vs. 136 for LoMa), increasing bundle adjustment size on that scene.

@ahojnnes

ahojnnes commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Fantastic, thanks very much!

FlexGlue (https://github.com/cvg/FlexGlue) matches the keypoints of any
extractor through dense image features. Since the dense features are computed
from the image pixels, the matcher reads the images during matching (the image
name is now passed in FeatureMatcher::Image) and caches the dense features of
the most recently used images. Pairs whose covisibility score is below
min_covisibility are rejected inside the model, which suppresses look-alike
pairs. The models also stop early on pairs that are confidently not covisible:
after layers 2, 4, 6 and 8, a pair is rejected without running the remaining
layers if its covisibility score is below
min_covisibility * (1 - early_stop_decay)^(9 - layer) (early_stop_decay = 0.5
by default; 1 disables it).

On CUDA GPUs with tensor cores, a float16 variant of the matcher is used by
default. On the CPU, a single matching worker runs the models with all threads.

Adds the FLEXGLUE matcher type, FlexGlueMatching.* options, the
'--feature flexglue' preset of automatic_reconstructor (DaD keypoints, as the
LoMa presets), pycolmap bindings, a GUI entry, documentation, and tests.
- Default FlexGlueMatching.image_path to the global image path also when
  reading a project file with OptionManager::Read, not only in Parse.
- Validate the type and shape of the covisibility_score output when
  loading the matcher.
- Reject a NaN max_distance_px and empty descriptor/matcher model paths.
- Document that a FlexGlue matcher must not run concurrent Match() calls.
- Disable early stopping in the min_covisibility test, so that it only
  exercises the final pair rejection.
- State the provenance of the early stopping figures in the docs, and
  drop the unconfirmed fp16 speedup factors.
- Match images without descriptors with FlexGlue: the matching workers check
  and load the descriptors only if the matcher uses them
  (FeatureMatchingOptions::UsesDescriptors), so keypoint-only databases no
  longer silently give zero matches. The tests pass no descriptors.
- Reject guided matching with FlexGlue in FeatureMatchingOptions::Check, before
  loading the models.
- Replace CudaDeviceSupportsBf16 and CudaDeviceHasTensorCores with
  CudaDeviceHasComputeCapability(gpu_index, major), used by the bf16 LoMa and
  fp16 FlexGlue gates.
- Require the covisibility_score output.
- Document that the matcher commands have no global image path, so
  FlexGlueMatching.image_path must be set in their project files, and that
  custom matchers must be exported with early stopping.
@sarlinpe
sarlinpe merged commit 721e185 into main Nov 1, 2026
16 checks passed
@sarlinpe
sarlinpe deleted the sarlinpe/flexglue branch November 1, 2026 20:33
fringehush-gif added a commit to fringehush-gif/colmap that referenced this pull request Nov 3, 2026
Integrate thirteen official upstream commits after the private GPU cleanup
checkpoint. Preserve blocking host-sync selection and its CUDA 12.0/12.1
flag boundary while adopting upstream's generalized compute-capability
helper and LoMa/FlexGlue callers. Keep both capability and policy tests;
remove the duplicate colmap_util_cuda link from automatic composition.

Includes upstream rig-frame covisibility, thin bitmap thumbnails, visibility
coordinate conversion, row-wise warp dispatch, benchmark fixes, FlexGlue,
review guidance, and the Windows non-GUI SIFT-test build fix.

Validation at this private checkpoint: merge conflicts resolved in cuda.cc,
cuda.h, cuda_test.cc; source search found no remaining old BF16-helper call;
git diff --check passed. Native merged-source qualification and independent
exact-diff review remain pending. Prior GPU tests qualify their pinned source,
not this entire merged product. No reconstruction or performance claim.

AI-assisted integration. Existing published history is preserved.

## Upstream commits

- `e52aa8ee12ef58eb1cd16ada2be9316fa1c02cd3` Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933)
- `8007d1a51ae0c0af87fff3ebd204cf4698df7565` Preserve float weight ordering in maximum spanning trees (colmap#4928)
- `dd84102ef5e75fc17e20da281d2fc2fee5aca6a9` Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942)
- `721e185ddb6205b85fddc39ccf0774567f6be6ed` Add FlexGlue feature matcher (colmap#4908)
- `d0a411f2a9af139b5ea8118d64e5da5b70c6a890` FlexGlue: cache the dense features on the GPU (colmap#4915)
- `105817b6299cf22144fbbf39f95689681352476d` Hoist camera model dispatch per row in image warps (colmap#4935)
- `27ffad4388ce5647df3ea9e547bf8e1ea155fbcd` Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932)
- `e8f3522b936882cbceae074a19fca58fe378d323` Resolve benchmark paths before changing subprocess directories (colmap#4931)
- `f664d5d219230c1f1ebf9c2c7ecdcafc451b95ce` Clamp visibility coordinates before unsigned conversion (colmap#4943)
- `138119b7640d46f1f51fe329694a9ddae3ae0908` Keep thin bitmap thumbnails nonempty (colmap#4927)
- `b16ed5f97fa405ff2462745f9509386a9ebe6ece` Count rig frame covisibility once per 3D point (colmap#4893)
- `93dbbcb7f3019da30bd4a2c72753421e6cb85b45` Add colmap-review agent skill for code review (colmap#4934)
- `2a6f6955ed73a3d15b2a75edede5750e7c7b3304` Fix sift_test build on MSVC without the GUI (colmap#4955)
fringehush-gif added a commit to fringehush-gif/colmap that referenced this pull request Nov 4, 2026
Integrate thirteen official upstream commits after the private GPU cleanup
checkpoint. Preserve blocking host-sync selection and its CUDA 12.0/12.1
flag boundary while adopting upstream's generalized compute-capability
helper and LoMa/FlexGlue callers. Keep both capability and policy tests;
remove the duplicate colmap_util_cuda link from automatic composition.

Includes upstream rig-frame covisibility, thin bitmap thumbnails, visibility
coordinate conversion, row-wise warp dispatch, benchmark fixes, FlexGlue,
review guidance, and the Windows non-GUI SIFT-test build fix.

Validation at this private checkpoint: merge conflicts resolved in cuda.cc,
cuda.h, cuda_test.cc; source search found no remaining old BF16-helper call;
git diff --check passed. Native merged-source qualification and independent
exact-diff review remain pending. Prior GPU tests qualify their pinned source,
not this entire merged product. No reconstruction or performance claim.

AI-assisted integration. Existing published history is preserved.

## Upstream commits

- `e52aa8ee12ef58eb1cd16ada2be9316fa1c02cd3` Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933)
- `8007d1a51ae0c0af87fff3ebd204cf4698df7565` Preserve float weight ordering in maximum spanning trees (colmap#4928)
- `dd84102ef5e75fc17e20da281d2fc2fee5aca6a9` Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942)
- `721e185ddb6205b85fddc39ccf0774567f6be6ed` Add FlexGlue feature matcher (colmap#4908)
- `d0a411f2a9af139b5ea8118d64e5da5b70c6a890` FlexGlue: cache the dense features on the GPU (colmap#4915)
- `105817b6299cf22144fbbf39f95689681352476d` Hoist camera model dispatch per row in image warps (colmap#4935)
- `27ffad4388ce5647df3ea9e547bf8e1ea155fbcd` Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932)
- `e8f3522b936882cbceae074a19fca58fe378d323` Resolve benchmark paths before changing subprocess directories (colmap#4931)
- `f664d5d219230c1f1ebf9c2c7ecdcafc451b95ce` Clamp visibility coordinates before unsigned conversion (colmap#4943)
- `138119b7640d46f1f51fe329694a9ddae3ae0908` Keep thin bitmap thumbnails nonempty (colmap#4927)
- `b16ed5f97fa405ff2462745f9509386a9ebe6ece` Count rig frame covisibility once per 3D point (colmap#4893)
- `93dbbcb7f3019da30bd4a2c72753421e6cb85b45` Add colmap-review agent skill for code review (colmap#4934)
- `2a6f6955ed73a3d15b2a75edede5750e7c7b3304` Fix sift_test build on MSVC without the GUI (colmap#4955)
fringehush-gif added a commit to fringehush-gif/colmap that referenced this pull request Nov 7, 2026
Refresh the experimental alternative with fork main while retaining its previous head as a parent. The resulting tree is exactly main plus the isolated shared-current-cost-cache deletion: one CUDA file, +1/-25.

## Change

Read the existing cost map directly during the ordered sampled-cost fold. Preserve current plane propagation, source packing, scheduling and fallback mathematics. The cache originates in custom a37c1c3, MVS: Reuse sampled current costs (GPU perf). The earlier alternative is retained through 8481f89; its original experiment was 8b1412f.

Historical Fountain11 photo+geometry at 3072 recorded 76.85 -> 78.82 seconds, one observation per arm, on the older revision. Retain this inconclusive/adverse observation with its earlier revision; current performance has not been measured. No performance experiment was reopened.

## Validation

- Exact candidate CUDA owner compiled with CUDA 13.2 for compute_86/sm_86. In a bounded retained-support context on RTX 3060, both matched targets passed all 13 selected cases: propagation, ordered source folding, checkpoint-HMM and paired-sweep preservation.
- Four complete inputs and four outputs matched across executables: 8,492,128 bytes per arm. Maps were finite with positive depth and checked ordered graph IDs. Complete witnesses use checkpoint_hmm=false; separate checkpoint tests cover both settings without full cross-executable true-mode witnesses.
- Direct owners, link maps and actual loaded providers were checked; managed jobs ended and released their bookings. Later host resize/warp changes have separate owner qualification. This is not a whole-branch build, reconstruction-quality, portability or current-performance result.

## Upstream commits carried forward

- e52aa8e Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933)
- 8007d1a Preserve float weight ordering in maximum spanning trees (colmap#4928)
- dd84102 Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942)
- 721e185 Add FlexGlue feature matcher (colmap#4908)
- d0a411f FlexGlue: cache the dense features on the GPU (colmap#4915)
- 105817b Hoist camera model dispatch per row in image warps (colmap#4935)
- 27ffad4 Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932)
- e8f3522 Resolve benchmark paths before changing subprocess directories (colmap#4931)
- f664d5d Clamp visibility coordinates before unsigned conversion (colmap#4943)
- 138119b Keep thin bitmap thumbnails nonempty (colmap#4927)
- b16ed5f Count rig frame covisibility once per 3D point (colmap#4893)
- 93dbbcb Add colmap-review agent skill for code review (colmap#4934)
- 2a6f695 Fix sift_test build on MSVC without the GUI (colmap#4955)
- 78cc729 Fix default prior_position_loss_scale to sqrt(chi2_95_3dof) (colmap#4936)
- 8653c6b Estimate two-view relative rotation covariance (colmap#4950)
- e1317dd FlexGlue: match keypoints in the resized descriptor input frame (colmap#4949)
- 3db6eb0 Extract ConvertPosePriorsToENU and skip pose priors without position (colmap#4937)
- 77cbe48 Store and estimate rotation covariances on PoseGraph (colmap#4951)
- 30a1a81 Support covariance whitening in CeresRotationAverager (colmap#4952)
- e6644e7 Fix -Wunused-result warning in ThreadPool::AddTask (colmap#4958)
- 5b6da88 Clear descriptor-index results for empty queries (colmap#4960)
- d1d92d9 Add COVARIANCE reweighting for Ceres rotation averaging (colmap#4953)
- 761d90d Enable covariance-weighted rotation averaging in global pipelines (colmap#4823)
- fdee2e1 Reuse the pose graph of GlobalPipeline in the component mappers (colmap#4912)

AI disclosure: Totally vibe coded
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run Windows and remaining pycolmap CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants