Skip to content

Prepare 4.1.1 patch release#4554

Merged
ahojnnes merged 16 commits into
release/4.1from
user/jsch/release-4.1.1
Jul 17, 2026
Merged

Prepare 4.1.1 patch release#4554
ahojnnes merged 16 commits into
release/4.1from
user/jsch/release-4.1.1

Conversation

@ahojnnes

Copy link
Copy Markdown
Contributor

Prepares the 4.1.1 patch release by cherry-picking important bug/build fixes that landed on main since 4.1.0, plus one previously-unmerged fix. Features, refactors, algorithm changes, benchmarks, docs-site infra, and dependency upgrades from main are intentionally excluded.

Included fixes

Bug / correctness

Build / packaging

GUI

Docs

Previously unmerged

⚠️ Breaking change

#4551 removes pycolmap.GPSTransfromEllipsoid (present since ≤4.1.0) with no backward-compatible alias. Downstream code must switch to pycolmap.GPSTransformEllipsoid. This is unusual for a patch release and must be highlighted in the 4.1.1 release notes.

Notes

Still TODO before tagging

  • Version bump to 4.1.1
  • CHANGELOG entry (incl. the breaking-change callout)
  • Full build + test run across platforms (CI)

ahojnnes and others added 14 commits July 17, 2026 18:01
### Background

COLMAP's Caspar integration uses a small MSVC compatibility header for
generated sources. This works for regular C/C++ compilation, but the
same header was also force-included for CUDA sources through `-Xcompiler
/FI`.

That is too broad for NVCC builds on Windows: the compatibility header
pulls in C++ standard library / MSVC headers during CUDA compilation,
which can trigger MSVC/UCRT header redefinition errors with newer CUDA +
Visual Studio toolchains.

The CUDA sources only need a much smaller compatibility shim for MSVC,
currently just the missing `uint` typedef used by generated Caspar CUDA
code. This PR separates the CUDA pre-include path from the regular C/C++
forced include path and uses NVCC's native `--pre-include` option for
CUDA files.

### Fix
Keep the existing forced MSVC compact header for C/C++ sources, but use
NVCC's native --pre-include with a CUDA-only compatibility header for
Caspar CUDA sources.

This avoids pulling C++ standard library headers into NVCC's MSVC host
pass and fixes UCRT/MSVC header redefinition failures across supported
CUDA + Visual Studio toolchains.

### Validation

Tested on Windows with CUDA 12.5 and MSVC 19.44:

- `caspar_lib_core` builds successfully in Release
- `INSTALL` target completes successfully
- Installed `COLMAP.bat help` runs successfully and reports CUDA support

(cherry picked from commit 44e9f74)
## Problem

Building with `-DCASPAR_ENABLED=ON` fails during Caspar kernel
compilation when the target CUDA architecture is below compute
capability 7.0. Caspar's Symforce-generated kernels use
`cooperative_groups::labeled_partition` and `atomicAdd_block`, which
require SM >= 7.0. The failure surfaces as low-level `nvcc` errors deep
in the kernel build rather than a clear message, and is especially easy
to hit in containerized / no-GPU builds where `CMAKE_CUDA_ARCHITECTURES`
defaults to `native` and `nvcc` falls back to an older default arch.

## Fix

Add a configure-time guard in `cmake/FindDependencies.cmake`, right
after the arch list is finalized (i.e. after the `native` default is
applied):

- **FATAL_ERROR** when `CASPAR_ENABLED` and any resolved arch entry is
`< 70`. The numeric check handles list entries and `-real`/`-virtual`
suffixes.
- **WARNING** for `native`/`all`/`all-major`, which cannot be resolved
statically (the containerized/no-GPU case).

This fails early with an actionable message instead of a cryptic
kernel-compile error.

## Verification

Tested the regex/comparison logic with `cmake -P`:

| archs | result |
|---|---|
| `52`, `60;75`, `75;61` | FATAL |
| `70`, `86`, `86-real`, `75;80;86;89;75-virtual` | pass |
| `native`, `all`, `all-major` | warn |

Fixes #4494

(cherry picked from commit a0477f9)
Fixes: #4490

## Problem

The SVG toolbar/menu icons do not render in the distributed Windows
binaries produced by CI — they show up blank.

The GUI loads all icons via `QIcon(":/media/*.svg")` (embedded Qt
resources). For Qt to rasterize an SVG at runtime it needs two plugins
deployed alongside the executable:
- `iconengines/qsvgicon.dll` (icon engine used by `QIcon` for `.svg`)
- `imageformats/qsvg.dll`

Inspecting the artifact from a recent `main` run confirmed:
- `bin/Qt6Svg.dll` ✅ present
- `plugins/imageformats/` had only `qgif/qico/qjpeg` — **`qsvg.dll`
missing**
- `plugins/iconengines/` — **directory did not exist at all**

