tomasuciu / compass

A robust header-only circle-fitting library written in C++17.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

compass

A robust header-only curve fitting library.

SummaryUsageInstallationDocumentationReferences


Summary

compass is the first comprehensive open source circle-fitting library written in C++.

Using an easily extensible interface based on the curiously recurring template pattern, compass implements 22 numerical algorithms for solving the canonical problem of fitting a circle to given points in the plane. Of the algorithms included in the library, most contain multiple implementations so as to optimize for either numerical stability or computational efficiency.

compass is designed to be easily integrated into larger projects. To that end, its only dependency is Eigen 3.3. Further, compass can be collapsed into a singular header file using the included Python script, though, the benefits of this are minimal considering the small scale and incomplexity of the codebase.

The taxonomy of compass comprises three broad categories of algorithms — algebraic, geometric, and specialized — as well as several subcatagories which are further discussed in the usage section. An algorithm's classification is given in its relation to the following function.

  • Algebraic algorithms are non-iterative procedures that reparameterize and minimize the corresponding objective function by way of orthogonal least squares.
  • Geometric algorithms, supplied with an initial guess (usually produced by an algorithm of the Algebraic variety), iteratively converge to a minimum of .
  • Specialized algorithms are a group of sophisticated routines that bear little similarity with their geometric and algebraic counterparts as well as with one another. They employ nonstandard techniques ranging from stereographic projections of the Riemann sphere to trigonometric transformations of .

Usage

compass implements a module-centric organizational structure to reduce the amount of #include directives needed to write a minimally functional program. To avoid compilation overhead, files can be included individually.

Algebraic.hpp Geometric.hpp Specialized.hpp Compass.hpp
GanderGolubStrebel ✔️ ✔️ ✔️
Kasa ✔️ ✔️ ✔️
KasaConsistent ✔️ ✔️ ✔️
HyperSVD ✔️ ✔️ ✔️
HyperSimple ✔️ ✔️ ✔️
KukushMarkovskyHuffel ✔️ ✔️ ✔️
Nievergelt ✔️ ✔️ ✔️
PrattSVD ✔️ ✔️ ✔️
PrattNewton ✔️ ✔️ ✔️
PrattRobust ✔️ ✔️ ✔️
TaubinSVD ✔️ ✔️ ✔️
TaubinNystromSVD ✔️ ✔️ ✔️
TaubinNewton ✔️ ✔️ ✔️
Landau ✔️ ✔️
LevenbergMarquardt ✔️ ✔️
LevenbergMarquardtReduced ✔️ ✔️
Spath ✔️ ✔️
Trust ✔️ ✔️
Riemann ✔️ ✔️
RiemannAlgebraic ✔️ ✔️
InversionIterative ✔️ ✔️
InversionNonIterative ✔️ ✔️

compass is in the early stages of its development and has not been exhaustively tested and optimized. As such, it is not recommended for use in production.

Example

#include "Compass/Geometric.hpp"
using namespace compass;

Eigen::Matrix<double, Eigen::Dynamic, 2, Eigen::RowMajor> data(6, 2);
data << 1.0, 7.0, 2.0, 6.0, 5.0, 8.0, 7.0, 7.0, 9.0, 5.0, 3.0, 7.0;

PrattNewton PN;
PN.fit(data);
std::cout << PN.getCircle() << "\n";

LevenbergMarquardtFull<TaubinSVD> LMF(data);
std::cout << LMF.getVector() << "\n";

There are two primary ways in which an algorithm can be fit to a given data matrix , where the columns represent and , respectively.

The first example utilizes the default constructor and passes the matrix by fit, which dispatches to a local implementation of the algorithm. fit overwrites the parameters associated with an exisiting instance without requiring the construction of a new object, which can be expensive.

The second example makes use of the overloaded constructor in the GeometricFit interface from which LevenbergMarquardtFull derives. Note that since LevenbergMarquardtFull is of class GeometricFit, it requires an initial guess produced by an algebraic algorithm, in this case, TaubinSVD. The program will not compile if a non-algebraic algorithm is passed as a template parameter.

compass::Circle<T> is a wrapper type for Eigen::RowVector3<double> that is used to store the approximated circle parameters that are output by each algorithm. If desired, the underlying vector can be extracted by getVector() instead of getCircle().


In a forthcoming release, the following instantiation methods will also be supported:

1 - Preallocation, given a priori data matrix dimensions.

PrattNewton PN(6, 2);
PN.fit(data);

2 - Geometric methods with TaubinNewton default initial guess computation.

LevenbergMarquardtFull LMF(data);

3 - Geometric methods with a precomputed initial guess.

Circle<double> precomputed(4.5, 2.7, 2.75);
LevenbergMarquardtFull LMF(data, precomputed);
std::cout << LMF.getCircle() << "\n";

4 - In-place computation for all classes of algorithms

// where data is non-const
PrattNewton PN(data);

Algorithms that derive from the SpecializedFitWithPole interface require an additional pole parameter of type Eigen::RowVector2<T> or Eigen::Vector2<T>. Selecting the optimal pole for each algorithm is a nontrivial problem that oftentimes requires some insight into the dataset. The following example demonstrates the usage of one of the algorithms in this category.

#include "Compass/Specialized n.hpp"
using namespace compass;

Eigen::Matrix<double, Eigen::Dynamic, 2, Eigen::RowMajor> data(6, 2);
data << 1.0, 7.0, 2.0, 6.0, 5.0, 8.0, 7.0, 7.0, 9.0, 5.0, 3.0, 7.0;

InversionIterative I;
I.fit(data, data.block<1, 2>(0, 0));
// here we select the first data point in the matrix as the pole

Quick Start

Dependencies

  • Eigen 3.3 :
    • Linux ( Ubuntu and similar )

      apt-get install libeigen3-dev
      
    For more granular information, see Eigen's downloads page.
    • OS X

      brew install eigen
      

Installation

$ git clone https://github.com/tomasuciu/compass.git
$ cd compass && mkdir build && cd build
$ cmake ..
$ make
$ make install

Using compass

In your CMakeLists.txt :

project(foo)

# Find Eigen
find_package(Eigen3 3.3 REQUIRED)
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${EIGEN3_INCLUDE_DIRS})

# Find compass
find_package(compass REQUIRED)
add_executable(${PROJECT_NAME} src/circles.cpp)

# Add compass include directories to the target
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${compass_INCLUDE_DIRS})

Future Directions

  • Python bindings
  • Matrix preallocation functionality
  • Precomputed initial guesses for geometric methods
  • Non-templated geometric methods with default TaubinNewton initial guess
  • In place algorithm computation
  • CUDA++ parallelization (if supported)
  • Official support for multiple platforms
  • Interface for algorithm comparison
  • Improved genericity re: data types and matrix algebra libraries
  • ChernovLesort, ChernovHoussam, and other geometric methods with guaranteed convergence

Documentation

TODO

References

TODO

About

A robust header-only circle-fitting library written in C++17.


Languages

Language:C++ 90.5%Language:CMake 9.5%