compass
A robust header-only curve fitting library.
Summary • Usage • Installation • Documentation • References •
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
-
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