## Root cause

A vcpkg triplet mismatch in `build-windows.yml`. The export contains
`qtbase:x64-windows`, `qtbase:x64-windows-release`, and
`qtsvg:x64-windows-release` — i.e. `qtsvg` is installed **only** for
`x64-windows-release`. But the packaging step copied plugins from
`x64-windows`:

```
cp -r vcpkg_export/colmap/installed/x64-windows/Qt6/plugins install
```

That grabs qtbase's base plugins but silently misses both qtsvg plugins.
The DLLs were already copied from `x64-windows-release`, which is why
`Qt6Svg.dll` was present but orphaned.

## Fix

- Copy plugins from `x64-windows-release` — the triplet COLMAP is
actually built against and where qtsvg lives (consistent with the DLL
copy). This is a strict superset of what was copied before, plus the
missing SVG plugins.
- Add a fail-fast guard asserting both SVG plugins exist after the copy.
The bug was invisible at build time (blank icons only appear at
runtime), so the check prevents a silent regression.

No C++ or `vcpkg.json` changes were needed — `qtsvg` was already a `gui`
dependency; the defect was purely in packaging.

(cherry picked from commit ef755a9)
Starting the COLMAP GUI (with no mapping requested) reads the entire
database into a `DatabaseCache`, emitting logs like:

```
incremental_pipeline.cc:278] Loading database
database_cache.cc:72] Loading rigs...
database_cache.cc:90] Loading cameras...
...
```

The `MainWindow` constructor called `CreateControllers()`, which eagerly
constructed an `IncrementalPipeline` whose constructor loads the
database. This also fired on every New/Open Project and Import (via
`ReconstructionReset()`). The work is redundant since the pipeline is
rebuilt anyway in `ReconstructionStart()` right before running.

Construct the mapper controller lazily — only when a reconstruction is
actually started:

- The constructor now just initializes control states via
`UpdateMapperControls()` (already null-safe).
- `ReconstructionReset()` calls the new `ResetMapperController()`, which
tears down any running controller **without** building a new one (no DB
read).
- `CreateMapperController()` (renamed from the inaccurate plural
`CreateControllers`) remains the single place that builds the pipeline,
invoked from `ReconstructionStart()`.
- Shared `Stop`/`Wait`/`reset` teardown factored into
`StopMapperController()`.
- `mapper_controller_` can now be null before the first start, so the
relevant checks in `ReconstructionStart()`/`ReconstructionStep()` are
null-guarded.

After this change, launching the GUI and opening/importing projects no
longer read the database — that only happens when mapping actually
starts.

- `ninja colmap_ui` builds cleanly.

(cherry picked from commit 97bcd8f)
The example in the class comment passed the address of a local
DatabaseCache/Reconstruction, but the constructor and
BeginReconstruction now take std::shared_ptr
(IncrementalMapper(std::shared_ptr<const DatabaseCache>) and
BeginReconstruction(const std::shared_ptr< Reconstruction>&)). Update
the example to construct and pass shared_ptrs so it matches the current
API.

Fixes #2656.

(cherry picked from commit e2c17fa)
…4526)

## Problem

Reported in #4398. There are two intentional but different quaternion
layouts in the codebase, and the Python side does not document the
difference:

