From 6271afbba48033fd5f173cc1c3fd1e3306a3d75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Thu, 20 Nov 2025 18:00:12 +0100 Subject: [PATCH 1/4] Update to latest colmap and use L1 solver + union-find from colmap --- .github/workflows/ubuntu.yml | 2 +- .gitignore | 1 + cmake/FindDependencies.cmake | 36 +- cmake/FindSuiteSparse.cmake | 537 ------------------ glomap/CMakeLists.txt | 5 +- glomap/controllers/global_mapper_test.cc | 8 +- glomap/controllers/rotation_averager_test.cc | 15 +- glomap/controllers/track_establishment.cc | 2 +- glomap/controllers/track_establishment.h | 5 +- .../estimators/global_rotation_averaging.cc | 23 +- glomap/estimators/global_rotation_averaging.h | 3 +- glomap/estimators/rotation_initializer.cc | 3 +- glomap/io/colmap_converter.cc | 2 +- glomap/math/l1_solver.h | 113 ---- glomap/math/union_find.h | 40 -- glomap/processors/reconstruction_normalizer.h | 3 +- glomap/processors/view_graph_manipulation.cc | 5 +- glomap/scene/view_graph.cc | 2 - glomap/thirdparty/CMakeLists.txt | 75 +++ scripts/format/c++.sh | 10 +- 20 files changed, 126 insertions(+), 764 deletions(-) delete mode 100644 cmake/FindSuiteSparse.cmake delete mode 100644 glomap/math/l1_solver.h delete mode 100644 glomap/math/union_find.h create mode 100644 glomap/thirdparty/CMakeLists.txt diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 0ebc1e14..29d05f07 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -100,7 +100,7 @@ jobs: if: matrix.config.checkCodeFormat run: | set +x -euo pipefail - python -m pip install clang-format==19.1.0 + python -m pip install clang-format==20.1.5 ./scripts/format/c++.sh git diff --name-only git diff --exit-code || (echo "Code formatting failed" && exit 1) diff --git a/.gitignore b/.gitignore index 5066fb1b..9220f287 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /data /.vscode /compile_commands.json +.cache diff --git a/cmake/FindDependencies.cmake b/cmake/FindDependencies.cmake index c1d0110e..a656c221 100644 --- a/cmake/FindDependencies.cmake +++ b/cmake/FindDependencies.cmake @@ -1,10 +1,6 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -find_package(Eigen3 3.4 REQUIRED) -find_package(CHOLMOD QUIET) -if(NOT TARGET SuiteSparse::CHOLMOD) - find_package(SuiteSparse COMPONENTS CHOLMOD REQUIRED) -endif() +find_package(Eigen3 REQUIRED) find_package(Ceres REQUIRED COMPONENTS SuiteSparse) find_package(Boost REQUIRED) find_package(OpenMP REQUIRED COMPONENTS C CXX) @@ -24,36 +20,6 @@ if(TESTS_ENABLED) find_package(GTest REQUIRED) endif() -include(FetchContent) -FetchContent_Declare(PoseLib - GIT_REPOSITORY https://github.com/PoseLib/PoseLib.git - GIT_TAG 7e9f5f53372e43f89655040d4dfc4a00e5ace11c # 2.0.5 - EXCLUDE_FROM_ALL - SYSTEM -) -message(STATUS "Configuring PoseLib...") -if (FETCH_POSELIB) - FetchContent_MakeAvailable(PoseLib) -else() - find_package(PoseLib REQUIRED) -endif() -message(STATUS "Configuring PoseLib... done") - -FetchContent_Declare(COLMAP - GIT_REPOSITORY https://github.com/colmap/colmap.git - GIT_TAG c5f9cefc87e5dd596b638e4cee0ff543c7d14755 # Oct 23 2025 - EXCLUDE_FROM_ALL -) -message(STATUS "Configuring COLMAP...") -set(UNINSTALL_ENABLED OFF CACHE INTERNAL "") -set(GUI_ENABLED OFF CACHE INTERNAL "") -if (FETCH_COLMAP) - FetchContent_MakeAvailable(COLMAP) -else() - find_package(COLMAP REQUIRED) -endif() -message(STATUS "Configuring COLMAP... done") - set(CUDA_MIN_VERSION "7.0") if(CUDA_ENABLED) if(CMAKE_VERSION VERSION_LESS 3.17) diff --git a/cmake/FindSuiteSparse.cmake b/cmake/FindSuiteSparse.cmake deleted file mode 100644 index bccd89fe..00000000 --- a/cmake/FindSuiteSparse.cmake +++ /dev/null @@ -1,537 +0,0 @@ -# Ceres Solver - A fast non-linear least squares minimizer -# Copyright 2023 Google Inc. All rights reserved. -# http://ceres-solver.org/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of Google Inc. nor the names of its contributors may be -# used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Author: alexs.mac@gmail.com (Alex Stewart) -# - -#[=======================================================================[.rst: -FindSuiteSparse -=============== - -Module for locating SuiteSparse libraries and its dependencies. - -This module defines the following variables: - -``SuiteSparse_FOUND`` - ``TRUE`` iff SuiteSparse and all dependencies have been found. - -``SuiteSparse_VERSION`` - Extracted from ``SuiteSparse_config.h`` (>= v4). - -``SuiteSparse_VERSION_MAJOR`` - Equal to 4 if ``SuiteSparse_VERSION`` = 4.2.1 - -``SuiteSparse_VERSION_MINOR`` - Equal to 2 if ``SuiteSparse_VERSION`` = 4.2.1 - -``SuiteSparse_VERSION_PATCH`` - Equal to 1 if ``SuiteSparse_VERSION`` = 4.2.1 - -The following variables control the behaviour of this module: - -``SuiteSparse_NO_CMAKE`` - Do not attempt to use the native SuiteSparse CMake package configuration. - - -Targets -------- - -The following targets define the SuiteSparse components searched for. - -``SuiteSparse::AMD`` - Symmetric Approximate Minimum Degree (AMD) - -``SuiteSparse::CAMD`` - Constrained Approximate Minimum Degree (CAMD) - -``SuiteSparse::COLAMD`` - Column Approximate Minimum Degree (COLAMD) - -``SuiteSparse::CCOLAMD`` - Constrained Column Approximate Minimum Degree (CCOLAMD) - -``SuiteSparse::CHOLMOD`` - Sparse Supernodal Cholesky Factorization and Update/Downdate (CHOLMOD) - -``SuiteSparse::Partition`` - CHOLMOD with METIS support - -``SuiteSparse::SPQR`` - Multifrontal Sparse QR (SuiteSparseQR) - -``SuiteSparse::Config`` - Common configuration for all but CSparse (SuiteSparse version >= 4). - -Optional SuiteSparse dependencies: - -``METIS::METIS`` - Serial Graph Partitioning and Fill-reducing Matrix Ordering (METIS) -]=======================================================================] - -if (NOT SuiteSparse_NO_CMAKE) - find_package (SuiteSparse NO_MODULE QUIET) -endif (NOT SuiteSparse_NO_CMAKE) - -if (SuiteSparse_FOUND) - return () -endif (SuiteSparse_FOUND) - -# Push CMP0057 to enable support for IN_LIST, when cmake_minimum_required is -# set to <3.3. -cmake_policy (PUSH) -cmake_policy (SET CMP0057 NEW) - -if (NOT SuiteSparse_FIND_COMPONENTS) - set (SuiteSparse_FIND_COMPONENTS - AMD - CAMD - CCOLAMD - CHOLMOD - COLAMD - SPQR - ) - - foreach (component IN LISTS SuiteSparse_FIND_COMPONENTS) - set (SuiteSparse_FIND_REQUIRED_${component} TRUE) - endforeach (component IN LISTS SuiteSparse_FIND_COMPONENTS) -endif (NOT SuiteSparse_FIND_COMPONENTS) - -# Assume SuiteSparse was found and set it to false only if third-party -# dependencies could not be located. SuiteSparse components are handled by -# FindPackageHandleStandardArgs HANDLE_COMPONENTS option. -set (SuiteSparse_FOUND TRUE) - -include (CheckLibraryExists) -include (CheckSymbolExists) -include (CMakePushCheckState) - -# Config is a base component and thus always required -set (SuiteSparse_IMPLICIT_COMPONENTS Config) - -# CHOLMOD depends on AMD, CAMD, CCOLAMD, and COLAMD. -if (CHOLMOD IN_LIST SuiteSparse_FIND_COMPONENTS) - list (APPEND SuiteSparse_IMPLICIT_COMPONENTS AMD CAMD CCOLAMD COLAMD) -endif (CHOLMOD IN_LIST SuiteSparse_FIND_COMPONENTS) - -# SPQR depends on CHOLMOD. -if (SPQR IN_LIST SuiteSparse_FIND_COMPONENTS) - list (APPEND SuiteSparse_IMPLICIT_COMPONENTS CHOLMOD) -endif (SPQR IN_LIST SuiteSparse_FIND_COMPONENTS) - -# Implicit components are always required -foreach (component IN LISTS SuiteSparse_IMPLICIT_COMPONENTS) - set (SuiteSparse_FIND_REQUIRED_${component} TRUE) -endforeach (component IN LISTS SuiteSparse_IMPLICIT_COMPONENTS) - -list (APPEND SuiteSparse_FIND_COMPONENTS ${SuiteSparse_IMPLICIT_COMPONENTS}) - -# Do not list components multiple times. -list (REMOVE_DUPLICATES SuiteSparse_FIND_COMPONENTS) - -# Reset CALLERS_CMAKE_FIND_LIBRARY_PREFIXES to its value when -# FindSuiteSparse was invoked. -macro(SuiteSparse_RESET_FIND_LIBRARY_PREFIX) - if (MSVC) - set(CMAKE_FIND_LIBRARY_PREFIXES "${CALLERS_CMAKE_FIND_LIBRARY_PREFIXES}") - endif (MSVC) -endmacro(SuiteSparse_RESET_FIND_LIBRARY_PREFIX) - -# Called if we failed to find SuiteSparse or any of it's required dependencies, -# unsets all public (designed to be used externally) variables and reports -# error message at priority depending upon [REQUIRED/QUIET/] argument. -macro(SuiteSparse_REPORT_NOT_FOUND REASON_MSG) - # Will be set to FALSE by find_package_handle_standard_args - unset (SuiteSparse_FOUND) - - # Do NOT unset SuiteSparse_REQUIRED_VARS here, as it is used by - # FindPackageHandleStandardArgs() to generate the automatic error message on - # failure which highlights which components are missing. - - suitesparse_reset_find_library_prefix() - - # Note _FIND_[REQUIRED/QUIETLY] variables defined by FindPackage() - # use the camelcase library name, not uppercase. - if (SuiteSparse_FIND_QUIETLY) - message(STATUS "Failed to find SuiteSparse - " ${REASON_MSG} ${ARGN}) - elseif (SuiteSparse_FIND_REQUIRED) - message(FATAL_ERROR "Failed to find SuiteSparse - " ${REASON_MSG} ${ARGN}) - else() - # Neither QUIETLY nor REQUIRED, use no priority which emits a message - # but continues configuration and allows generation. - message("-- Failed to find SuiteSparse - " ${REASON_MSG} ${ARGN}) - endif (SuiteSparse_FIND_QUIETLY) - - # Do not call return(), s/t we keep processing if not called with REQUIRED - # and report all missing components, rather than bailing after failing to find - # the first. -endmacro(SuiteSparse_REPORT_NOT_FOUND) - -# Handle possible presence of lib prefix for libraries on MSVC, see -# also SuiteSparse_RESET_FIND_LIBRARY_PREFIX(). -if (MSVC) - # Preserve the caller's original values for CMAKE_FIND_LIBRARY_PREFIXES - # s/t we can set it back before returning. - set(CALLERS_CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") - # The empty string in this list is important, it represents the case when - # the libraries have no prefix (shared libraries / DLLs). - set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "" "${CMAKE_FIND_LIBRARY_PREFIXES}") -endif (MSVC) - -# Additional suffixes to try appending to each search path. -list(APPEND SuiteSparse_CHECK_PATH_SUFFIXES - suitesparse) # Windows/Ubuntu - -# Wrappers to find_path/library that pass the SuiteSparse search hints/paths. -# -# suitesparse_find_component( [FILES name1 [name2 ...]] -# [LIBRARIES name1 [name2 ...]]) -macro(suitesparse_find_component COMPONENT) - include(CMakeParseArguments) - set(MULTI_VALUE_ARGS FILES LIBRARIES) - cmake_parse_arguments(SuiteSparse_FIND_COMPONENT_${COMPONENT} - "" "" "${MULTI_VALUE_ARGS}" ${ARGN}) - - set(SuiteSparse_${COMPONENT}_FOUND TRUE) - if (SuiteSparse_FIND_COMPONENT_${COMPONENT}_FILES) - find_path(SuiteSparse_${COMPONENT}_INCLUDE_DIR - NAMES ${SuiteSparse_FIND_COMPONENT_${COMPONENT}_FILES} - PATH_SUFFIXES ${SuiteSparse_CHECK_PATH_SUFFIXES}) - if (SuiteSparse_${COMPONENT}_INCLUDE_DIR) - message(STATUS "Found ${COMPONENT} headers in: " - "${SuiteSparse_${COMPONENT}_INCLUDE_DIR}") - mark_as_advanced(SuiteSparse_${COMPONENT}_INCLUDE_DIR) - else() - # Specified headers not found. - set(SuiteSparse_${COMPONENT}_FOUND FALSE) - if (SuiteSparse_FIND_REQUIRED_${COMPONENT}) - suitesparse_report_not_found( - "Did not find ${COMPONENT} header (required SuiteSparse component).") - else() - message(STATUS "Did not find ${COMPONENT} header (optional " - "SuiteSparse component).") - # Hide optional vars from CMake GUI even if not found. - mark_as_advanced(SuiteSparse_${COMPONENT}_INCLUDE_DIR) - endif() - endif() - endif() - - if (SuiteSparse_FIND_COMPONENT_${COMPONENT}_LIBRARIES) - find_library(SuiteSparse_${COMPONENT}_LIBRARY - NAMES ${SuiteSparse_FIND_COMPONENT_${COMPONENT}_LIBRARIES} - PATH_SUFFIXES ${SuiteSparse_CHECK_PATH_SUFFIXES}) - if (SuiteSparse_${COMPONENT}_LIBRARY) - message(STATUS "Found ${COMPONENT} library: ${SuiteSparse_${COMPONENT}_LIBRARY}") - mark_as_advanced(SuiteSparse_${COMPONENT}_LIBRARY) - else () - # Specified libraries not found. - set(SuiteSparse_${COMPONENT}_FOUND FALSE) - if (SuiteSparse_FIND_REQUIRED_${COMPONENT}) - suitesparse_report_not_found( - "Did not find ${COMPONENT} library (required SuiteSparse component).") - else() - message(STATUS "Did not find ${COMPONENT} library (optional SuiteSparse " - "dependency)") - # Hide optional vars from CMake GUI even if not found. - mark_as_advanced(SuiteSparse_${COMPONENT}_LIBRARY) - endif() - endif() - endif() - - # A component can be optional (given to OPTIONAL_COMPONENTS). However, if the - # component is implicit (must be always present, such as the Config component) - # assume it be required as well. - if (SuiteSparse_FIND_REQUIRED_${COMPONENT}) - list (APPEND SuiteSparse_REQUIRED_VARS SuiteSparse_${COMPONENT}_INCLUDE_DIR) - list (APPEND SuiteSparse_REQUIRED_VARS SuiteSparse_${COMPONENT}_LIBRARY) - endif (SuiteSparse_FIND_REQUIRED_${COMPONENT}) - - # Define the target only if the include directory and the library were found - if (SuiteSparse_${COMPONENT}_INCLUDE_DIR AND SuiteSparse_${COMPONENT}_LIBRARY) - if (NOT TARGET SuiteSparse::${COMPONENT}) - add_library(SuiteSparse::${COMPONENT} IMPORTED UNKNOWN) - endif (NOT TARGET SuiteSparse::${COMPONENT}) - - set_property(TARGET SuiteSparse::${COMPONENT} PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${SuiteSparse_${COMPONENT}_INCLUDE_DIR}) - set_property(TARGET SuiteSparse::${COMPONENT} PROPERTY - IMPORTED_LOCATION ${SuiteSparse_${COMPONENT}_LIBRARY}) - endif (SuiteSparse_${COMPONENT}_INCLUDE_DIR AND SuiteSparse_${COMPONENT}_LIBRARY) -endmacro() - -# Given the number of components of SuiteSparse, and to ensure that the -# automatic failure message generated by FindPackageHandleStandardArgs() -# when not all required components are found is helpful, we maintain a list -# of all variables that must be defined for SuiteSparse to be considered found. -unset(SuiteSparse_REQUIRED_VARS) - -# BLAS. -find_package(BLAS QUIET) -if (NOT BLAS_FOUND) - suitesparse_report_not_found( - "Did not find BLAS library (required for SuiteSparse).") -endif (NOT BLAS_FOUND) - -# LAPACK. -find_package(LAPACK QUIET) -if (NOT LAPACK_FOUND) - suitesparse_report_not_found( - "Did not find LAPACK library (required for SuiteSparse).") -endif (NOT LAPACK_FOUND) - -foreach (component IN LISTS SuiteSparse_FIND_COMPONENTS) - if (component STREQUAL Partition) - # Partition is a meta component that neither provides additional headers nor - # a separate library. It is strictly part of CHOLMOD. - continue () - endif (component STREQUAL Partition) - string (TOLOWER ${component} component_library) - - if (component STREQUAL "Config") - set (component_header SuiteSparse_config.h) - set (component_library suitesparseconfig) - elseif (component STREQUAL "SPQR") - set (component_header SuiteSparseQR.hpp) - else (component STREQUAL "SPQR") - set (component_header ${component_library}.h) - endif (component STREQUAL "Config") - - suitesparse_find_component(${component} - FILES ${component_header} - LIBRARIES ${component_library}) -endforeach (component IN LISTS SuiteSparse_FIND_COMPONENTS) - -if (TARGET SuiteSparse::SPQR) - # SuiteSparseQR may be compiled with Intel Threading Building Blocks, - # we assume that if TBB is installed, SuiteSparseQR was compiled with - # support for it, this will do no harm if it wasn't. - find_package(TBB QUIET) - if (TBB_FOUND) - message(STATUS "Found Intel Thread Building Blocks (TBB) library " - "(${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR} / ${TBB_INTERFACE_VERSION}) " - "include location: ${TBB_INCLUDE_DIRS}. Assuming SuiteSparseQR was " - "compiled with TBB.") - # Add the TBB libraries to the SuiteSparseQR libraries (the only - # libraries to optionally depend on TBB). - if (TARGET TBB::tbb) - # Native TBB package configuration provides an imported target. Use it if - # available. - set_property (TARGET SuiteSparse::SPQR APPEND PROPERTY - INTERFACE_LINK_LIBRARIES TBB::tbb) - else (TARGET TBB::tbb) - set_property (TARGET SuiteSparse::SPQR APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}) - set_property (TARGET SuiteSparse::SPQR APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ${TBB_LIBRARIES}) - endif (TARGET TBB::tbb) - else (TBB_FOUND) - message(STATUS "Did not find Intel TBB library, assuming SuiteSparseQR was " - "not compiled with TBB.") - endif (TBB_FOUND) -endif (TARGET SuiteSparse::SPQR) - -check_library_exists(rt shm_open "" HAVE_LIBRT) - -if (TARGET SuiteSparse::Config) - # SuiteSparse_config (SuiteSparse version >= 4) requires librt library for - # timing by default when compiled on Linux or Unix, but not on OSX (which - # does not have librt). - if (HAVE_LIBRT) - message(STATUS "Adding librt to " - "SuiteSparse_config libraries (required on Linux & Unix [not OSX] if " - "SuiteSparse is compiled with timing).") - set_property (TARGET SuiteSparse::Config APPEND PROPERTY - INTERFACE_LINK_LIBRARIES $) - else (HAVE_LIBRT) - message(STATUS "Could not find librt, but found SuiteSparse_config, " - "assuming that SuiteSparse was compiled without timing.") - endif (HAVE_LIBRT) - - # Add BLAS and LAPACK as dependencies of SuiteSparse::Config for convenience - # given that all components depend on it. - if (BLAS_FOUND) - if (TARGET BLAS::BLAS) - set_property (TARGET SuiteSparse::Config APPEND PROPERTY - INTERFACE_LINK_LIBRARIES $) - else (TARGET BLAS::BLAS) - set_property (TARGET SuiteSparse::Config APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ${BLAS_LIBRARIES}) - endif (TARGET BLAS::BLAS) - endif (BLAS_FOUND) - - if (LAPACK_FOUND) - if (TARGET LAPACK::LAPACK) - set_property (TARGET SuiteSparse::Config APPEND PROPERTY - INTERFACE_LINK_LIBRARIES $) - else (TARGET LAPACK::LAPACK) - set_property (TARGET SuiteSparse::Config APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ${LAPACK_LIBRARIES}) - endif (TARGET LAPACK::LAPACK) - endif (LAPACK_FOUND) - - # SuiteSparse version >= 4. - set(SuiteSparse_VERSION_FILE - ${SuiteSparse_Config_INCLUDE_DIR}/SuiteSparse_config.h) - if (NOT EXISTS ${SuiteSparse_VERSION_FILE}) - suitesparse_report_not_found( - "Could not find file: ${SuiteSparse_VERSION_FILE} containing version " - "information for >= v4 SuiteSparse installs, but SuiteSparse_config was " - "found (only present in >= v4 installs).") - else (NOT EXISTS ${SuiteSparse_VERSION_FILE}) - file(READ ${SuiteSparse_VERSION_FILE} Config_CONTENTS) - - string(REGEX MATCH "#define SUITESPARSE_MAIN_VERSION[ \t]+([0-9]+)" - SuiteSparse_VERSION_LINE "${Config_CONTENTS}") - set (SuiteSparse_VERSION_MAJOR ${CMAKE_MATCH_1}) - - string(REGEX MATCH "#define SUITESPARSE_SUB_VERSION[ \t]+([0-9]+)" - SuiteSparse_VERSION_LINE "${Config_CONTENTS}") - set (SuiteSparse_VERSION_MINOR ${CMAKE_MATCH_1}) - - string(REGEX MATCH "#define SUITESPARSE_SUBSUB_VERSION[ \t]+([0-9]+)" - SuiteSparse_VERSION_LINE "${Config_CONTENTS}") - set (SuiteSparse_VERSION_PATCH ${CMAKE_MATCH_1}) - - unset (SuiteSparse_VERSION_LINE) - - # This is on a single line s/t CMake does not interpret it as a list of - # elements and insert ';' separators which would result in 4.;2.;1 nonsense. - set(SuiteSparse_VERSION - "${SuiteSparse_VERSION_MAJOR}.${SuiteSparse_VERSION_MINOR}.${SuiteSparse_VERSION_PATCH}") - - if (SuiteSparse_VERSION MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+") - set(SuiteSparse_VERSION_COMPONENTS 3) - else (SuiteSparse_VERSION MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+") - message (WARNING "Could not parse SuiteSparse_config.h: SuiteSparse " - "version will not be available") - - unset (SuiteSparse_VERSION) - unset (SuiteSparse_VERSION_MAJOR) - unset (SuiteSparse_VERSION_MINOR) - unset (SuiteSparse_VERSION_PATCH) - endif (SuiteSparse_VERSION MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+") - endif (NOT EXISTS ${SuiteSparse_VERSION_FILE}) -endif (TARGET SuiteSparse::Config) - -# CHOLMOD requires AMD CAMD CCOLAMD COLAMD -if (TARGET SuiteSparse::CHOLMOD) - foreach (component IN ITEMS AMD CAMD CCOLAMD COLAMD) - if (TARGET SuiteSparse::${component}) - set_property (TARGET SuiteSparse::CHOLMOD APPEND PROPERTY - INTERFACE_LINK_LIBRARIES SuiteSparse::${component}) - else (TARGET SuiteSparse::${component}) - # Consider CHOLMOD not found if COLAMD cannot be found - set (SuiteSparse_CHOLMOD_FOUND FALSE) - endif (TARGET SuiteSparse::${component}) - endforeach (component IN ITEMS AMD CAMD CCOLAMD COLAMD) -endif (TARGET SuiteSparse::CHOLMOD) - -# SPQR requires CHOLMOD -if (TARGET SuiteSparse::SPQR) - if (TARGET SuiteSparse::CHOLMOD) - set_property (TARGET SuiteSparse::SPQR APPEND PROPERTY - INTERFACE_LINK_LIBRARIES SuiteSparse::CHOLMOD) - else (TARGET SuiteSparse::CHOLMOD) - # Consider SPQR not found if CHOLMOD cannot be found - set (SuiteSparse_SQPR_FOUND FALSE) - endif (TARGET SuiteSparse::CHOLMOD) -endif (TARGET SuiteSparse::SPQR) - -# Add SuiteSparse::Config as dependency to all components -if (TARGET SuiteSparse::Config) - foreach (component IN LISTS SuiteSparse_FIND_COMPONENTS) - if (component STREQUAL Config) - continue () - endif (component STREQUAL Config) - - if (TARGET SuiteSparse::${component}) - set_property (TARGET SuiteSparse::${component} APPEND PROPERTY - INTERFACE_LINK_LIBRARIES SuiteSparse::Config) - endif (TARGET SuiteSparse::${component}) - endforeach (component IN LISTS SuiteSparse_FIND_COMPONENTS) -endif (TARGET SuiteSparse::Config) - -# Check whether CHOLMOD was compiled with METIS support. The check can be -# performed only after the main components have been set up. -if (TARGET SuiteSparse::CHOLMOD) - # NOTE If SuiteSparse was compiled as a static library we'll need to link - # against METIS already during the check. Otherwise, the check can fail due to - # undefined references even though SuiteSparse was compiled with METIS. - find_package (METIS) - - if (TARGET METIS::METIS) - cmake_push_check_state (RESET) - set (CMAKE_REQUIRED_LIBRARIES SuiteSparse::CHOLMOD METIS::METIS) - check_symbol_exists (cholmod_metis cholmod.h SuiteSparse_CHOLMOD_USES_METIS) - cmake_pop_check_state () - - if (SuiteSparse_CHOLMOD_USES_METIS) - set_property (TARGET SuiteSparse::CHOLMOD APPEND PROPERTY - INTERFACE_LINK_LIBRARIES $) - - # Provide the SuiteSparse::Partition component whose availability indicates - # that CHOLMOD was compiled with the Partition module. - if (NOT TARGET SuiteSparse::Partition) - add_library (SuiteSparse::Partition IMPORTED INTERFACE) - endif (NOT TARGET SuiteSparse::Partition) - - set_property (TARGET SuiteSparse::Partition APPEND PROPERTY - INTERFACE_LINK_LIBRARIES SuiteSparse::CHOLMOD) - endif (SuiteSparse_CHOLMOD_USES_METIS) - endif (TARGET METIS::METIS) -endif (TARGET SuiteSparse::CHOLMOD) - -# We do not use suitesparse_find_component to find Partition and therefore must -# handle the availability in an extra step. -if (TARGET SuiteSparse::Partition) - set (SuiteSparse_Partition_FOUND TRUE) -else (TARGET SuiteSparse::Partition) - set (SuiteSparse_Partition_FOUND FALSE) -endif (TARGET SuiteSparse::Partition) - -suitesparse_reset_find_library_prefix() - -# Handle REQUIRED and QUIET arguments to FIND_PACKAGE -include(FindPackageHandleStandardArgs) -if (SuiteSparse_FOUND) - find_package_handle_standard_args(SuiteSparse - REQUIRED_VARS ${SuiteSparse_REQUIRED_VARS} - VERSION_VAR SuiteSparse_VERSION - FAIL_MESSAGE "Failed to find some/all required components of SuiteSparse." - HANDLE_COMPONENTS) -else (SuiteSparse_FOUND) - # Do not pass VERSION_VAR to FindPackageHandleStandardArgs() if we failed to - # find SuiteSparse to avoid a confusing autogenerated failure message - # that states 'not found (missing: FOO) (found version: x.y.z)'. - find_package_handle_standard_args(SuiteSparse - REQUIRED_VARS ${SuiteSparse_REQUIRED_VARS} - FAIL_MESSAGE "Failed to find some/all required components of SuiteSparse." - HANDLE_COMPONENTS) -endif (SuiteSparse_FOUND) - -# Pop CMP0057. -cmake_policy (POP) diff --git a/glomap/CMakeLists.txt b/glomap/CMakeLists.txt index 145adc4d..9baa76b0 100644 --- a/glomap/CMakeLists.txt +++ b/glomap/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(thirdparty) + set(SOURCES controllers/global_mapper.cc controllers/option_manager.cc @@ -47,11 +49,9 @@ set(HEADERS io/colmap_io.h io/pose_io.h math/gravity.h - math/l1_solver.h math/rigid3d.h math/tree.h math/two_view_geometry.h - math/union_find.h processors/image_pair_inliers.h processors/image_undistorter.h processors/reconstruction_normalizer.h @@ -86,7 +86,6 @@ target_link_libraries( PUBLIC Eigen3::Eigen Ceres::ceres - SuiteSparse::CHOLMOD OpenMP::OpenMP_CXX ${BOOST_LIBRARIES} ) diff --git a/glomap/controllers/global_mapper_test.cc b/glomap/controllers/global_mapper_test.cc index dee5cada..98b8fc2b 100644 --- a/glomap/controllers/global_mapper_test.cc +++ b/glomap/controllers/global_mapper_test.cc @@ -60,7 +60,6 @@ TEST(GlobalMapper, WithoutNoise) { synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); @@ -97,7 +96,6 @@ TEST(GlobalMapper, WithoutNoiseWithNonTrivialKnownRig) { synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.1; // No noise synthetic_dataset_options.sensor_from_rig_rotation_stddev = 5.; // No noise @@ -137,7 +135,6 @@ TEST(GlobalMapper, WithoutNoiseWithNonTrivialUnknownRig) { synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.1; // No noise synthetic_dataset_options.sensor_from_rig_rotation_stddev = 5.; // No noise @@ -187,10 +184,13 @@ TEST(GlobalMapper, WithNoiseAndOutliers) { synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 100; - synthetic_dataset_options.point2D_stddev = 0.5; synthetic_dataset_options.inlier_match_ratio = 0.6; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); + colmap::SyntheticNoiseOptions synthetic_noise_options; + synthetic_noise_options.point2D_stddev = 0.5; + colmap::SynthesizeNoise( + synthetic_noise_options, >_reconstruction, database.get()); ViewGraph view_graph; std::unordered_map cameras; diff --git a/glomap/controllers/rotation_averager_test.cc b/glomap/controllers/rotation_averager_test.cc index 095ff437..1dac8abd 100644 --- a/glomap/controllers/rotation_averager_test.cc +++ b/glomap/controllers/rotation_averager_test.cc @@ -135,7 +135,6 @@ TEST(RotationEstimator, WithoutNoise) { synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 20.; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); @@ -181,7 +180,6 @@ TEST(RotationEstimator, WithoutNoiseWithNoneTrivialKnownRig) { synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 20.; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); @@ -225,7 +223,6 @@ TEST(RotationEstimator, WithoutNoiseWithNoneTrivialUnknownRig) { synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 50; - synthetic_dataset_options.point2D_stddev = 0; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 20.; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); @@ -277,10 +274,13 @@ TEST(RotationEstimator, WithNoiseAndOutliers) { synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; - synthetic_dataset_options.point2D_stddev = 1; synthetic_dataset_options.inlier_match_ratio = 0.6; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); + colmap::SyntheticNoiseOptions synthetic_noise_options; + synthetic_noise_options.point2D_stddev = 1; + colmap::SynthesizeNoise( + synthetic_noise_options, >_reconstruction, database.get()); ViewGraph view_graph; std::unordered_map rigs; @@ -323,10 +323,13 @@ TEST(RotationEstimator, WithNoiseAndOutliersWithNonTrivialKnownRigs) { synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; - synthetic_dataset_options.point2D_stddev = 1; synthetic_dataset_options.inlier_match_ratio = 0.6; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); + colmap::SyntheticNoiseOptions synthetic_noise_options; + synthetic_noise_options.point2D_stddev = 1; + colmap::SynthesizeNoise( + synthetic_noise_options, >_reconstruction, database.get()); ViewGraph view_graph; std::unordered_map rigs; @@ -372,7 +375,6 @@ TEST(RotationEstimator, RefineGravity) { synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 25; synthetic_dataset_options.num_points3D = 100; - synthetic_dataset_options.point2D_stddev = 0; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); @@ -416,7 +418,6 @@ TEST(RotationEstimator, RefineGravityWithNontrivialRigs) { synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 25; synthetic_dataset_options.num_points3D = 100; - synthetic_dataset_options.point2D_stddev = 0; colmap::SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); diff --git a/glomap/controllers/track_establishment.cc b/glomap/controllers/track_establishment.cc index d4396ed3..77c307ec 100644 --- a/glomap/controllers/track_establishment.cc +++ b/glomap/controllers/track_establishment.cc @@ -5,7 +5,7 @@ namespace glomap { size_t TrackEngine::EstablishFullTracks( std::unordered_map& tracks) { tracks.clear(); - uf_.Clear(); + uf_ = {}; // Blindly concatenate tracks if any matches occur BlindConcatenation(); diff --git a/glomap/controllers/track_establishment.h b/glomap/controllers/track_establishment.h index 7e0c3ef0..1eb6a58b 100644 --- a/glomap/controllers/track_establishment.h +++ b/glomap/controllers/track_establishment.h @@ -1,9 +1,10 @@ #pragma once -#include "glomap/math/union_find.h" #include "glomap/scene/types_sfm.h" +#include + namespace glomap { struct TrackEstablishmentOptions { @@ -53,7 +54,7 @@ class TrackEngine { const std::unordered_map& images_; // Internal structure used for concatenating tracks - UnionFind uf_; + colmap::UnionFind uf_; }; } // namespace glomap diff --git a/glomap/estimators/global_rotation_averaging.cc b/glomap/estimators/global_rotation_averaging.cc index c78ee1cc..75b46d04 100644 --- a/glomap/estimators/global_rotation_averaging.cc +++ b/glomap/estimators/global_rotation_averaging.cc @@ -1,14 +1,17 @@ #include "global_rotation_averaging.h" #include "glomap/estimators/rotation_initializer.h" -#include "glomap/math/l1_solver.h" #include "glomap/math/rigid3d.h" #include "glomap/math/tree.h" +#include +#include + #include #include -#include "colmap/geometry/pose.h" +#include +#include namespace glomap { namespace { @@ -477,11 +480,15 @@ bool RotationEstimator::SolveL1Regression( const ViewGraph& view_graph, std::unordered_map& frames, std::unordered_map& images) { - L1SolverOptions opt_l1_solver; - opt_l1_solver.max_num_iterations = 10; + colmap::LeastAbsoluteDeviationSolver::Options l1_solver_options; + l1_solver_options.max_num_iterations = 10; + l1_solver_options.solver_type = colmap::LeastAbsoluteDeviationSolver:: + Options::SolverType::SupernodalCholmodLLT; + + const Eigen::SparseMatrix A = + weights_.matrix().asDiagonal() * sparse_matrix_; - L1Solver> l1_solver( - opt_l1_solver, weights_.matrix().asDiagonal() * sparse_matrix_); + colmap::LeastAbsoluteDeviationSolver l1_solver(l1_solver_options, A); double last_norm = 0; double curr_norm = 0; @@ -526,8 +533,8 @@ bool RotationEstimator::SolveL1Regression( iteration++; break; } - opt_l1_solver.max_num_iterations = - std::min(opt_l1_solver.max_num_iterations * 2, 100); + l1_solver_options.max_num_iterations = + std::min(l1_solver_options.max_num_iterations * 2, 100); } VLOG(2) << "L1 ADMM total iteration: " << iteration; return true; diff --git a/glomap/estimators/global_rotation_averaging.h b/glomap/estimators/global_rotation_averaging.h index fa0bdab4..e3d5057c 100644 --- a/glomap/estimators/global_rotation_averaging.h +++ b/glomap/estimators/global_rotation_averaging.h @@ -1,12 +1,13 @@ #pragma once -#include "glomap/math/l1_solver.h" #include "glomap/scene/types_sfm.h" #include "glomap/types.h" #include #include +#include + // Code is adapted from Theia's RobustRotationEstimator // (http://www.theia-sfm.org/). For gravity aligned rotation averaging, refere // to the paper "Gravity Aligned Rotation Averaging" diff --git a/glomap/estimators/rotation_initializer.cc b/glomap/estimators/rotation_initializer.cc index 3d1ca90e..2fd6c5e1 100644 --- a/glomap/estimators/rotation_initializer.cc +++ b/glomap/estimators/rotation_initializer.cc @@ -1,6 +1,7 @@ #include "glomap/estimators/rotation_initializer.h" -#include "colmap/geometry/pose.h" +#include + namespace glomap { bool ConvertRotationsFromImageToRig( diff --git a/glomap/io/colmap_converter.cc b/glomap/io/colmap_converter.cc index 9fc1cc53..3fcb0959 100644 --- a/glomap/io/colmap_converter.cc +++ b/glomap/io/colmap_converter.cc @@ -2,7 +2,7 @@ #include "glomap/math/two_view_geometry.h" -#include "colmap/scene/reconstruction_io_utils.h" +#include namespace glomap { diff --git a/glomap/math/l1_solver.h b/glomap/math/l1_solver.h deleted file mode 100644 index 1327bcae..00000000 --- a/glomap/math/l1_solver.h +++ /dev/null @@ -1,113 +0,0 @@ -// This code is adapted from Theia library (http://theia-sfm.org/), -// with its original L1 solver adapted from -// "https://web.stanford.edu/~boyd/papers/admm/least_abs_deviations/lad.html" - -#pragma once - -#include - -#include -#include -#include - -// An L1 norm (|| A * x - b ||_1) approximation solver based on ADMM -// (alternating direction method of multipliers, -// https://web.stanford.edu/~boyd/papers/pdf/admm_distr_stats.pdf). -namespace glomap { - -// TODO: L1 solver for dense matrix -struct L1SolverOptions { - int max_num_iterations = 1000; - // Rho is the augmented Lagrangian parameter. - double rho = 1.0; - // Alpha is the over-relaxation parameter (typically between 1.0 and 1.8). - double alpha = 1.0; - - double absolute_tolerance = 1e-4; - double relative_tolerance = 1e-2; -}; - -template -class L1Solver { - public: - L1Solver(const L1SolverOptions& options, const MatrixType& mat) - : options_(options), a_(mat) { - // Pre-compute the sparsity pattern. - const MatrixType spd_mat = a_.transpose() * a_; - linear_solver_.compute(spd_mat); - } - - void Solve(const Eigen::VectorXd& rhs, Eigen::VectorXd* solution) { - Eigen::VectorXd& x = *solution; - Eigen::VectorXd z(a_.rows()), u(a_.rows()); - z.setZero(); - u.setZero(); - - Eigen::VectorXd a_times_x(a_.rows()), z_old(z.size()), ax_hat(a_.rows()); - // Precompute some convergence terms. - const double rhs_norm = rhs.norm(); - const double primal_abs_tolerance_eps = - std::sqrt(a_.rows()) * options_.absolute_tolerance; - const double dual_abs_tolerance_eps = - std::sqrt(a_.cols()) * options_.absolute_tolerance; - - const std::string row_format = - " % 4d % 4.4e % 4.4e % 4.4e % 4.4e"; - for (int i = 0; i < options_.max_num_iterations; i++) { - // Update x. - x.noalias() = linear_solver_.solve(a_.transpose() * (rhs + z - u)); - if (linear_solver_.info() != Eigen::Success) { - LOG(ERROR) << "L1 Minimization failed. Could not solve the sparse " - "linear system with Cholesky Decomposition"; - return; - } - - a_times_x.noalias() = a_ * x; - ax_hat.noalias() = options_.alpha * a_times_x; - ax_hat.noalias() += (1.0 - options_.alpha) * (z + rhs); - - // Update z and set z_old. - std::swap(z, z_old); - z.noalias() = Shrinkage(ax_hat - rhs + u, 1.0 / options_.rho); - - // Update u. - u.noalias() += ax_hat - z - rhs; - - // Compute the convergence terms. - const double r_norm = (a_times_x - z - rhs).norm(); - const double s_norm = - (-options_.rho * a_.transpose() * (z - z_old)).norm(); - const double max_norm = std::max({a_times_x.norm(), z.norm(), rhs_norm}); - const double primal_eps = - primal_abs_tolerance_eps + options_.relative_tolerance * max_norm; - const double dual_eps = dual_abs_tolerance_eps + - options_.relative_tolerance * - (options_.rho * a_.transpose() * u).norm(); - - // Determine if the minimizer has converged. - if (r_norm < primal_eps && s_norm < dual_eps) { - break; - } - } - } - - private: - const L1SolverOptions& options_; - - // Matrix A in || Ax - b ||_1 - const MatrixType a_; - - // Cholesky linear solver. Since our linear system will be a SPD matrix we can - // utilize the Cholesky factorization. - Eigen::CholmodSupernodalLLT> linear_solver_; - - static Eigen::VectorXd Shrinkage(const Eigen::VectorXd& vec, - const double kappa) { - Eigen::ArrayXd zero_vec(vec.size()); - zero_vec.setZero(); - return zero_vec.max(vec.array() - kappa) - - zero_vec.max(-vec.array() - kappa); - } -}; - -} // namespace glomap diff --git a/glomap/math/union_find.h b/glomap/math/union_find.h deleted file mode 100644 index 8c9687f9..00000000 --- a/glomap/math/union_find.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include - -namespace glomap { - -// UnionFind class to maintain disjoint sets for creating tracks -template -class UnionFind { - public: - // Find the root of the element x - DataType Find(DataType x) { - // If x is not in parent map, initialize it with x as its parent - auto parentIt = parent_.find(x); - if (parentIt == parent_.end()) { - parent_.emplace_hint(parentIt, x, x); - return x; - } - // Path compression: set the parent of x to the root of the set containing x - if (parentIt->second != x) { - parentIt->second = Find(parentIt->second); - } - return parentIt->second; - } - - // Unite the sets containing x and y - void Union(DataType x, DataType y) { - DataType root_x = Find(x); - DataType root_y = Find(y); - if (root_x != root_y) parent_[root_x] = root_y; - } - - void Clear() { parent_.clear(); } - - private: - // Map to store the parent of each element - std::unordered_map parent_; -}; - -} // namespace glomap diff --git a/glomap/processors/reconstruction_normalizer.h b/glomap/processors/reconstruction_normalizer.h index 3d3c3193..6566b849 100644 --- a/glomap/processors/reconstruction_normalizer.h +++ b/glomap/processors/reconstruction_normalizer.h @@ -2,7 +2,7 @@ #include "glomap/scene/types_sfm.h" -#include "colmap/geometry/pose.h" +#include namespace glomap { @@ -16,4 +16,5 @@ colmap::Sim3d NormalizeReconstruction( double extent = 10., double p0 = 0.1, double p1 = 0.9); + } // namespace glomap diff --git a/glomap/processors/view_graph_manipulation.cc b/glomap/processors/view_graph_manipulation.cc index db5bc3ca..e63a57a2 100644 --- a/glomap/processors/view_graph_manipulation.cc +++ b/glomap/processors/view_graph_manipulation.cc @@ -1,8 +1,8 @@ #include "view_graph_manipulation.h" #include "glomap/math/two_view_geometry.h" -#include "glomap/math/union_find.h" +#include #include namespace glomap { @@ -77,7 +77,8 @@ image_t ViewGraphManipulater::EstablishStrongClusters( view_graph.KeepLargestConnectedComponents(frames, images); // Construct the initial cluster by keeping the pairs with weight > min_thres - UnionFind uf; + colmap::UnionFind uf; + uf.Reserve(frames.size()); // Go through the edges, and add the edge with weight > min_thres for (auto& [pair_id, image_pair] : view_graph.image_pairs) { if (image_pair.is_valid == false) continue; diff --git a/glomap/scene/view_graph.cc b/glomap/scene/view_graph.cc index b1835b30..60009116 100644 --- a/glomap/scene/view_graph.cc +++ b/glomap/scene/view_graph.cc @@ -1,7 +1,5 @@ #include "glomap/scene/view_graph.h" -#include "glomap/math/union_find.h" - #include namespace glomap { diff --git a/glomap/thirdparty/CMakeLists.txt b/glomap/thirdparty/CMakeLists.txt new file mode 100644 index 00000000..6d42e9fd --- /dev/null +++ b/glomap/thirdparty/CMakeLists.txt @@ -0,0 +1,75 @@ +# Determine project compiler. +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(IS_MSVC TRUE) +endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(IS_GNU TRUE) +endif() +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(IS_CLANG TRUE) +endif() + +# Only show moderate warnings for external library code. +if(IS_MSVC) + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + elseif(CMAKE_CXX_FLAGS MATCHES "/Wall") + string(REGEX REPLACE "/Wall" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + endif() + if(CMAKE_C_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + elseif(CMAKE_C_FLAGS MATCHES "/Wall") + string(REGEX REPLACE "/Wall" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + endif() +elseif(IS_GNU OR IS_CLANG) + if(CMAKE_CXX_FLAGS MATCHES "-Wall") + string(REGEX REPLACE "-Wall" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + endif() + if(CMAKE_C_FLAGS MATCHES "-Wall") + string(REGEX REPLACE "-Wall" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + set(REMOVED_WARNING_LEVEL TRUE) + endif() +endif() + +if(IS_MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0") +elseif(IS_GNU OR IS_CLANG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +endif() + +include(FetchContent) +FetchContent_Declare(PoseLib + GIT_REPOSITORY https://github.com/PoseLib/PoseLib.git + GIT_TAG 7e9f5f53372e43f89655040d4dfc4a00e5ace11c # 2.0.5 + EXCLUDE_FROM_ALL + SYSTEM +) +message(STATUS "Configuring PoseLib...") +if (FETCH_POSELIB) + FetchContent_MakeAvailable(PoseLib) +else() + find_package(PoseLib REQUIRED) +endif() +message(STATUS "Configuring PoseLib... done") + +FetchContent_Declare(COLMAP + GIT_REPOSITORY https://github.com/colmap/colmap.git + GIT_TAG b6b7b54eca6078070f73a3f0a084f79c629a6f10 # Nov 20, 2025 + EXCLUDE_FROM_ALL +) +message(STATUS "Configuring COLMAP...") +set(UNINSTALL_ENABLED OFF CACHE INTERNAL "") +set(GUI_ENABLED OFF CACHE INTERNAL "") +if (FETCH_COLMAP) + FetchContent_MakeAvailable(COLMAP) +else() + find_package(COLMAP REQUIRED) +endif() +message(STATUS "Configuring COLMAP... done") diff --git a/scripts/format/c++.sh b/scripts/format/c++.sh index 2b5a64b9..15ddc2f0 100755 --- a/scripts/format/c++.sh +++ b/scripts/format/c++.sh @@ -3,12 +3,12 @@ # This script applies clang-format to the whole repository. # Check version -version_string=$(clang-format --version | sed -E 's/^.* ([0-9]+\.[0-9]+)\..*$/\1/') -expected_version_string='19.1' -if [[ "$version_string" == "$expected_version_string" ]]; then - echo "clang-format major.minor version '$version_string' matches expected '$expected_version_string'" +version_string=$(clang-format --version | sed -E 's/^.*(\d+\.\d+\.\d+-.*).*$/\1/') +expected_version_string='20.1.5' +if [[ "$version_string" =~ "$expected_version_string" ]]; then + echo "clang-format version '$version_string' matches '$expected_version_string'" else - echo "clang-format major.minor version '$version_string' doesn't match expected '$expected_version_string'" + echo "clang-format version '$version_string' doesn't match '$expected_version_string'" exit 1 fi From 4b8a229327103b81607be4fa04e5ae6d4347c129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 21 Nov 2025 14:39:57 +0100 Subject: [PATCH 2/4] Simplify view graph --- .../estimators/global_rotation_averaging.cc | 2 +- glomap/estimators/gravity_refinement.cc | 35 +++-- glomap/io/colmap_converter.cc | 4 +- glomap/io/pose_io.cc | 20 +-- glomap/processors/image_pair_inliers.cc | 2 +- glomap/processors/reconstruction_pruning.cc | 18 ++- glomap/processors/relpose_filter.cc | 2 +- glomap/processors/view_graph_manipulation.cc | 7 +- glomap/scene/image_pair.h | 44 ++---- glomap/scene/view_graph.cc | 136 ++++++++++-------- glomap/scene/view_graph.h | 72 +++------- 11 files changed, 146 insertions(+), 196 deletions(-) diff --git a/glomap/estimators/global_rotation_averaging.cc b/glomap/estimators/global_rotation_averaging.cc index 75b46d04..8246b272 100644 --- a/glomap/estimators/global_rotation_averaging.cc +++ b/glomap/estimators/global_rotation_averaging.cc @@ -121,7 +121,7 @@ void RotationEstimator::InitializeFromMaximumSpanningTree( // Directly use the relative pose for estimation rotation const ImagePair& image_pair = view_graph.image_pairs.at( - ImagePair::ImagePairToPairId(curr, parents[curr])); + colmap::ImagePairToPairId(curr, parents[curr])); if (image_pair.image_id1 == curr) { // 1_R_w = 2_R_1^T * 2_R_w cam_from_worlds[curr].rotation = diff --git a/glomap/estimators/gravity_refinement.cc b/glomap/estimators/gravity_refinement.cc index bb19e28d..29f7adb6 100644 --- a/glomap/estimators/gravity_refinement.cc +++ b/glomap/estimators/gravity_refinement.cc @@ -9,12 +9,10 @@ namespace glomap { void GravityRefiner::RefineGravity(const ViewGraph& view_graph, std::unordered_map& frames, std::unordered_map& images) { - const std::unordered_map& image_pairs = - view_graph.image_pairs; const std::unordered_map>& - adjacency_list = view_graph.GetAdjacencyList(); + adjacency_list = view_graph.CreateImageAdjacencyList(); if (adjacency_list.empty()) { - LOG(INFO) << "Adjacency list not established" << std::endl; + LOG(INFO) << "Adjacency list not established"; return; } @@ -24,16 +22,16 @@ void GravityRefiner::RefineGravity(const ViewGraph& view_graph, IdentifyErrorProneGravity(view_graph, frames, images, error_prone_frames); if (error_prone_frames.empty()) { - LOG(INFO) << "No error prone frames found" << std::endl; + LOG(INFO) << "No error prone frames found"; return; } // Get the relevant pair ids for frames std::unordered_map> adjacency_list_frames_to_pair_id; for (auto& [image_id, neighbors] : adjacency_list) { - for (auto neighbor : neighbors) { + for (const auto& neighbor : neighbors) { adjacency_list_frames_to_pair_id[images[image_id].frame_id].insert( - ImagePair::ImagePairToPairId(image_id, neighbor)); + colmap::ImagePairToPairId(image_id, neighbor)); } } @@ -59,8 +57,8 @@ void GravityRefiner::RefineGravity(const ViewGraph& view_graph, int counter = 0; Eigen::Vector3d gravity = frames[frame_id].gravity_info.GetGravity(); for (const auto& pair_id : neighbors) { - image_t image_id1 = image_pairs.at(pair_id).image_id1; - image_t image_id2 = image_pairs.at(pair_id).image_id2; + const image_t image_id1 = view_graph.image_pairs.at(pair_id).image_id1; + const image_t image_id2 = view_graph.image_pairs.at(pair_id).image_id2; if (!images.at(image_id1).HasGravity() || !images.at(image_id2).HasGravity()) continue; @@ -82,17 +80,18 @@ void GravityRefiner::RefineGravity(const ViewGraph& view_graph, // consider a single cost term if (images.at(image_id1).frame_id == frame_id) { gravities.emplace_back( - (colmap::Inverse(image_pairs.at(pair_id).cam2_from_cam1 * + (colmap::Inverse(view_graph.image_pairs.at(pair_id).cam2_from_cam1 * cam1_from_rig1) .rotation.toRotationMatrix() * images[image_id2].GetRAlign()) .col(1)); } else if (images.at(image_id2).frame_id == frame_id) { - gravities.emplace_back(((colmap::Inverse(cam2_from_rig2) * - image_pairs.at(pair_id).cam2_from_cam1) - .rotation.toRotationMatrix() * - images[image_id1].GetRAlign()) - .col(1)); + gravities.emplace_back( + ((colmap::Inverse(cam2_from_rig2) * + view_graph.image_pairs.at(pair_id).cam2_from_cam1) + .rotation.toRotationMatrix() * + images[image_id1].GetRAlign()) + .col(1)); } ceres::CostFunction* coor_cost = @@ -123,9 +122,8 @@ void GravityRefiner::RefineGravity(const ViewGraph& view_graph, frames[frame_id].gravity_info.SetGravity(gravity); } } - std::cout << std::endl; LOG(INFO) << "Number of rectified frames: " << counter_rect << " / " - << error_prone_frames.size() << std::endl; + << error_prone_frames.size(); } void GravityRefiner::IdentifyErrorProneGravity( @@ -179,7 +177,6 @@ void GravityRefiner::IdentifyErrorProneGravity( error_prone_frames.insert(frame_id); } } - LOG(INFO) << "Number of error prone frames: " << error_prone_frames.size() - << std::endl; + LOG(INFO) << "Number of error prone frames: " << error_prone_frames.size(); } } // namespace glomap diff --git a/glomap/io/colmap_converter.cc b/glomap/io/colmap_converter.cc index 3fcb0959..13dc8308 100644 --- a/glomap/io/colmap_converter.cc +++ b/glomap/io/colmap_converter.cc @@ -361,7 +361,7 @@ void ConvertDatabaseToGlomap(const colmap::Database& database, // Initialize the image pair auto ite = image_pairs.insert( - std::make_pair(ImagePair::ImagePairToPairId(image_id1, image_id2), + std::make_pair(colmap::ImagePairToPairId(image_id1, image_id2), ImagePair(image_id1, image_id2))); ImagePair& image_pair = ite.first->second; @@ -419,8 +419,6 @@ void ConvertDatabaseToGlomap(const colmap::Database& database, } image_pair.matches.conservativeResize(count, 2); } - std::cout << std::endl; - LOG(INFO) << "Pairs read done. " << invalid_count << " / " << view_graph.image_pairs.size() << " are invalid"; } diff --git a/glomap/io/pose_io.cc b/glomap/io/pose_io.cc index 49835a8c..d2349f3c 100644 --- a/glomap/io/pose_io.cc +++ b/glomap/io/pose_io.cc @@ -56,7 +56,7 @@ void ReadRelPose(const std::string& file_path, image_t index1 = name_idx[file1]; image_t index2 = name_idx[file2]; - image_pair_t pair_id = ImagePair::ImagePairToPairId(index1, index2); + const image_pair_t pair_id = colmap::ImagePairToPairId(index1, index2); // rotation Rigid3d pose_rel; @@ -81,7 +81,7 @@ void ReadRelPose(const std::string& file_path, } counter++; } - LOG(INFO) << counter << " relpose are loaded" << std::endl; + LOG(INFO) << counter << " relative poses are loaded"; } void ReadRelWeight(const std::string& file_path, @@ -118,7 +118,7 @@ void ReadRelWeight(const std::string& file_path, image_t index1 = name_idx[file1]; image_t index2 = name_idx[file2]; - image_pair_t pair_id = ImagePair::ImagePairToPairId(index1, index2); + image_pair_t pair_id = colmap::ImagePairToPairId(index1, index2); if (view_graph.image_pairs.find(pair_id) == view_graph.image_pairs.end()) continue; @@ -127,7 +127,7 @@ void ReadRelWeight(const std::string& file_path, view_graph.image_pairs[pair_id].weight = std::stod(item); counter++; } - LOG(INFO) << counter << " weights are used are loaded" << std::endl; + LOG(INFO) << counter << " weights are used are loaded"; } // TODO: now, we only store 1 single gravity per rig. @@ -172,7 +172,7 @@ void ReadGravity(const std::string& gravity_path, } } } - LOG(INFO) << counter << " images are loaded with gravity" << std::endl; + LOG(INFO) << counter << " images are loaded with gravity"; } void WriteGlobalRotation(const std::string& file_path, @@ -185,7 +185,7 @@ void WriteGlobalRotation(const std::string& file_path, } } for (const auto& image_id : existing_images) { - const auto image = images.at(image_id); + const auto& image = images.at(image_id); if (!image.IsRegistered()) continue; file << image.file_name; Rigid3d cam_from_world = image.CamFromWorld(); @@ -205,8 +205,8 @@ void WriteRelPose(const std::string& file_path, std::map name_pair; for (const auto& [pair_id, image_pair] : view_graph.image_pairs) { if (image_pair.is_valid) { - const auto image1 = images.at(image_pair.image_id1); - const auto image2 = images.at(image_pair.image_id2); + const auto& image1 = images.at(image_pair.image_id1); + const auto& image2 = images.at(image_pair.image_id2); name_pair[image1.file_name + " " + image2.file_name] = pair_id; } } @@ -226,6 +226,6 @@ void WriteRelPose(const std::string& file_path, file << "\n"; } - LOG(INFO) << name_pair.size() << " relpose are written" << std::endl; + LOG(INFO) << name_pair.size() << " relpose are written"; } -} // namespace glomap \ No newline at end of file +} // namespace glomap diff --git a/glomap/processors/image_pair_inliers.cc b/glomap/processors/image_pair_inliers.cc index c32878f1..a0e14594 100644 --- a/glomap/processors/image_pair_inliers.cc +++ b/glomap/processors/image_pair_inliers.cc @@ -212,4 +212,4 @@ void ImagePairsInlierCount(ViewGraph& view_graph, } } -} // namespace glomap \ No newline at end of file +} // namespace glomap diff --git a/glomap/processors/reconstruction_pruning.cc b/glomap/processors/reconstruction_pruning.cc index 014a10e5..6fc0eb2f 100644 --- a/glomap/processors/reconstruction_pruning.cc +++ b/glomap/processors/reconstruction_pruning.cc @@ -23,8 +23,8 @@ image_t PruneWeaklyConnectedImages(std::unordered_map& frames, image_t image_id2 = track.observations[j].first; frame_t frame_id2 = images[image_id2].frame_id; if (frame_id1 == frame_id2) continue; - image_pair_t pair_id = - ImagePair::ImagePairToPairId(frame_id1, frame_id2); + const image_pair_t pair_id = + colmap::ImagePairToPairId(frame_id1, frame_id2); if (pair_covisibility_count.find(pair_id) == pair_covisibility_count.end()) { pair_covisibility_count[pair_id] = 1; @@ -44,8 +44,7 @@ image_t PruneWeaklyConnectedImages(std::unordered_map& frames, // then require each pair to have at least 5 points if (count >= 5) { counter++; - image_t image_id1, image_id2; - ImagePair::PairIdToImagePair(pair_id, image_id1, image_id2); + const auto [image_id1, image_id2] = colmap::PairIdToImagePair(pair_id); if (frame_observation_count[image_id1] < min_num_observations || frame_observation_count[image_id2] < min_num_observations) @@ -76,10 +75,9 @@ image_t PruneWeaklyConnectedImages(std::unordered_map& frames, ViewGraph visibility_graph; for (auto& [pair_id, image_pair] : visibility_graph_frame.image_pairs) { - frame_t frame_id1, frame_id2; - ImagePair::PairIdToImagePair(pair_id, frame_id1, frame_id2); - image_t image_id1 = frame_id_to_begin_img[frame_id1]; - image_t image_id2 = frame_id_to_begin_img[frame_id2]; + const auto [frame_id1, frame_id2] = colmap::PairIdToImagePair(pair_id); + const image_t image_id1 = frame_id_to_begin_img[frame_id1]; + const image_t image_id2 = frame_id_to_begin_img[frame_id2]; visibility_graph.image_pairs.insert( std::make_pair(pair_id, ImagePair(image_id1, image_id2))); visibility_graph.image_pairs[pair_id].weight = image_pair.weight; @@ -96,7 +94,7 @@ image_t PruneWeaklyConnectedImages(std::unordered_map& frames, if (image_id == begin_image_id || images.find(image_id) == images.end()) continue; image_pair_t pair_id = - ImagePair::ImagePairToPairId(begin_image_id, image_id); + colmap::ImagePairToPairId(begin_image_id, image_id); visibility_graph.image_pairs.insert( std::make_pair(pair_id, ImagePair(begin_image_id, image_id))); @@ -132,4 +130,4 @@ image_t PruneWeaklyConnectedImages(std::unordered_map& frames, // return visibility_graph.MarkConnectedComponents(images, min_num_images); } -} // namespace glomap \ No newline at end of file +} // namespace glomap diff --git a/glomap/processors/relpose_filter.cc b/glomap/processors/relpose_filter.cc index 8af7cf80..1f14cd07 100644 --- a/glomap/processors/relpose_filter.cc +++ b/glomap/processors/relpose_filter.cc @@ -64,4 +64,4 @@ void RelPoseFilter::FilterInlierRatio(ViewGraph& view_graph, << " relative poses with inlier ratio < " << min_inlier_ratio; } -} // namespace glomap \ No newline at end of file +} // namespace glomap diff --git a/glomap/processors/view_graph_manipulation.cc b/glomap/processors/view_graph_manipulation.cc index e63a57a2..24c36726 100644 --- a/glomap/processors/view_graph_manipulation.cc +++ b/glomap/processors/view_graph_manipulation.cc @@ -17,7 +17,7 @@ image_pair_t ViewGraphManipulater::SparsifyGraph( // Keep track of chosen edges std::unordered_set chosen_edges; const std::unordered_map>& - adjacency_list = view_graph.GetAdjacencyList(); + adjacency_list = view_graph.CreateImageAdjacencyList(); // Here, the average is the mean of the degrees double average_degree = 0; @@ -47,6 +47,7 @@ image_pair_t ViewGraphManipulater::SparsifyGraph( continue; } + // TODO: Replace rand() with thread-safe random number generator. if (rand() / double(RAND_MAX) < (expected_degree * average_degree) / (degree1 * degree2)) { chosen_edges.insert(pair_id); @@ -208,9 +209,7 @@ void ViewGraphManipulater::UpdateImagePairsConfig( // pairs are valid, then set the camera to valid std::unordered_map camera_validity; for (auto& [camera_id, counter] : camera_counter) { - if (counter.first == 0) { - camera_validity[camera_id] = false; - } else if (counter.second * 1. / counter.first > 0.5) { + if (counter.second * 1. / counter.first > 0.5) { camera_validity[camera_id] = true; } else { camera_validity[camera_id] = false; diff --git a/glomap/scene/image_pair.h b/glomap/scene/image_pair.h index 67bf2ed3..31b01386 100644 --- a/glomap/scene/image_pair.h +++ b/glomap/scene/image_pair.h @@ -9,19 +9,24 @@ namespace glomap { -// FUTURE: add covariance to the relative pose +// TODO: add covariance to the relative pose struct ImagePair { - ImagePair() : pair_id(-1), image_id1(-1), image_id2(-1) {} - ImagePair(image_t img_id1, image_t img_id2, Rigid3d pose_rel = Rigid3d()) - : pair_id(ImagePairToPairId(img_id1, img_id2)), - image_id1(img_id1), - image_id2(img_id2), - cam2_from_cam1(pose_rel) {} + ImagePair() + : image_id1(colmap::kInvalidImageId), + image_id2(colmap::kInvalidImageId), + pair_id(colmap::kInvalidImagePairId) {} + ImagePair(image_t image_id1, + image_t image_id2, + Rigid3d cam2_from_cam1 = Rigid3d()) + : image_id1(image_id1), + image_id2(image_id2), + pair_id(colmap::ImagePairToPairId(image_id1, image_id2)), + cam2_from_cam1(cam2_from_cam1) {} // Ids are kept constant - const image_pair_t pair_id; const image_t image_id1; const image_t image_id2; + const image_pair_t pair_id; // indicator whether the image pair is valid bool is_valid = true; @@ -40,7 +45,7 @@ struct ImagePair { Eigen::Matrix3d H = Eigen::Matrix3d::Zero(); // Relative pose. - Rigid3d cam2_from_cam1; + Rigid3d cam2_from_cam1 = Rigid3d(); // Matches between the two images. // First column is the index of the feature in the first image. @@ -49,27 +54,6 @@ struct ImagePair { // Row index of inliers in the matches matrix. std::vector inliers; - - static inline image_pair_t ImagePairToPairId(const image_t image_id1, - const image_t image_id2); - - static inline void PairIdToImagePair(const image_pair_t pair_id, - image_t& image_id1, - image_t& image_id2); }; -image_pair_t ImagePair::ImagePairToPairId(const image_t image_id1, - const image_t image_id2) { - return colmap::ImagePairToPairId(image_id1, image_id2); -} - -void ImagePair::PairIdToImagePair(const image_pair_t pair_id, - image_t& image_id1, - image_t& image_id2) { - std::pair image_id_pair = - colmap::PairIdToImagePair(pair_id); - image_id1 = image_id_pair.first; - image_id2 = image_id_pair.second; -} - } // namespace glomap diff --git a/glomap/scene/view_graph.cc b/glomap/scene/view_graph.cc index 60009116..5e8e855e 100644 --- a/glomap/scene/view_graph.cc +++ b/glomap/scene/view_graph.cc @@ -3,18 +3,65 @@ #include namespace glomap { +namespace { + +void BreadthFirstSearch( + const std::unordered_map>& + adjacency_list, + image_t root, + std::unordered_map& visited, + std::unordered_set& component) { + std::queue queue; + queue.push(root); + visited[root] = true; + component.insert(root); + + while (!queue.empty()) { + const image_t curr = queue.front(); + queue.pop(); + + for (const image_t neighbor : adjacency_list.at(curr)) { + if (!visited[neighbor]) { + queue.push(neighbor); + visited[neighbor] = true; + component.insert(neighbor); + } + } + } +} + +std::vector> FindConnectedComponents( + const std::unordered_map>& + adjacency_list) { + std::vector> connected_components; + std::unordered_map visited; + visited.reserve(adjacency_list.size()); + for (const auto& [frame_id, neighbors] : adjacency_list) { + visited[frame_id] = false; + } + + for (auto& [frame_id, _] : adjacency_list) { + if (!visited[frame_id]) { + std::unordered_set component; + BreadthFirstSearch(adjacency_list, frame_id, visited, component); + connected_components.push_back(std::move(component)); + } + } + + return connected_components; +} + +} // namespace int ViewGraph::KeepLargestConnectedComponents( std::unordered_map& frames, std::unordered_map& images) { - EstablishAdjacencyList(); - EstablishAdjacencyListFrame(images); - - int num_comp = FindConnectedComponent(); + const std::vector> connected_components = + FindConnectedComponents(CreateFrameAdjacencyList(images)); int max_idx = -1; int max_img = 0; - for (int comp = 0; comp < num_comp; comp++) { + for (int comp = 0; comp < connected_components.size(); comp++) { if (connected_components[comp].size() > max_img) { max_img = connected_components[comp].size(); max_idx = comp; @@ -23,7 +70,8 @@ int ViewGraph::KeepLargestConnectedComponents( if (max_img == 0) return 0; - std::unordered_set largest_component = connected_components[max_idx]; + const std::unordered_set& largest_component = + connected_components[max_idx]; // Set all frames to not registered for (auto& [frame_id, frame] : frames) { @@ -34,13 +82,11 @@ int ViewGraph::KeepLargestConnectedComponents( frames[frame_id].is_registered = true; } // set all pairs not in the largest component to invalid - num_pairs = 0; for (auto& [pair_id, image_pair] : image_pairs) { if (!images[image_pair.image_id1].IsRegistered() || !images[image_pair.image_id2].IsRegistered()) { image_pair.is_valid = false; } - if (image_pair.is_valid) num_pairs++; } for (auto& [image_id, image] : images) { @@ -49,32 +95,13 @@ int ViewGraph::KeepLargestConnectedComponents( return max_img; } -int ViewGraph::FindConnectedComponent() { - connected_components.clear(); - std::unordered_map visited; - for (auto& [frame_id, neighbors] : adjacency_list_frame) { - visited[frame_id] = false; - } - - for (auto& [frame_id, neighbors] : adjacency_list_frame) { - if (!visited[frame_id]) { - std::unordered_set component; - BFS(frame_id, visited, component); - connected_components.push_back(component); - } - } - - return connected_components.size(); -} - int ViewGraph::MarkConnectedComponents( std::unordered_map& frames, std::unordered_map& images, int min_num_img) { - EstablishAdjacencyList(); - EstablishAdjacencyListFrame(images); - - int num_comp = FindConnectedComponent(); + const std::vector> connected_components = + FindConnectedComponents(CreateFrameAdjacencyList(images)); + const int num_comp = connected_components.size(); std::vector> cluster_num_img(num_comp); for (int comp = 0; comp < num_comp; comp++) { @@ -97,48 +124,31 @@ int ViewGraph::MarkConnectedComponents( return comp; } -void ViewGraph::BFS(image_t root, - std::unordered_map& visited, - std::unordered_set& component) { - std::queue q; - q.push(root); - visited[root] = true; - component.insert(root); - - while (!q.empty()) { - image_t curr = q.front(); - q.pop(); - - for (image_t neighbor : adjacency_list_frame[curr]) { - if (!visited[neighbor]) { - q.push(neighbor); - visited[neighbor] = true; - component.insert(neighbor); - } - } - } -} - -void ViewGraph::EstablishAdjacencyList() { - adjacency_list.clear(); - for (auto& [pair_id, image_pair] : image_pairs) { +std::unordered_map> +ViewGraph::CreateImageAdjacencyList() const { + std::unordered_map> adjacency_list; + for (const auto& [_, image_pair] : image_pairs) { if (image_pair.is_valid) { adjacency_list[image_pair.image_id1].insert(image_pair.image_id2); adjacency_list[image_pair.image_id2].insert(image_pair.image_id1); } } + return adjacency_list; } -void ViewGraph::EstablishAdjacencyListFrame( - std::unordered_map& images) { - adjacency_list_frame.clear(); - for (auto& [pair_id, image_pair] : image_pairs) { +std::unordered_map> +ViewGraph::CreateFrameAdjacencyList( + const std::unordered_map& images) const { + std::unordered_map> adjacency_list; + for (const auto& [_, image_pair] : image_pairs) { if (image_pair.is_valid) { - frame_t frame_id1 = images[image_pair.image_id1].frame_id; - frame_t frame_id2 = images[image_pair.image_id2].frame_id; - adjacency_list_frame[frame_id1].insert(frame_id2); - adjacency_list_frame[frame_id2].insert(frame_id1); + const frame_t frame_id1 = images.at(image_pair.image_id1).frame_id; + const frame_t frame_id2 = images.at(image_pair.image_id2).frame_id; + adjacency_list[frame_id1].insert(frame_id2); + adjacency_list[frame_id2].insert(frame_id1); } } + return adjacency_list; } + } // namespace glomap diff --git a/glomap/scene/view_graph.h b/glomap/scene/view_graph.h index 21c1a16c..74497e24 100644 --- a/glomap/scene/view_graph.h +++ b/glomap/scene/view_graph.h @@ -1,73 +1,37 @@ #pragma once -#include "glomap/scene/camera.h" #include "glomap/scene/image.h" #include "glomap/scene/image_pair.h" #include "glomap/scene/types.h" -#include "glomap/types.h" + +#include +#include namespace glomap { -class ViewGraph { - public: - // Methods - inline void RemoveInvalidPair(image_pair_t pair_id); +struct ViewGraph { + std::unordered_map image_pairs; + + // Create the adjacency list for the images in the view graph. + std::unordered_map> + CreateImageAdjacencyList() const; + + // Create the adjacency list for the frames in the view graph. + std::unordered_map> + CreateFrameAdjacencyList( + const std::unordered_map& images) const; - // Mark the image which is not connected to any other images as not registered - // Return: the number of images in the largest connected component + // Mark the images which are not connected to any other images as not + // registered Returns the number of images in the largest connected component. int KeepLargestConnectedComponents( std::unordered_map& frames, std::unordered_map& images); - // Mark the cluster of the cameras (cluster_id sort by the the number of - // images) + // Mark connected clusters of images, where the cluster_id is sorted by the + // the number of images. int MarkConnectedComponents(std::unordered_map& frames, std::unordered_map& images, int min_num_img = -1); - - // Establish the adjacency list - void EstablishAdjacencyList(); - - // Establish the frame based adjacency list - void EstablishAdjacencyListFrame(std::unordered_map& images); - - inline const std::unordered_map>& - GetAdjacencyList() const; - inline const std::unordered_map>& - GetAdjacencyListFrame() const; - - // Data - std::unordered_map image_pairs; - - image_t num_images = 0; - image_pair_t num_pairs = 0; - - private: - int FindConnectedComponent(); - - void BFS(image_t root, - std::unordered_map& visited, - std::unordered_set& component); - - // Data for processing - std::unordered_map> adjacency_list; - std::unordered_map> adjacency_list_frame; - std::vector> connected_components; }; -const std::unordered_map>& -ViewGraph::GetAdjacencyList() const { - return adjacency_list; -} - -const std::unordered_map>& -ViewGraph::GetAdjacencyListFrame() const { - return adjacency_list_frame; -} - -void ViewGraph::RemoveInvalidPair(image_pair_t pair_id) { - ImagePair& pair = image_pairs.at(pair_id); - pair.is_valid = false; -} - } // namespace glomap From fac6a3f0731e6ca51bc8e8ce3a2b7a72dcded184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 22 Nov 2025 10:17:08 +0100 Subject: [PATCH 3/4] d --- glomap/thirdparty/CMakeLists.txt | 75 -------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 glomap/thirdparty/CMakeLists.txt diff --git a/glomap/thirdparty/CMakeLists.txt b/glomap/thirdparty/CMakeLists.txt deleted file mode 100644 index 6d42e9fd..00000000 --- a/glomap/thirdparty/CMakeLists.txt +++ /dev/null @@ -1,75 +0,0 @@ -# Determine project compiler. -if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(IS_MSVC TRUE) -endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(IS_GNU TRUE) -endif() -if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") - set(IS_CLANG TRUE) -endif() - -# Only show moderate warnings for external library code. -if(IS_MSVC) - if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") - string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - elseif(CMAKE_CXX_FLAGS MATCHES "/Wall") - string(REGEX REPLACE "/Wall" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - endif() - if(CMAKE_C_FLAGS MATCHES "/W[0-4]") - string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - elseif(CMAKE_C_FLAGS MATCHES "/Wall") - string(REGEX REPLACE "/Wall" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - endif() -elseif(IS_GNU OR IS_CLANG) - if(CMAKE_CXX_FLAGS MATCHES "-Wall") - string(REGEX REPLACE "-Wall" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - endif() - if(CMAKE_C_FLAGS MATCHES "-Wall") - string(REGEX REPLACE "-Wall" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - set(REMOVED_WARNING_LEVEL TRUE) - endif() -endif() - -if(IS_MSVC) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0") -elseif(IS_GNU OR IS_CLANG) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") -endif() - -include(FetchContent) -FetchContent_Declare(PoseLib - GIT_REPOSITORY https://github.com/PoseLib/PoseLib.git - GIT_TAG 7e9f5f53372e43f89655040d4dfc4a00e5ace11c # 2.0.5 - EXCLUDE_FROM_ALL - SYSTEM -) -message(STATUS "Configuring PoseLib...") -if (FETCH_POSELIB) - FetchContent_MakeAvailable(PoseLib) -else() - find_package(PoseLib REQUIRED) -endif() -message(STATUS "Configuring PoseLib... done") - -FetchContent_Declare(COLMAP - GIT_REPOSITORY https://github.com/colmap/colmap.git - GIT_TAG b6b7b54eca6078070f73a3f0a084f79c629a6f10 # Nov 20, 2025 - EXCLUDE_FROM_ALL -) -message(STATUS "Configuring COLMAP...") -set(UNINSTALL_ENABLED OFF CACHE INTERNAL "") -set(GUI_ENABLED OFF CACHE INTERNAL "") -if (FETCH_COLMAP) - FetchContent_MakeAvailable(COLMAP) -else() - find_package(COLMAP REQUIRED) -endif() -message(STATUS "Configuring COLMAP... done") From f4a0c72aa74439139e8875e39ff437b99ce04f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 22 Nov 2025 10:18:34 +0100 Subject: [PATCH 4/4] d --- glomap/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/glomap/CMakeLists.txt b/glomap/CMakeLists.txt index 86a09f64..69b7c761 100644 --- a/glomap/CMakeLists.txt +++ b/glomap/CMakeLists.txt @@ -1,5 +1,3 @@ -add_subdirectory(thirdparty) - set(SOURCES controllers/global_mapper.cc controllers/option_manager.cc