Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into pablo/dockerfile_two_…
Browse files Browse the repository at this point in the history
…stages
  • Loading branch information
pablospe committed Feb 6, 2024
2 parents 520c198 + c9cd802 commit 9c88596
Show file tree
Hide file tree
Showing 30 changed files with 1,067 additions and 1,372 deletions.
147 changes: 147 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
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/
17 changes: 9 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 .)
61 changes: 4 additions & 57 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 \
Expand All @@ -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
39 changes: 15 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/).
11 changes: 6 additions & 5 deletions _pyceres/bindings.cc
Original file line number Diff line number Diff line change
@@ -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 <pybind11/iostream.h>
#include <pybind11/pybind11.h>
Expand All @@ -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);
}
Loading

0 comments on commit 9c88596

Please sign in to comment.