- The rig config JSON `cam_from_rig_rotation` is read in **`[w, x, y,
z]`** order — see the `ReadRigConfig` header docs ("The rotation is
expected in the order [w, x, y, z]") and `Eigen::Quaterniond(w, x, y,
z)` in `rig.cc`.
- `pycolmap.Rotation3d.quat` / `Rigid3d.rotation.quat` expose the
quaternion in **`[x, y, z, w]`** order (Eigen coefficient order), as
their own docstrings state.

Because the `read_rig_config` binding docstring was just "Read the rig
configuration from a .json file.", a Python user naturally assumes the
JSON quaternion and `.quat` share the same layout and compares them
directly, getting a surprising mismatch (the reporter's `assert
np.allclose(...)` fails with the components swapped).

## Change

Documentation only — no behavior change. Spell out both conventions in
the `read_rig_config` docstring so the difference is discoverable via
`help(pycolmap.read_rig_config)`.

Addresses #4398

---------

Co-authored-by: Johannes Schönberger <jsch@meta.com>
(cherry picked from commit f27a3e7)
Adds a Fedora section to doc/install.rst, covering the full set of build
issues encountered on a fresh Fedora 43 system: package name differences
(CGAL, OpenImageIO), CUDA toolkit setup via NVIDIA's official Fedora
repo, the suitesparse-static packaging quirk, and the
Ceres/FindGlog.cmake target-collision fix mentioned in #3347.

---------

Co-authored-by: Johannes Schönberger <jsch@meta.com>
(cherry picked from commit 6617573)
## Motivation

When double-clicking an image in the 3D model viewer whose file cannot
be found on disk, the image viewer window never appeared at all — the
user only got a console error, even though the metadata was available.
This makes the GUI more forgiving when working with reconstructions
whose source images are no longer present.

## Changes

- **Image viewer (double-click an image):** show the viewer with an
empty image pane when the file cannot be read, so the metadata table
(image/frame/rig id, camera, pose, point counts, name) remains visible.
Previously `ShowPixmap` — which shows and raises the widget — was only
called after a successful read, so a failed read returned early and the
window never opened. The log is downgraded from error to warning.

- **Image viewer table:** fix `ResizeTable` including the height of the
*hidden* horizontal header, which left an empty strip below the last row
that looked like an extra empty row.

- **Point viewer (select a 3D point):** downgrade the missing-image log
from error to warning. The loop already skips images that cannot be
read, so a missing source image is expected rather than an error.

(cherry picked from commit 89426d2)
## Summary

- `exe/sfm.cc`: "follwoing" → "following" in the `pose_prior_mapper`
`--overwrite_priors_covariance` help text.
- `doc/faq.rst`: "conditiions" → "conditions".

(cherry picked from commit bd7c30b)
…SAC (#4553)

## Problem

Feature matching became ~4-6x slower after the OpenMP parallelization of
`RANSAC`/`LORANSAC` introduced in #4169, as reported in #4482.

## Root cause

The parallelization used an **unnamed** `#pragma omp critical` to guard
updates to the shared best model/support. An unnamed `omp critical` maps
to a *single process-global lock* shared by every unnamed critical
region, and it is acquired **even when not inside a parallel region**.

Geometric verification (`two_view_geometry.cc`) runs many
`RANSAC`/`LORANSAC` calls concurrently across the matcher's
`std::thread` pool, each with the default `num_threads == 1` (i.e.
serial — the `#pragma omp parallel ... if (num_threads > 1)` region is
never entered). Despite running serially, every model evaluation still
acquired the one global critical lock, so all concurrent verification
threads serialized on it in the hottest loop.

## Fix

Replace the `#pragma omp critical` with a per-call `std::mutex`, locked
via `std::unique_lock(..., std::defer_lock)` only when `num_threads >
1`:

- **Serial path** (matching's default): no lock is taken at all → no
cross-thread contention.
- **Parallel path** (`num_threads > 1`): a call-local mutex, contended
only among that call's own team threads → correctness preserved.

## Verification

- `ransac_test` (6/6) and `loransac_test` (3/3) pass, including the
`Parallel*` tests.
- A standalone reproduction of the pattern confirmed the unnamed `omp
critical` is ~100-150x slower than the per-call mutex under concurrent
serial callers.
- Audited the entire repository: these two were the only unnamed `omp
critical` sites in first-party COLMAP code; all other `omp parallel`
regions are either `num_threads(1)` or one-shot MVS worksharing loops
and are unaffected.

Fixes #4482

(cherry picked from commit 0af598c)
The Python binding of GPSTransform::Ellipsoid was exposed under the
misspelled name GPSTransfromEllipsoid (Transfrom -> Transform). Rename it
to GPSTransformEllipsoid.

BREAKING CHANGE: the old misspelled name pycolmap.GPSTransfromEllipsoid is
removed with no backwards-compatible alias. Update any code referencing it
to pycolmap.GPSTransformEllipsoid.

(cherry picked from commit 17f8ea1)
@ahojnnes
ahojnnes enabled auto-merge (squash) July 17, 2026 17:21
@ahojnnes
ahojnnes merged commit a0d785f into release/4.1 Jul 17, 2026
14 checks passed
@ahojnnes
ahojnnes deleted the user/jsch/release-4.1.1 branch July 17, 2026 17:59
ahojnnes added a commit that referenced this pull request Jul 18, 2026
Adds the `CHANGELOG.rst` entry for the upcoming **4.1.1** patch release.

Documents the fixes being backported to `release/4.1` in #4554
(Improvements, Bug Fixes, and one Breaking Change — the
`GPSTransfromEllipsoid` → `GPSTransformEllipsoid` pycolmap enum rename).

Changelog-only; no code changes. The release date in the entry is
provisional and can be adjusted when 4.1.1 is tagged.
Dawars pushed a commit to Dawars/colmap that referenced this pull request Jul 22, 2026
Adds the `CHANGELOG.rst` entry for the upcoming **4.1.1** patch release.

Documents the fixes being backported to `release/4.1` in colmap#4554
(Improvements, Bug Fixes, and one Breaking Change — the
`GPSTransfromEllipsoid` → `GPSTransformEllipsoid` pycolmap enum rename).

Changelog-only; no code changes. The release date in the entry is
provisional and can be adjusted when 4.1.1 is tagged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants