diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..10fb834 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,147 @@ +name: Build and publish wheels + +on: + push: + branches: + - main + pull_request: + types: [ assigned, opened, synchronize, reopened ] + release: + types: [ published, edited ] + workflow_dispatch: + +jobs: + build: + name: Build on ${{ matrix.config.os }} ${{ matrix.config.arch }} + runs-on: ${{ matrix.config.os }} + strategy: + matrix: + config: [ + {os: ubuntu-latest}, + {os: macos-13, arch: x86_64}, + {os: macos-13, arch: arm64}, + {os: windows-latest}, + ] + env: + COMPILER_CACHE_VERSION: 1 + COMPILER_CACHE_DIR: ${{ github.workspace }}/compiler-cache + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + id: cache-builds + with: + key: v${{ env.COMPILER_CACHE_VERSION }}-${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ github.run_id }}-${{ github.run_number }} + restore-keys: v${{ env.COMPILER_CACHE_VERSION }}-${{ matrix.config.os }}-${{ matrix.config.arch }} + path: ${{ env.COMPILER_CACHE_DIR }} + - name: Set env (macOS) + if: runner.os == 'macOS' + run: | + if [[ ${{ matrix.config.arch }} == "x86_64" ]]; then + VCPKG_TARGET_TRIPLET="x64-osx" + elif [[ ${{ matrix.config.arch }} == "arm64" ]]; then + VCPKG_TARGET_TRIPLET="arm64-osx-release" + else + exit 1 + fi + echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "$GITHUB_ENV" + + VCPKG_INSTALLATION_ROOT="/Users/runner/work/vcpkg" + CMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" + CMAKE_OSX_ARCHITECTURES=${{ matrix.config.arch }} + echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "$GITHUB_ENV" + echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "$GITHUB_ENV" + echo "CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}" >> "$GITHUB_ENV" + + # Fix: cibuildhweel cannot interpolate env variables. + CONFIG_SETTINGS="cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" + CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" + CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}" + echo "CIBW_CONFIG_SETTINGS_MACOS=${CONFIG_SETTINGS}" >> "$GITHUB_ENV" + + # vcpkg binary caching + VCPKG_CACHE_DIR="${COMPILER_CACHE_DIR}/vcpkg" + VCPKG_BINARY_SOURCES="clear;files,${VCPKG_CACHE_DIR},readwrite" + echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "$GITHUB_ENV" + - name: Set env (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $VCPKG_INSTALLATION_ROOT="${{ github.workspace }}/vcpkg" + echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "${env:GITHUB_ENV}" + $CMAKE_TOOLCHAIN_FILE = "${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" + echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "${env:GITHUB_ENV}" + $VCPKG_TARGET_TRIPLET = "x64-windows" + echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "${env:GITHUB_ENV}" + + # Fix: cibuildhweel cannot interpolate env variables. + $CMAKE_TOOLCHAIN_FILE = $CMAKE_TOOLCHAIN_FILE.replace('\', '/') + $CONFIG_SETTINGS = "cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" + $CONFIG_SETTINGS = "${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" + echo "CIBW_CONFIG_SETTINGS_WINDOWS=${CONFIG_SETTINGS}" >> "${env:GITHUB_ENV}" + $CIBW_REPAIR_WHEEL_COMMAND = "delvewheel repair -v --add-path ${VCPKG_INSTALLATION_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/bin -w {dest_dir} {wheel}" + echo "CIBW_REPAIR_WHEEL_COMMAND_WINDOWS=${CIBW_REPAIR_WHEEL_COMMAND}" >> "${env:GITHUB_ENV}" + + # vcpkg binary caching + $VCPKG_CACHE_DIR = "${env:COMPILER_CACHE_DIR}/vcpkg" + $VCPKG_BINARY_SOURCES = "clear;files,${VCPKG_CACHE_DIR},readwrite" + echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "${env:GITHUB_ENV}" + - name: Set env (Ubuntu) + if: runner.os == 'Linux' + run: | + VCPKG_TARGET_TRIPLET="x64-linux-release" + echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "$GITHUB_ENV" + + VCPKG_INSTALLATION_ROOT="${{ github.workspace }}/vcpkg" + CMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" + echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "$GITHUB_ENV" + echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "$GITHUB_ENV" + + # Fix: cibuildhweel cannot interpolate env variables. + CONFIG_SETTINGS="cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" + CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" + echo "CIBW_CONFIG_SETTINGS_LINUX=${CONFIG_SETTINGS}" >> "$GITHUB_ENV" + + # Remap caching paths to the container + CONTAINER_COMPILER_CACHE_DIR="/compiler-cache" + CIBW_CONTAINER_ENGINE="docker; create_args: -v ${COMPILER_CACHE_DIR}:${CONTAINER_COMPILER_CACHE_DIR}" + echo "CIBW_CONTAINER_ENGINE=${CIBW_CONTAINER_ENGINE}" >> "$GITHUB_ENV" + echo "CONTAINER_COMPILER_CACHE_DIR=${CONTAINER_COMPILER_CACHE_DIR}" >> "$GITHUB_ENV" + + # vcpkg binary caching + VCPKG_CACHE_DIR="${CONTAINER_COMPILER_CACHE_DIR}/vcpkg" + VCPKG_BINARY_SOURCES="clear;files,${VCPKG_CACHE_DIR},readwrite" + echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "$GITHUB_ENV" + + CIBW_ENVIRONMENT_PASS_LINUX="VCPKG_TARGET_TRIPLET VCPKG_INSTALLATION_ROOT CMAKE_TOOLCHAIN_FILE VCPKG_BINARY_SOURCES CONTAINER_COMPILER_CACHE_DIR" + echo "CIBW_ENVIRONMENT_PASS_LINUX=${CIBW_ENVIRONMENT_PASS_LINUX}" >> "$GITHUB_ENV" + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.5 + env: + CIBW_ARCHS_MACOS: ${{ matrix.config.arch }} + - name: Archive wheels + uses: actions/upload-artifact@v4 + with: + name: pyceres-${{ matrix.config.os }}-${{ matrix.config.arch }} + path: wheelhouse/pyceres-*.whl + + pypi-publish: + name: Publish wheels to PyPI + needs: build + runs-on: ubuntu-latest + # We publish the wheel to pypi when a new tag is pushed, + # either by creating a new GitHub release or explictly with `git tag` + if: ${{ github.event_name == 'release' || startsWith(github.ref, 'refs/tags') }} + steps: + - name: Download wheels + uses: actions/download-artifact@v4 + with: + path: ./artifacts/ + - name: Move wheels + run: mkdir ./wheelhouse && mv ./artifacts/**/*.whl ./wheelhouse/ + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip_existing: true + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: ./wheelhouse/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 904d731..3574a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,25 @@ cmake_minimum_required(VERSION 3.10) project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION}) -if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) - set(CMAKE_CUDA_ARCHITECTURES "native") -endif() -find_package(COLMAP REQUIRED) -if(${COLMAP_VERSION} VERSION_LESS "3.9.0") - message( SEND_ERROR "COLMAP version >= 3.9.0 required, found ${COLMAP_VERSION}." ) +find_package(Ceres REQUIRED) +if(NOT TARGET Ceres::ceres) + # Older Ceres versions don't come with an imported interface target. + add_library(Ceres::ceres INTERFACE IMPORTED) + target_include_directories( + Ceres::ceres INTERFACE ${CERES_INCLUDE_DIRS}) + target_link_libraries( + Ceres::ceres INTERFACE ${CERES_LIBRARIES}) endif() if(${CERES_VERSION} VERSION_LESS "2.1.0") message( SEND_ERROR "Ceres version >= 2.1 required, found ${CERES_VERSION}." ) endif() - find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) find_package(pybind11 2.11.1 REQUIRED) pybind11_add_module(pyceres _pyceres/bindings.cc) target_include_directories(pyceres PRIVATE ${PROJECT_SOURCE_DIR}) -target_link_libraries(pyceres PRIVATE colmap::colmap glog::glog Ceres::ceres) +target_link_libraries(pyceres PRIVATE glog::glog Ceres::ceres) target_compile_definitions(pyceres PRIVATE VERSION_INFO="${PROJECT_VERSION}") install(TARGETS pyceres LIBRARY DESTINATION .) diff --git a/Dockerfile b/Dockerfile index 2d9c2e0..b9c5469 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,6 @@ ARG UBUNTU_VERSION=22.04 ARG NVIDIA_CUDA_VERSION=12.3.1 FROM nvidia/cuda:${NVIDIA_CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION} as builder -ARG COLMAP_VERSION=3.9.1 -ARG CUDA_ARCHITECTURES=70 -ENV CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} ENV QT_XCB_GL_INTEGRATION=xcb_egl # Prevent stop building ubuntu at time zone selection. @@ -17,21 +14,12 @@ RUN apt-get update && \ cmake \ ninja-build \ build-essential \ - libboost-program-options-dev \ - libboost-filesystem-dev \ - libboost-graph-dev \ - libboost-system-dev \ libeigen3-dev \ - libflann-dev \ - libfreeimage-dev \ - libmetis-dev \ libgoogle-glog-dev \ + libgflags-dev \ libgtest-dev \ - libsqlite3-dev \ - libglew-dev \ - qtbase5-dev \ - libqt5opengl5-dev \ - libcgal-dev \ + libatlas-base-dev \ + libsuitesparse-dev \ python-is-python3 \ python3-minimal \ python3-pip \ @@ -48,48 +36,7 @@ RUN apt-get install -y --no-install-recommends --no-install-suggests wget && \ -DCMAKE_INSTALL_PREFIX=/ceres_installed && \ ninja install RUN cp -r /ceres_installed/* /usr/local/ - -# Install Colmap. -RUN wget "https://github.com/colmap/colmap/archive/refs/tags/${COLMAP_VERSION}.tar.gz" -O colmap-${COLMAP_VERSION}.tar.gz && \ - tar zxvf colmap-${COLMAP_VERSION}.tar.gz && \ - mkdir colmap-build && \ - cd colmap-build && \ - cmake ../colmap-${COLMAP_VERSION} -GNinja \ - -DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} \ - -DCMAKE_INSTALL_PREFIX=/colmap_installed && \ - ninja install -RUN cp -r /colmap_installed/* /usr/local/ - # Build pyceres. ADD . /pyceres WORKDIR /pyceres -RUN pip install --upgrade pip -RUN pip wheel . --no-deps -w dist-wheel -vv --config-settings=cmake.define.CMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} && \ - whl_path=$(find dist-wheel/ -name "*.whl") && \ - echo $whl_path >dist-wheel/whl_path.txt - - -# -# Runtime stage. -# -FROM nvidia/cuda:${NVIDIA_CUDA_VERSION}-runtime-ubuntu${UBUNTU_VERSION} as runtime - -# Install minimal runtime dependencies. -RUN apt-get update && \ - apt-get install -y --no-install-recommends --no-install-suggests \ - libgoogle-glog0v5 \ - python-is-python3 \ - python3-minimal \ - python3-pip - -# Copy installed libraries in builder stage. -COPY --from=builder /ceres_installed/ /usr/local/ -COPY --from=builder /colmap_installed/ /usr/local/ - -# Install pyceres. -COPY --from=builder /pyceres/dist-wheel /tmp/dist-wheel -RUN cd /tmp && whl_path=$(cat dist-wheel/whl_path.txt) && pip install $whl_path -RUN rm -rfv /tmp/* - -# Verify if pyceres library is accessible from python. -RUN python -c "import pyceres" +RUN pip install . -vv diff --git a/README.md b/README.md index b9486a5..b9a7f1f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,13 @@ This repository provides minimal Python bindings for the [Ceres Solver](http://c ## Installation -1. Install [COLMAP 3.9.1](https://colmap.github.io/) +Wheels for Python 8/9/10 on Linux, macOS 10+ (both Intel and Apple Silicon), and Windows can be installed using pip: +```bash +pip install pyceres +``` +To build from source, follow the following steps: +1. Install the Ceres Solver following [the official instructions](http://ceres-solver.org/installation.html). 2. Clone the repository and build the package: ```sh @@ -14,36 +19,22 @@ cd pyceres python -m pip install . ``` -### Docker image - Alternatively, you can build the Docker image: ```sh -export COLMAP_VERSION=3.9.1 -export CUDA_ARCHITECTURES=70 -docker build -t pyceres \ - --build-arg COLMAP_VERSION=${COLMAP_VERSION} \ - --build-arg CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} \ - -f Dockerfile . +docker build -t pyceres -f Dockerfile . ``` ## Factor graph optimization -For now we support the following cost functions, defined in `_pyceres/factors/`: -- camera reprojection error (with fixed or variable pose) -- rig reprojection error (with fixed or variable rig extrinsics) -- relative pose prior -- absolute pose prior - -All factors support basic observation covariances. Reprojection error costs rely on camera models defined in COLMAP. Absolute poses are represented as quaternions and are expressed in the sensor frame, so are pose residuals, which use the right-hand convention as in the [GTSAM library](https://github.com/borglab/gtsam). - -## Examples -See the Jupyter notebooks in `examples/`. +Factors may be defined in Python (see [`examples/test_python_cost.py`](./examples/test_python_cost.py)) or in C++ with associated Python bindings. +[PyCOLMAP](https://github.com/colmap/colmap/tree/main/pycolmap) provides the following cost functions in `pycolmap.cost_functions`: +- reprojection error for different camera models, with fixed or variable pose and 3D points +- reprojection error for multi-camera rigs, with fixed or variable rig extrinsics +- error of absolute and relative poses +- Sampson error for epipolar geometry -## TODO -- [ ] Define a clean interface for covariances, like in GTSAM -- [ ] Add bindings for Ceres covariance estimation -- [ ] Proper benchmark against GTSAM +See [`examples/`](./examples/) to use these factors. ## Credits -The core bindings were written by Nikolaus Mitchell for [ceres_python_bindings](https://github.com/Edwinem/ceres_python_bindings) and later adapted by [Philipp Lindenberger](https://github.com/Phil26AT) for [pixel-perfect-sfm](https://github.com/cvg/pixel-perfect-sfm). +Pyceres was inspired by the work of Nikolaus Mitchell for [ceres_python_bindings](https://github.com/Edwinem/ceres_python_bindings) and is maintained by [Philipp Lindenberger](https://github.com/Phil26AT) and [Paul-Edouard Sarlin](https://psarlin.com/). diff --git a/_pyceres/bindings.cc b/_pyceres/bindings.cc index 64f4644..740ab71 100644 --- a/_pyceres/bindings.cc +++ b/_pyceres/bindings.cc @@ -1,8 +1,8 @@ #include "_pyceres/core/bindings.h" #include "_pyceres/factors/bindings.h" -#include "_pyceres/glog.h" #include "_pyceres/helpers.h" +#include "_pyceres/logging.h" #include #include @@ -12,12 +12,13 @@ namespace py = pybind11; PYBIND11_MODULE(pyceres, m) { - m.doc() = "PyCeres"; + m.doc() = "PyCeres - Python bindings for the Ceres solver."; + m.attr("__version__") = py::str(VERSION_INFO); py::add_ostream_redirect(m, "ostream_redirect"); - init_glog(m); - bind_core(m); + BindLogging(m); + BindCore(m); py::module_ f = m.def_submodule("factors"); - bind_factors(f); + BindFactors(f); } diff --git a/_pyceres/core/bindings.h b/_pyceres/core/bindings.h index 0b7a5dd..872bd44 100644 --- a/_pyceres/core/bindings.h +++ b/_pyceres/core/bindings.h @@ -1,33 +1,3 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/core/callbacks.h" @@ -43,13 +13,13 @@ namespace py = pybind11; -void bind_core(py::module& m) { - init_types(m); - init_callbacks(m); - init_covariance(m); - init_solver(m); - init_loss_functions(m); - init_cost_functions(m); - init_manifold(m); - init_problem(m); +void BindCore(py::module& m) { + BindTypes(m); + BindCallbacks(m); + BindCovariance(m); + BindSolver(m); + BindLossFunctions(m); + BindCostFunctions(m); + BindManifold(m); + BindProblem(m); } diff --git a/_pyceres/core/callbacks.h b/_pyceres/core/callbacks.h index 012b021..8616d2c 100644 --- a/_pyceres/core/callbacks.h +++ b/_pyceres/core/callbacks.h @@ -1,33 +1,3 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include @@ -72,7 +42,7 @@ class PyIterationCallback : public ceres::IterationCallback { PYBIND11_MAKE_OPAQUE(std::vector); -void init_callbacks(py::module& m) { +void BindCallbacks(py::module& m) { py::class_(m, "IterationCallback") .def(py::init<>()) diff --git a/_pyceres/core/cost_functions.h b/_pyceres/core/cost_functions.h index 1781f9d..6564a41 100644 --- a/_pyceres/core/cost_functions.h +++ b/_pyceres/core/cost_functions.h @@ -1,37 +1,7 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include @@ -42,7 +12,7 @@ namespace py = pybind11; // This allows use to create python based cost functions. class PyCostFunction : public ceres::CostFunction { public: - /* Inherit the constructors */ + // Inherit the constructors. using ceres::CostFunction::CostFunction; using ceres::CostFunction::set_num_residuals; @@ -112,16 +82,15 @@ class PyCostFunction : public ceres::CostFunction { // Mutable so they can be modified by the const function. mutable std::vector> parameters_vec; mutable std::vector> jacobians_vec; - mutable bool cached_flag = false; // Flag used to determine if the vectors - // need to be resized - mutable py::array_t - residuals_wrap; // Buffer to contain the residuals - // pointer - mutable py::str no_copy; // Dummy variable for pybind11 to avoid copy - // copy + // Flag used to determine if the vectors need to be resized. + mutable bool cached_flag = false; + // Buffer to contain the residuals pointer. + mutable py::array_t residuals_wrap; + // Dummy variable for pybind11 to avoid a copy. + mutable py::str no_copy; }; -void init_cost_functions(py::module& m) { +void BindCostFunctions(py::module& m) { py::class_( m, "CostFunction") .def(py::init<>()) diff --git a/_pyceres/core/covariance.h b/_pyceres/core/covariance.h index 6fc97fc..05a9917 100644 --- a/_pyceres/core/covariance.h +++ b/_pyceres/core/covariance.h @@ -1,34 +1,32 @@ #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include namespace py = pybind11; -void init_covariance(py::module& m) { - using c_options = ceres::Covariance::Options; - auto co = py::class_(m, "CovarianceOptions") - .def(py::init<>()) - .def_property( - "num_threads", - [](const c_options& self) { return self.num_threads; }, - [](c_options& self, int n_threads) { - int effective_n_threads = - GetEffectiveNumThreads(n_threads); - self.num_threads = effective_n_threads; - }) - .def_readwrite("sparse_linear_algebra_library_type", - &c_options::sparse_linear_algebra_library_type) - .def_readwrite("algorithm_type", &c_options::algorithm_type) - .def_readwrite("min_reciprocal_condition_number", - &c_options::min_reciprocal_condition_number) - .def_readwrite("null_space_rank", &c_options::null_space_rank) - .def_readwrite("apply_loss_function", - &c_options::apply_loss_function); - make_dataclass(co); +void BindCovariance(py::module& m) { + using Options = ceres::Covariance::Options; + py::class_ PyOptions(m, "CovarianceOptions"); + PyOptions.def(py::init<>()) + .def_property( + "num_threads", + [](const Options& self) { return self.num_threads; }, + [](Options& self, int n_threads) { + int effective_n_threads = GetEffectiveNumThreads(n_threads); + self.num_threads = effective_n_threads; + }) + .def_readwrite("sparse_linear_algebra_library_type", + &Options::sparse_linear_algebra_library_type) + .def_readwrite("algorithm_type", &Options::algorithm_type) + .def_readwrite("min_reciprocal_condition_number", + &Options::min_reciprocal_condition_number) + .def_readwrite("null_space_rank", &Options::null_space_rank) + .def_readwrite("apply_loss_function", &Options::apply_loss_function); + MakeDataclass(PyOptions); py::class_(m, "Covariance") .def(py::init()) diff --git a/_pyceres/core/loss_functions.h b/_pyceres/core/loss_functions.h index 9cf8091..cfeb5bc 100644 --- a/_pyceres/core/loss_functions.h +++ b/_pyceres/core/loss_functions.h @@ -1,37 +1,7 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include @@ -59,7 +29,7 @@ class PyLossFunction : public ceres::LossFunction { if (overload) { overload.operator()(sq_norm, out_arr); } else { - THROW_EXCEPTION(std::runtime_error, " not implemented.") + LOG_FATAL_THROW(std::runtime_error) << " not implemented."; } } @@ -117,7 +87,7 @@ std::shared_ptr CreateLossFunctionFromDict(py::dict dict) { } } -void init_loss_functions(py::module& m) { +void BindLossFunctions(py::module& m) { py::class_>(m, "LossFunction") diff --git a/_pyceres/core/manifold.h b/_pyceres/core/manifold.h index 462a04e..409faeb 100644 --- a/_pyceres/core/manifold.h +++ b/_pyceres/core/manifold.h @@ -1,37 +1,7 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include @@ -44,23 +14,25 @@ class PyManifold : public ceres::Manifold { bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - THROW_EXCEPTION(std::runtime_error, " not implemented."); + LOG_FATAL_THROW(std::runtime_error) << " not implemented."; return true; } bool PlusJacobian(const double* x, double* jacobian) const override { - THROW_EXCEPTION(std::runtime_error, " not implemented."); + LOG_FATAL_THROW(std::runtime_error) << " not implemented."; + return true; } bool Minus(const double* y, const double* x, double* y_minus_x) const override { - THROW_EXCEPTION(std::runtime_error, " not implemented."); + LOG_FATAL_THROW(std::runtime_error) << " not implemented."; return true; } bool MinusJacobian(const double* x, double* jacobian) const override { - THROW_EXCEPTION(std::runtime_error, " not implemented."); + LOG_FATAL_THROW(std::runtime_error) << " not implemented."; + return true; } // Size of x. @@ -86,7 +58,7 @@ class PyManifold : public ceres::Manifold { using namespace ceres; -void init_manifold(py::module& m) { +void BindManifold(py::module& m) { py::class_(m, "Manifold") .def(py::init<>()) .def("ambient_size", &Manifold::AmbientSize) diff --git a/_pyceres/core/problem.h b/_pyceres/core/problem.h index 4f38623..585071e 100644 --- a/_pyceres/core/problem.h +++ b/_pyceres/core/problem.h @@ -1,38 +1,8 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/core/wrappers.h" #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include @@ -56,7 +26,7 @@ std::unique_ptr CreatePythonProblem() { return std::unique_ptr(new ceres::Problem(options)); } -void init_problem(py::module& m) { +void BindProblem(py::module& m) { using options = ceres::Problem::Options; py::class_(m, "ProblemOptions") .def(py::init(&CreateProblemOptions)) // Ensures default is that diff --git a/_pyceres/core/solver.h b/_pyceres/core/solver.h index fc5a55f..5eaca60 100644 --- a/_pyceres/core/solver.h +++ b/_pyceres/core/solver.h @@ -1,312 +1,263 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include namespace py = pybind11; -void init_solver(py::module& m) { +void BindSolver(py::module& m) { m.def("solve", - overload_cast_()(&ceres::Solve), + py::overload_cast(&ceres::Solve), py::call_guard()); - using s_options = ceres::Solver::Options; - auto so = - py::class_(m, "SolverOptions") - .def(py::init<>()) - .def("IsValid", &s_options::IsValid) - .def_property( - "callbacks", - [](const s_options& self) { return self.callbacks; }, - py::cpp_function( - [](s_options& self, py::list list) { - std::vector callbacks; - for (auto& handle : list) { - self.callbacks.push_back( - handle.cast()); - } - }, - py::keep_alive<1, 2>())) - .def_readwrite("minimizer_type", &s_options::minimizer_type) - .def_readwrite("line_search_direction_type", - &s_options::line_search_direction_type) - .def_readwrite("line_search_type", &s_options::line_search_type) - .def_readwrite("nonlinear_conjugate_gradient_type", - &s_options::nonlinear_conjugate_gradient_type) - .def_readwrite("max_lbfgs_rank", &s_options::max_lbfgs_rank) - .def_readwrite("use_approximate_eigenvalue_bfgs_scaling", - &s_options::use_approximate_eigenvalue_bfgs_scaling) - .def_readwrite("line_search_interpolation_type", - &s_options::line_search_interpolation_type) - .def_readwrite("min_line_search_step_size", - &s_options::min_line_search_step_size) - .def_readwrite("line_search_sufficient_function_decrease", - &s_options::line_search_sufficient_function_decrease) - .def_readwrite("max_line_search_step_contraction", - &s_options::max_line_search_step_contraction) - .def_readwrite("min_line_search_step_contraction", - &s_options::min_line_search_step_contraction) - .def_readwrite("max_num_line_search_step_size_iterations", - &s_options::max_num_line_search_step_size_iterations) - .def_readwrite("max_num_line_search_direction_restarts", - &s_options::max_num_line_search_direction_restarts) - .def_readwrite("line_search_sufficient_curvature_decrease", - &s_options::line_search_sufficient_curvature_decrease) - .def_readwrite("max_line_search_step_expansion", - &s_options::max_line_search_step_expansion) - .def_readwrite("trust_region_strategy_type", - &s_options::trust_region_strategy_type) - .def_readwrite("dogleg_type", &s_options::dogleg_type) - .def_readwrite("use_nonmonotonic_steps", - &s_options::use_nonmonotonic_steps) - .def_readwrite("max_consecutive_nonmonotonic_steps", - &s_options::max_consecutive_nonmonotonic_steps) - .def_readwrite("max_num_iterations", &s_options::max_num_iterations) - .def_readwrite("max_solver_time_in_seconds", - &s_options::max_solver_time_in_seconds) - .def_property( - "num_threads", - [](const s_options& self) { return self.num_threads; }, - [](s_options& self, int n_threads) { - int effective_n_threads = GetEffectiveNumThreads(n_threads); - self.num_threads = effective_n_threads; + using Options = ceres::Solver::Options; + py::class_ PyOptions(m, "SolverOptions"); + PyOptions.def(py::init<>()) + .def("IsValid", &Options::IsValid) + .def_property( + "callbacks", + [](const Options& self) { return self.callbacks; }, + py::cpp_function( + [](Options& self, py::list list) { + std::vector callbacks; + for (auto& handle : list) { + self.callbacks.push_back( + handle.cast()); + } + }, + py::keep_alive<1, 2>())) + .def_readwrite("minimizer_type", &Options::minimizer_type) + .def_readwrite("line_search_direction_type", + &Options::line_search_direction_type) + .def_readwrite("line_search_type", &Options::line_search_type) + .def_readwrite("nonlinear_conjugate_gradient_type", + &Options::nonlinear_conjugate_gradient_type) + .def_readwrite("max_lbfgs_rank", &Options::max_lbfgs_rank) + .def_readwrite("use_approximate_eigenvalue_bfgs_scaling", + &Options::use_approximate_eigenvalue_bfgs_scaling) + .def_readwrite("line_search_interpolation_type", + &Options::line_search_interpolation_type) + .def_readwrite("min_line_search_step_size", + &Options::min_line_search_step_size) + .def_readwrite("line_search_sufficient_function_decrease", + &Options::line_search_sufficient_function_decrease) + .def_readwrite("max_line_search_step_contraction", + &Options::max_line_search_step_contraction) + .def_readwrite("min_line_search_step_contraction", + &Options::min_line_search_step_contraction) + .def_readwrite("max_num_line_search_step_size_iterations", + &Options::max_num_line_search_step_size_iterations) + .def_readwrite("max_num_line_search_direction_restarts", + &Options::max_num_line_search_direction_restarts) + .def_readwrite("line_search_sufficient_curvature_decrease", + &Options::line_search_sufficient_curvature_decrease) + .def_readwrite("max_line_search_step_expansion", + &Options::max_line_search_step_expansion) + .def_readwrite("trust_region_strategy_type", + &Options::trust_region_strategy_type) + .def_readwrite("dogleg_type", &Options::dogleg_type) + .def_readwrite("use_nonmonotonic_steps", &Options::use_nonmonotonic_steps) + .def_readwrite("max_consecutive_nonmonotonic_steps", + &Options::max_consecutive_nonmonotonic_steps) + .def_readwrite("max_num_iterations", &Options::max_num_iterations) + .def_readwrite("max_solver_time_in_seconds", + &Options::max_solver_time_in_seconds) + .def_property( + "num_threads", + [](const Options& self) { return self.num_threads; }, + [](Options& self, int n_threads) { + int effective_n_threads = GetEffectiveNumThreads(n_threads); + self.num_threads = effective_n_threads; #if CERES_VERSION_MAJOR < 2 - self.num_linear_solver_threads = effective_n_threads; + self.num_linear_solver_threads = effective_n_threads; #endif // CERES_VERSION_MAJOR - }) - .def_readwrite("initial_trust_region_radius", - &s_options::initial_trust_region_radius) - .def_readwrite("max_trust_region_radius", - &s_options::max_trust_region_radius) - .def_readwrite("min_trust_region_radius", - &s_options::min_trust_region_radius) - .def_readwrite("min_relative_decrease", - &s_options::min_relative_decrease) - .def_readwrite("min_lm_diagonal", &s_options::min_lm_diagonal) - .def_readwrite("max_lm_diagonal", &s_options::max_lm_diagonal) - .def_readwrite("max_num_consecutive_invalid_steps", - &s_options::max_num_consecutive_invalid_steps) - .def_readwrite("function_tolerance", &s_options::function_tolerance) - .def_readwrite("gradient_tolerance", &s_options::gradient_tolerance) - .def_readwrite("parameter_tolerance", &s_options::parameter_tolerance) - .def_readwrite("linear_solver_type", &s_options::linear_solver_type) - .def_readwrite("preconditioner_type", &s_options::preconditioner_type) - .def_readwrite("visibility_clustering_type", - &s_options::visibility_clustering_type) - .def_readwrite("dense_linear_algebra_library_type", - &s_options::dense_linear_algebra_library_type) - .def_readwrite("sparse_linear_algebra_library_type", - &s_options::sparse_linear_algebra_library_type) - // .def_readwrite("num_linear_solver_threads", - // &s_options::num_linear_solver_threads) - .def_readwrite("use_explicit_schur_complement", - &s_options::use_explicit_schur_complement) - .def_readwrite("dynamic_sparsity", &s_options::dynamic_sparsity) - .def_readwrite("use_inner_iterations", - &s_options::use_inner_iterations) - .def_readwrite("inner_iteration_tolerance", - &s_options::inner_iteration_tolerance) - .def_readwrite("min_linear_solver_iterations", - &s_options::min_linear_solver_iterations) - .def_readwrite("max_linear_solver_iterations", - &s_options::max_linear_solver_iterations) - .def_readwrite("eta", &s_options::eta) - .def_readwrite("jacobi_scaling", &s_options::jacobi_scaling) - .def_readwrite("logging_type", &s_options::logging_type) - .def_readwrite("minimizer_progress_to_stdout", - &s_options::minimizer_progress_to_stdout) - .def_readwrite("trust_region_problem_dump_directory", - &s_options::trust_region_problem_dump_directory) - .def_readwrite("trust_region_problem_dump_format_type", - &s_options::trust_region_problem_dump_format_type) - .def_readwrite("check_gradients", &s_options::check_gradients) - .def_readwrite("gradient_check_relative_precision", - &s_options::gradient_check_relative_precision) - .def_readwrite( - "gradient_check_numeric_derivative_relative_step_size", - &s_options::gradient_check_numeric_derivative_relative_step_size) - .def_readwrite("update_state_every_iteration", - &s_options::update_state_every_iteration); - make_dataclass(so); + }) + .def_readwrite("initial_trust_region_radius", + &Options::initial_trust_region_radius) + .def_readwrite("max_trust_region_radius", + &Options::max_trust_region_radius) + .def_readwrite("min_trust_region_radius", + &Options::min_trust_region_radius) + .def_readwrite("min_relative_decrease", &Options::min_relative_decrease) + .def_readwrite("min_lm_diagonal", &Options::min_lm_diagonal) + .def_readwrite("max_lm_diagonal", &Options::max_lm_diagonal) + .def_readwrite("max_num_consecutive_invalid_steps", + &Options::max_num_consecutive_invalid_steps) + .def_readwrite("function_tolerance", &Options::function_tolerance) + .def_readwrite("gradient_tolerance", &Options::gradient_tolerance) + .def_readwrite("parameter_tolerance", &Options::parameter_tolerance) + .def_readwrite("linear_solver_type", &Options::linear_solver_type) + .def_readwrite("preconditioner_type", &Options::preconditioner_type) + .def_readwrite("visibility_clustering_type", + &Options::visibility_clustering_type) + .def_readwrite("dense_linear_algebra_library_type", + &Options::dense_linear_algebra_library_type) + .def_readwrite("sparse_linear_algebra_library_type", + &Options::sparse_linear_algebra_library_type) + // .def_readwrite("num_linear_solver_threads", + // &Options::num_linear_solver_threads) + .def_readwrite("use_explicit_schur_complement", + &Options::use_explicit_schur_complement) + .def_readwrite("dynamic_sparsity", &Options::dynamic_sparsity) + .def_readwrite("use_inner_iterations", &Options::use_inner_iterations) + .def_readwrite("inner_iteration_tolerance", + &Options::inner_iteration_tolerance) + .def_readwrite("min_linear_solver_iterations", + &Options::min_linear_solver_iterations) + .def_readwrite("max_linear_solver_iterations", + &Options::max_linear_solver_iterations) + .def_readwrite("eta", &Options::eta) + .def_readwrite("jacobi_scaling", &Options::jacobi_scaling) + .def_readwrite("logging_type", &Options::logging_type) + .def_readwrite("minimizer_progress_to_stdout", + &Options::minimizer_progress_to_stdout) + .def_readwrite("trust_region_problem_dump_directory", + &Options::trust_region_problem_dump_directory) + .def_readwrite("trust_region_problem_dump_format_type", + &Options::trust_region_problem_dump_format_type) + .def_readwrite("check_gradients", &Options::check_gradients) + .def_readwrite("gradient_check_relative_precision", + &Options::gradient_check_relative_precision) + .def_readwrite( + "gradient_check_numeric_derivative_relative_step_size", + &Options::gradient_check_numeric_derivative_relative_step_size) + .def_readwrite("update_state_every_iteration", + &Options::update_state_every_iteration); + MakeDataclass(PyOptions); - using s_summary = ceres::Solver::Summary; - auto summary = - py::class_(m, "SolverSummary") - .def(py::init<>()) - .def("BriefReport", &s_summary::BriefReport) - .def("FullReport", &s_summary::FullReport) - .def("IsSolutionUsable", &s_summary::IsSolutionUsable) - .def_readwrite("minimizer_type", &s_summary::minimizer_type) - .def_readwrite("termination_type", &s_summary::termination_type) - .def_readwrite("message", &s_summary::message) - .def_readwrite("initial_cost", &s_summary::initial_cost) - .def_readwrite("final_cost", &s_summary::final_cost) - .def_readwrite("fixed_cost", &s_summary::fixed_cost) - .def_readwrite("num_successful_steps", - &s_summary::num_successful_steps) - .def_readwrite("num_unsuccessful_steps", - &s_summary::num_unsuccessful_steps) - .def_readwrite("num_inner_iteration_steps", - &s_summary::num_inner_iteration_steps) - .def_readwrite("num_line_search_steps", - &s_summary::num_line_search_steps) - .def_readwrite("preprocessor_time_in_seconds", - &s_summary::preprocessor_time_in_seconds) - .def_readwrite("minimizer_time_in_seconds", - &s_summary::minimizer_time_in_seconds) - .def_readwrite("postprocessor_time_in_seconds", - &s_summary::postprocessor_time_in_seconds) - .def_readwrite("total_time_in_seconds", - &s_summary::total_time_in_seconds) - .def_readwrite("linear_solver_time_in_seconds", - &s_summary::linear_solver_time_in_seconds) - .def_readwrite("num_linear_solves", &s_summary::num_linear_solves) - .def_readwrite("residual_evaluation_time_in_seconds", - &s_summary::residual_evaluation_time_in_seconds) - .def_readwrite("num_residual_evaluations", - &s_summary::num_residual_evaluations) - .def_readwrite("jacobian_evaluation_time_in_seconds", - &s_summary::jacobian_evaluation_time_in_seconds) - .def_readwrite("num_jacobian_evaluations", - &s_summary::num_jacobian_evaluations) - .def_readwrite("inner_iteration_time_in_seconds", - &s_summary::inner_iteration_time_in_seconds) - .def_readwrite( - "line_search_cost_evaluation_time_in_seconds", - &s_summary::line_search_cost_evaluation_time_in_seconds) - .def_readwrite( - "line_search_gradient_evaluation_time_in_seconds", - &s_summary::line_search_gradient_evaluation_time_in_seconds) - .def_readwrite( - "line_search_polynomial_minimization_time_in_seconds", - &s_summary::line_search_polynomial_minimization_time_in_seconds) - .def_readwrite("line_search_total_time_in_seconds", - &s_summary::line_search_total_time_in_seconds) - .def_readwrite("num_parameter_blocks", - &s_summary::num_parameter_blocks) - .def_readwrite("num_parameters", &s_summary::num_parameters) - .def_readwrite("num_effective_parameters", - &s_summary::num_effective_parameters) - .def_readwrite("num_residual_blocks", &s_summary::num_residual_blocks) - .def_readwrite("num_residuals", &s_summary::num_residuals) - .def_readwrite("num_parameter_blocks_reduced", - &s_summary::num_parameter_blocks_reduced) - .def_readwrite("num_parameters_reduced", - &s_summary::num_parameters_reduced) - .def_readwrite("num_effective_parameters_reduced", - &s_summary::num_effective_parameters_reduced) - .def_readwrite("num_residual_blocks_reduced", - &s_summary::num_residual_blocks_reduced) - .def_readwrite("num_residuals_reduced", - &s_summary::num_residuals_reduced) - .def_readwrite("is_constrained", &s_summary::is_constrained) - .def_readwrite("num_threads_given", &s_summary::num_threads_given) - .def_readwrite("num_threads_used", &s_summary::num_threads_used) + using Summary = ceres::Solver::Summary; + py::class_ PySummary(m, "SolverSummary"); + PySummary.def(py::init<>()) + .def("BriefReport", &Summary::BriefReport) + .def("FullReport", &Summary::FullReport) + .def("IsSolutionUsable", &Summary::IsSolutionUsable) + .def_readwrite("minimizer_type", &Summary::minimizer_type) + .def_readwrite("termination_type", &Summary::termination_type) + .def_readwrite("message", &Summary::message) + .def_readwrite("initial_cost", &Summary::initial_cost) + .def_readwrite("final_cost", &Summary::final_cost) + .def_readwrite("fixed_cost", &Summary::fixed_cost) + .def_readwrite("num_successful_steps", &Summary::num_successful_steps) + .def_readwrite("num_unsuccessful_steps", &Summary::num_unsuccessful_steps) + .def_readwrite("num_inner_iteration_steps", + &Summary::num_inner_iteration_steps) + .def_readwrite("num_line_search_steps", &Summary::num_line_search_steps) + .def_readwrite("preprocessor_time_in_seconds", + &Summary::preprocessor_time_in_seconds) + .def_readwrite("minimizer_time_in_seconds", + &Summary::minimizer_time_in_seconds) + .def_readwrite("postprocessor_time_in_seconds", + &Summary::postprocessor_time_in_seconds) + .def_readwrite("total_time_in_seconds", &Summary::total_time_in_seconds) + .def_readwrite("linear_solver_time_in_seconds", + &Summary::linear_solver_time_in_seconds) + .def_readwrite("num_linear_solves", &Summary::num_linear_solves) + .def_readwrite("residual_evaluation_time_in_seconds", + &Summary::residual_evaluation_time_in_seconds) + .def_readwrite("num_residual_evaluations", + &Summary::num_residual_evaluations) + .def_readwrite("jacobian_evaluation_time_in_seconds", + &Summary::jacobian_evaluation_time_in_seconds) + .def_readwrite("num_jacobian_evaluations", + &Summary::num_jacobian_evaluations) + .def_readwrite("inner_iteration_time_in_seconds", + &Summary::inner_iteration_time_in_seconds) + .def_readwrite("line_search_cost_evaluation_time_in_seconds", + &Summary::line_search_cost_evaluation_time_in_seconds) + .def_readwrite("line_search_gradient_evaluation_time_in_seconds", + &Summary::line_search_gradient_evaluation_time_in_seconds) + .def_readwrite( + "line_search_polynomial_minimization_time_in_seconds", + &Summary::line_search_polynomial_minimization_time_in_seconds) + .def_readwrite("line_search_total_time_in_seconds", + &Summary::line_search_total_time_in_seconds) + .def_readwrite("num_parameter_blocks", &Summary::num_parameter_blocks) + .def_readwrite("num_parameters", &Summary::num_parameters) + .def_readwrite("num_effective_parameters", + &Summary::num_effective_parameters) + .def_readwrite("num_residual_blocks", &Summary::num_residual_blocks) + .def_readwrite("num_residuals", &Summary::num_residuals) + .def_readwrite("num_parameter_blocks_reduced", + &Summary::num_parameter_blocks_reduced) + .def_readwrite("num_parameters_reduced", &Summary::num_parameters_reduced) + .def_readwrite("num_effective_parameters_reduced", + &Summary::num_effective_parameters_reduced) + .def_readwrite("num_residual_blocks_reduced", + &Summary::num_residual_blocks_reduced) + .def_readwrite("num_residuals_reduced", &Summary::num_residuals_reduced) + .def_readwrite("is_constrained", &Summary::is_constrained) + .def_readwrite("num_threads_given", &Summary::num_threads_given) + .def_readwrite("num_threads_used", &Summary::num_threads_used) #if CERES_VERSION_MAJOR < 2 - .def_readwrite("num_linear_solver_threads_given", - &s_summary::num_linear_solver_threads_given) - .def_readwrite("num_linear_solver_threads_used", - &s_summary::num_linear_solver_threads_used) + .def_readwrite("num_linear_solver_threads_given", + &Summary::num_linear_solver_threads_given) + .def_readwrite("num_linear_solver_threads_used", + &Summary::num_linear_solver_threads_used) #endif - .def_readwrite("linear_solver_type_given", - &s_summary::linear_solver_type_given) - .def_readwrite("linear_solver_type_used", - &s_summary::linear_solver_type_used) - .def_readwrite("schur_structure_given", - &s_summary::schur_structure_given) - .def_readwrite("schur_structure_used", - &s_summary::schur_structure_used) - .def_readwrite("inner_iterations_given", - &s_summary::inner_iterations_given) - .def_readwrite("inner_iterations_used", - &s_summary::inner_iterations_used) - .def_readwrite("preconditioner_type_given", - &s_summary::preconditioner_type_given) - .def_readwrite("preconditioner_type_used", - &s_summary::preconditioner_type_used) - .def_readwrite("visibility_clustering_type", - &s_summary::visibility_clustering_type) - .def_readwrite("trust_region_strategy_type", - &s_summary::trust_region_strategy_type) - .def_readwrite("dogleg_type", &s_summary::dogleg_type) - .def_readwrite("dense_linear_algebra_library_type", - &s_summary::dense_linear_algebra_library_type) - .def_readwrite("sparse_linear_algebra_library_type", - &s_summary::sparse_linear_algebra_library_type) - .def_readwrite("line_search_direction_type", - &s_summary::line_search_direction_type) - .def_readwrite("line_search_type", &s_summary::line_search_type) - .def_readwrite("line_search_interpolation_type", - &s_summary::line_search_interpolation_type) - .def_readwrite("nonlinear_conjugate_gradient_type", - &s_summary::nonlinear_conjugate_gradient_type) - .def_readwrite("max_lbfgs_rank", &s_summary::max_lbfgs_rank); - make_dataclass(summary); + .def_readwrite("linear_solver_type_given", + &Summary::linear_solver_type_given) + .def_readwrite("linear_solver_type_used", + &Summary::linear_solver_type_used) + .def_readwrite("schur_structure_given", &Summary::schur_structure_given) + .def_readwrite("schur_structure_used", &Summary::schur_structure_used) + .def_readwrite("inner_iterations_given", &Summary::inner_iterations_given) + .def_readwrite("inner_iterations_used", &Summary::inner_iterations_used) + .def_readwrite("preconditioner_type_given", + &Summary::preconditioner_type_given) + .def_readwrite("preconditioner_type_used", + &Summary::preconditioner_type_used) + .def_readwrite("visibility_clustering_type", + &Summary::visibility_clustering_type) + .def_readwrite("trust_region_strategy_type", + &Summary::trust_region_strategy_type) + .def_readwrite("dogleg_type", &Summary::dogleg_type) + .def_readwrite("dense_linear_algebra_library_type", + &Summary::dense_linear_algebra_library_type) + .def_readwrite("sparse_linear_algebra_library_type", + &Summary::sparse_linear_algebra_library_type) + .def_readwrite("line_search_direction_type", + &Summary::line_search_direction_type) + .def_readwrite("line_search_type", &Summary::line_search_type) + .def_readwrite("line_search_interpolation_type", + &Summary::line_search_interpolation_type) + .def_readwrite("nonlinear_conjugate_gradient_type", + &Summary::nonlinear_conjugate_gradient_type) + .def_readwrite("max_lbfgs_rank", &Summary::max_lbfgs_rank); + MakeDataclass(PySummary); - using it_sum = ceres::IterationSummary; - auto it_summary = - py::class_(m, "IterationSummary") - .def(py::init<>()) - .def_readonly("iteration", &it_sum::iteration) - .def_readonly("step_is_valid", &it_sum::step_is_valid) - .def_readonly("step_is_nonmonotonic", &it_sum::step_is_nonmonotonic) - .def_readonly("step_is_successful", &it_sum::step_is_successful) - .def_readonly("cost", &it_sum::cost) - .def_readonly("cost_change", &it_sum::cost_change) - .def_readonly("gradient_max_norm", &it_sum::gradient_max_norm) - .def_readonly("gradient_norm", &it_sum::gradient_norm) - .def_readonly("step_norm", &it_sum::step_norm) - .def_readonly("relative_decrease", &it_sum::relative_decrease) - .def_readonly("trust_region_radius", &it_sum::trust_region_radius) - .def_readonly("eta", &it_sum::eta) - .def_readonly("step_size", &it_sum::step_size) - .def_readonly("line_search_function_evaluations", - &it_sum::line_search_function_evaluations) - .def_readonly("line_search_gradient_evaluations", - &it_sum::line_search_gradient_evaluations) - .def_readonly("line_search_iterations", - &it_sum::line_search_iterations) - .def_readonly("linear_solver_iterations", - &it_sum::linear_solver_iterations) - .def_readonly("iteration_time_in_seconds", - &it_sum::iteration_time_in_seconds) - .def_readonly("step_solver_time_in_seconds", - &it_sum::step_solver_time_in_seconds) - .def_readonly("cumulative_time_in_seconds", - &it_sum::cumulative_time_in_seconds); + using IterSummary = ceres::IterationSummary; + py::class_ PyIterSummary(m, "IterationSummary"); + PyIterSummary.def(py::init<>()) + .def_readonly("iteration", &IterSummary::iteration) + .def_readonly("step_is_valid", &IterSummary::step_is_valid) + .def_readonly("step_is_nonmonotonic", &IterSummary::step_is_nonmonotonic) + .def_readonly("step_is_successful", &IterSummary::step_is_successful) + .def_readonly("cost", &IterSummary::cost) + .def_readonly("cost_change", &IterSummary::cost_change) + .def_readonly("gradient_max_norm", &IterSummary::gradient_max_norm) + .def_readonly("gradient_norm", &IterSummary::gradient_norm) + .def_readonly("step_norm", &IterSummary::step_norm) + .def_readonly("relative_decrease", &IterSummary::relative_decrease) + .def_readonly("trust_region_radius", &IterSummary::trust_region_radius) + .def_readonly("eta", &IterSummary::eta) + .def_readonly("step_size", &IterSummary::step_size) + .def_readonly("line_search_function_evaluations", + &IterSummary::line_search_function_evaluations) + .def_readonly("line_search_gradient_evaluations", + &IterSummary::line_search_gradient_evaluations) + .def_readonly("line_search_iterations", + &IterSummary::line_search_iterations) + .def_readonly("linear_solver_iterations", + &IterSummary::linear_solver_iterations) + .def_readonly("iteration_time_in_seconds", + &IterSummary::iteration_time_in_seconds) + .def_readonly("step_solver_time_in_seconds", + &IterSummary::step_solver_time_in_seconds) + .def_readonly("cumulative_time_in_seconds", + &IterSummary::cumulative_time_in_seconds); } diff --git a/_pyceres/core/types.h b/_pyceres/core/types.h index 6dd7a83..b9c4fe7 100644 --- a/_pyceres/core/types.h +++ b/_pyceres/core/types.h @@ -1,37 +1,7 @@ -// Ceres Solver Python Bindings -// Copyright Nikolaus Mitchell. All rights reserved. -// -// 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 the copyright holder 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: nikolausmitchell@gmail.com (Nikolaus Mitchell) -// Edited by: philipp.lindenberger@math.ethz.ch (Philipp Lindenberger) - #pragma once #include "_pyceres/helpers.h" -#include "_pyceres/log_exceptions.h" +#include "_pyceres/logging.h" #include #include @@ -42,7 +12,7 @@ namespace py = pybind11; -void init_types(py::module& m) { +void BindTypes(py::module& m) { auto ownt = py::enum_(m, "Ownership") .value("DO_NOT_TAKE_OWNERSHIP", ceres::Ownership::DO_NOT_TAKE_OWNERSHIP) diff --git a/_pyceres/factors/bindings.h b/_pyceres/factors/bindings.h index 3f2809f..e2aca1d 100644 --- a/_pyceres/factors/bindings.h +++ b/_pyceres/factors/bindings.h @@ -1,85 +1,24 @@ #pragma once -#include "_pyceres/factors/bundle.h" -#include "_pyceres/factors/common.h" -#include "_pyceres/factors/pose_graph.h" #include "_pyceres/helpers.h" +#include +#include #include #include -#include namespace py = pybind11; -using namespace colmap; -void bind_factors(py::module& m) { - m.def("ReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("point2D")); - m.def("ReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("point2D"), - py::arg("stddev")); - m.def("ReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("cam_from_world"), - py::arg("point2D")); - m.def("ReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("cam_from_world"), - py::arg("point2D"), - py::arg("stddev")); - - m.def("RigReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("point2D")); - m.def("RigReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("point2D"), - py::arg("stddev")); - m.def("RigReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("cam_from_rig"), - py::arg("point2D")); - m.def("RigReprojErrorCost", - &CreateCostFunction, - py::arg("camera_model_id"), - py::arg("cam_from_rig"), - py::arg("point2D"), - py::arg("stddev")); - - m.def("PoseGraphRelativeCost", - &PoseGraphRelativeCost::Create, - py::arg("j_from_i"), - py::arg("covariance")); - m.def("PoseGraphAbsoluteCost", - &PoseGraphAbsoluteCost::Create, - py::arg("cam_from_world"), - py::arg("covariance")); - - m.def("NormalPrior", - &CreateNormalPrior, - py::arg("mean"), - py::arg("covariance")); +void BindFactors(py::module& m) { + m.def( + "NormalPrior", + [](const Eigen::VectorXd& mean, + const Eigen::Matrix& covariance) { + THROW_CHECK_EQ(covariance.cols(), mean.size()); + THROW_CHECK_EQ(covariance.cols(), covariance.rows()); + return new ceres::NormalPrior(covariance.inverse().llt().matrixL(), + mean); + }, + py::arg("mean"), + py::arg("covariance")); } diff --git a/_pyceres/factors/bundle.h b/_pyceres/factors/bundle.h deleted file mode 100644 index 0cee120..0000000 --- a/_pyceres/factors/bundle.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include "_pyceres/log_exceptions.h" - -#include -#include -#include -#include -#include - -using namespace colmap; - -class LinearCostFunction : public ceres::CostFunction { - public: - LinearCostFunction(const double s) : s_(s) { - set_num_residuals(1); - mutable_parameter_block_sizes()->push_back(1); - } - - bool Evaluate(double const* const* parameters, - double* residuals, - double** jacobians) const final { - *residuals = **parameters * s_; - if (jacobians && *jacobians) { - **jacobians = s_; - } - return true; - } - - private: - const double s_; -}; - -template -class CostFunctionIsotropicNoise { - public: - static ceres::CostFunction* Create(Args&&... args, const double stddev) { - THROW_CHECK_GE(stddev, 0.0); - ceres::CostFunction* cost_function = - CostFunction::Create(std::forward(args)...); - std::vector conditioners( - cost_function->num_residuals(), new LinearCostFunction(1.0 / stddev)); - return new ceres::ConditionedCostFunction( - cost_function, conditioners, ceres::TAKE_OWNERSHIP); - } -}; - -template -class RigReprojErrorConstantRigCostFunction - : public ReprojErrorCostFunction { - using Parent = ReprojErrorCostFunction; - - public: - explicit RigReprojErrorConstantRigCostFunction(const Rigid3d& cam_from_rig, - const Eigen::Vector2d& point2D) - : Parent(point2D), cam_from_rig_(cam_from_rig) {} - - static ceres::CostFunction* Create(const Rigid3d& cam_from_rig, - const Eigen::Vector2d& point2D) { - return (new ceres::AutoDiffCostFunction< - RigReprojErrorConstantRigCostFunction, - 2, - 4, - 3, - 3, - CameraModel::num_params>( - new RigReprojErrorConstantRigCostFunction(cam_from_rig, point2D))); - } - - template - bool operator()(const T* const rig_from_world_rotation, - const T* const rig_from_world_translation, - const T* const point3D, - const T* const camera_params, - T* residuals) const { - const Eigen::Quaternion cam_from_world_rotation = - cam_from_rig_.rotation.cast() * - EigenQuaternionMap(rig_from_world_rotation); - const Eigen::Matrix cam_from_world_translation = - cam_from_rig_.rotation.cast() * - EigenVector3Map(rig_from_world_translation) + - cam_from_rig_.translation.cast(); - return Parent::operator()(cam_from_world_rotation.coeffs().data(), - cam_from_world_translation.data(), - point3D, - camera_params, - residuals); - } - - private: - const Rigid3d& cam_from_rig_; -}; - -template -using ReprojErrorCostFunctionWithNoise = - CostFunctionIsotropicNoise, - const Eigen::Vector2d&>; - -template -using ReprojErrorConstantPoseCostFunctionWithNoise = - CostFunctionIsotropicNoise, - const Rigid3d&, - const Eigen::Vector2d&>; - -template -using RigReprojErrorCostFunctionWithNoise = - CostFunctionIsotropicNoise, - const Eigen::Vector2d&>; - -template -using RigReprojErrorConstantRigCostFunctionWithNoise = - CostFunctionIsotropicNoise< - RigReprojErrorConstantRigCostFunction, - const Rigid3d&, - const Eigen::Vector2d&>; - -template