Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py

# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

5 changes: 5 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/
source/api/generated/
source/gallery/


20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

12 changes: 12 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
matplotlib
opencv-python
Sphinx==5.3.0
sphinx-gallery==0.11.1
sphinx-rtd-theme==1.1.1
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-napoleon==0.7
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
39 changes: 39 additions & 0 deletions docs/source/api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.. _api:

homlib API
=================

The Application Programming Interface (API) includes:

Module
------

.. currentmodule:: homlib

.. autosummary::
:toctree: generated/

PoseData
LORansacOptions
RansacStatistics

estimate_fitzgibbon_cvpr_2001_one_sided
estimate_fitzgibbon_cvpr_2001_two_sided_equal
estimate_kukelova_cvpr_2015_two_sided
estimate_kukelova_cvpr_2015_two_sided_6pt
estimate_nakano_icpr_2025_one_sided
estimate_wadenback_3dv_2026_one_sided
estimate_wadenback_3dv_2026_two_sided_equal
estimate_wadenback_3dv_2026_two_sided

lomsac_fitzgibbon_cvpr_2001_one_sided
lomsac_fitzgibbon_cvpr_2001_two_sided_equal
lomsac_kukelova_cvpr_2015_two_sided
lomsac_kukelova_cvpr_2015_two_sided_equal
lomsac_kukelova_cvpr_2015_two_sided_equal_6pt
lomsac_kukelova_cvpr_2015_two_sided_6pt
lomsac_nakano_icpr_2025_one_sided
lomsac_wadenback_3dv_2026_one_sided
lomsac_wadenback_3dv_2026_two_sided_equal
lomsac_wadenback_3dv_2026_two_sided

94 changes: 94 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
import datetime

from sphinx_gallery.sorting import ExampleTitleSortKey


extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.coverage',
'sphinx.ext.doctest',
'sphinx.ext.viewcode',
'sphinx.ext.extlinks',
'sphinx.ext.napoleon',
'sphinx_gallery.gen_gallery',
]


# Generate autodoc stubs with summaries from code
autosummary_generate = True

# Include Python objects as they appear in source files
autodoc_member_order = 'bysource'

# Default flags used by autodoc directives
autodoc_default_flags = ['members']

#autodoc_typehints = "description"

sphinx_gallery_conf = {
# path to your examples scripts
'examples_dirs': ['../../python/example'],
# path where to save gallery generated examples
'gallery_dirs': ['gallery'],
'filename_pattern': '\.py',
# Remove the "Download all examples" button from the top level gallery
'download_all_examples': False,
# Sort gallery example by file name instead of number of lines (default)
'within_subsection_order': ExampleTitleSortKey,
# directory where function granular galleries are stored
'backreferences_dir': 'api/generated/backreferences',
# Modules for which function level galleries are created.
'doc_module': 'homlib',
# Insert links to documentation of objects in the examples
'reference_url': {'satoa': None},
# Allow animations
'matplotlib_animations': False,
}

# Always show the source code that generates a plot
plot_include_source = True
plot_formats = ['png']

# Sphinx project configuration
templates_path = ['_templates']
exclude_patterns = ['_build', '**.ipynb_checkpoints']
source_suffix = '.rst'
# The encoding of source files.
source_encoding = 'utf-8-sig'
master_doc = 'index'

# General information about the project
year = datetime.date.today().year
project = 'HomLib'
copyright = '{}, Marcus Valtonen Örnhag'.format(year)

# Version
version = 'latest'

# These enable substitutions using |variable| in the rst files
rst_epilog = """
.. |year| replace:: {year}
""".format(year=year)

html_last_updated_fmt = '%b %d, %Y'
html_title = 'HomLib'
html_short_title = 'HomLib'
#html_logo = '_static/logo.png'
#html_favicon = '_static/favicon.ico'
#html_static_path = ['_static']
html_extra_path = []
pygments_style = 'default'
add_function_parentheses = False
html_show_sourcelink = False
html_show_sphinx = True
html_show_copyright = True

# Theme config
html_theme = "sphinx_rtd_theme"
html_theme_options = {
'logo_only': True,
'display_version': True,
}

37 changes: 37 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Welcome to homlib's documentation!
=========================================================

A collection of state-of-the-art homography solvers.

.. warning::
This repository is currently under active development.
Please be mindful of potential issues and keep an eye for improvements,
new features and bug fixes in upcoming versions.

**Maintainer**

* `Marcus Valtonen Örnhag <marcusvaltonen@gmail.com>`_


.. toctree::
:maxdepth: 1
:hidden:
:caption: Getting started:

installation/dependencies.rst
installation/installation_users.rst
installation/installation_developers.rst

.. toctree::
:maxdepth: 1
:hidden:
:caption: Examples and usage:

gallery/index.rst

.. toctree::
:maxdepth: 2
:hidden:
:caption: Reference documentation:

api/index.rst
6 changes: 6 additions & 0 deletions docs/source/installation/dependencies.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. _dependencies:

Dependencies
============

To install the the library you will need **Python 3.10 or greater**.
28 changes: 28 additions & 0 deletions docs/source/installation/installation_developers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.. _installation_developers:

Step-by-step installation for developers
========================================

To build the core C++ library you need Eigen 3.3+. On Ubuntu:


.. code-block::

apt install libeigen3-dev

and also `PoseLib <https://github.com/PoseLib/PoseLib>`__. Follow
the installation instructions in the repo.

Furthermore, you need `RansacLib <https://github.com/tsattler/RansacLib>`__, which
is included as a submodule. You may simply pull it recursively,

.. code-block::

git submodule update --init --recursive

RansacLib is a header-only library and does not need any further installation.

You may now use the **build.sh** script in the root of the repository to build the
C++ library for HomLib.


12 changes: 12 additions & 0 deletions docs/source/installation/installation_users.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. _installation_users:

Step-by-step installation for users
===================================

Pre-built python wheels are available on PyPI for latest version of Windows/Mac/Linux.
Simply install them using

.. code-block::

pip install homlib

11 changes: 11 additions & 0 deletions python/example/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. _examples:

Examples
--------
Install the necessary extra requirements

.. code:: bash

pip install -r requirements.txt

prior to running the examples.
57 changes: 57 additions & 0 deletions python/example/example_ransac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
r"""
Synthetic RANSAC experiment
===========================

In this example, we show how to robustly estimate a homography
in the presence of outliers using synthetic data and different
distortion profiles.
"""

import numpy as np

import homlib


###############################################################################
# Configure RANSAC options

options = homlib.LORansacOptions()
options.squared_inlier_threshold = 0.005
options.final_least_squares = True

###############################################################################
# Generate random problem instance (no radial distortion added for simplicity)

N = 100
H = np.random.randn(3, 3)
x = np.random.randn(2, N)
y = H @ np.vstack((x, np.ones((1, N))))
y = y[:2] / y[2]


###############################################################################
# Add noise

noise_std = 1e-4
for i in range(N):
x[:,i] += np.random.randn(2) * noise_std
y[:,i] += np.random.randn(2) * noise_std


###############################################################################
# Add outliers

nbr_outliers = 20
for i in range(nbr_outliers):
x[:,i] = np.random.randn(2) * 10
y[:,i] = np.random.randn(2) * 10

###############################################################################
# Estimate homography and distortion coefficient

estimate, stats = homlib.lomsac_wadenback_3dv_2026_one_sided(x, y, options)

print(f"H error = {np.linalg.norm(estimate.homography / estimate.homography[2,2] - H / H[2,2]):4e}")
print(f"Dist. error = {np.linalg.norm(estimate.distortion_parameter):4e}") # No distortion added - should be zero
print(stats.inlier_indices)

21 changes: 15 additions & 6 deletions python/example/test.py → python/example/example_synthetic.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import numpy as np
r"""
Synthetic experiment
====================

import homlib
In this example, we show how to estimate a homography from synthetic data.
"""

import numpy as np

H = np.eye(3)
p = homlib.PoseData(H, 1.0, -0.001, -0.002)
print(f"PoseData: {p}")
import homlib

######################################
# Generate synthetic data
N = 5
H = np.random.randn(3, 3)
x = np.random.randn(2, N)
Expand All @@ -15,7 +19,12 @@

print(f"H={H / H[2,2]}")

poses = homlib.estimate_wadenback_2025_one_sided(x, y, True)
######################################
# Run estimator from homlib on the minimal sample.
poses = homlib.estimate_wadenback_3dv_2026_one_sided(x, y, True)

######################################
# Test against ground truth

for i, p in enumerate(poses):
print(f"Pose {i}: {p} with H =")
Expand Down
1 change: 1 addition & 0 deletions python/example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
matplotlib
Loading
Loading