Skip to content

Commit

Permalink
Added dip::Polygon::FitCircle() and dip::Polygon::FitEllipse().
Browse files Browse the repository at this point in the history
  • Loading branch information
crisluengo committed Feb 2, 2025
1 parent a8950e0 commit e27b1b1
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 95 deletions.
8 changes: 8 additions & 0 deletions changelogs/diplib_next.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ date: 2020-00-00
- Added `dip::RankFromPercentile()`, which computes the rank (or index into the sorted array) for the given
percentile and array size. This was a computation done in many different functions in the library.

- Added `dip::Polygon::FitCircle()` and `dip::Polygon::FitEllipse()`. The former returns a new data structure
`dip::CircleParameters`.

### Changed functionality

- All functions that compute a percentile (`dip::Percentile()`, `dip::PercentilePosition()`,
Expand All @@ -30,6 +33,11 @@ date: 2020-00-00
This version of `dip::KMeansClustering()` additionally has overloads that take a `dip::Random` object as input,
instead of using a default-initialized one.

- `dip::CovarianceMatrix::EllipseParameters` is now `dip::EllipseParameters`, and adds a member `center` to encode
the coordinates of the center of the ellipse. This is the data structure returned by `dip::CovarianceMatrix::Ellipse()`,
which does not fill in the new `center` value. This new value is useful for the new function `dip::Polygon::FitEllipse()`.
An alias `dip::CovarianceMatrix::EllipseParameters` exists to avoid existing code breaking.

### Bug fixes

- Fixed `dip::IsotropicErosion()` to use the same structuring element size as `dip::IsotropicDilation()`.
Expand Down
165 changes: 111 additions & 54 deletions include/diplib/chain_code.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (c)2016-2022, Cris Luengo.
* (c)2016-2025, Cris Luengo.
* Based on original DIPlib code: (c)1995-2014, Delft University of Technology.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -38,52 +38,6 @@ namespace dip {
/// \addtogroup measurement


/// \brief Contains the various Feret diameters as returned by \ref dip::ConvexHull::Feret and \ref dip::ChainCode::Feret.
struct DIP_NO_EXPORT FeretValues {
dfloat maxDiameter = 0.0; ///< The maximum Feret diameter
dfloat minDiameter = 0.0; ///< The minimum Feret diameter
dfloat maxPerpendicular = 0.0; ///< The Feret diameter perpendicular to `minDiameter`
dfloat maxAngle = 0.0; ///< The angle at which `maxDiameter` was measured
dfloat minAngle = 0.0; ///< The angle at which `minDiameter` was measured
};

/// \brief Holds the various output values of the \ref dip::Polygon::RadiusStatistics function.
class DIP_NO_EXPORT RadiusValues {
public:
/// Returns the mean radius.
dfloat Mean() const { return vacc.Mean(); }
/// Returns the standard deviation of radii.
dfloat StandardDeviation() const { return vacc.StandardDeviation(); }
/// Returns the variance of radii.
dfloat Variance() const { return vacc.Variance(); }
/// Returns the maximum radius.
dfloat Maximum() const { return macc.Maximum(); }
/// Returns the minimum radius.
dfloat Minimum() const { return macc.Minimum(); }

/// Computes a circularity measure given by the coefficient of variation of the radii of the object.
dfloat Circularity() const {
return vacc.Mean() == 0.0 ? 0.0 : vacc.StandardDeviation() / vacc.Mean();
}

/// Multiple `RadiusValues` objects can be added together.
RadiusValues& operator+=( RadiusValues const& other ) {
vacc += other.vacc;
macc += other.macc;
return *this;
}

void Push( dfloat x ) {
vacc.Push( x );
macc.Push( x );
}

private:
VarianceAccumulator vacc;
MinMaxAccumulator macc;
};


//
// Vertex of a polygon
//
Expand Down Expand Up @@ -454,6 +408,73 @@ inline FloatArray BoundingBox< dfloat >::Size() const {
return { res.x, res.y };
}


//
// Support data structures
//


/// \brief Contains the various Feret diameters as returned by \ref dip::ConvexHull::Feret and \ref dip::ChainCode::Feret.
struct DIP_NO_EXPORT FeretValues {
dfloat maxDiameter = 0.0; ///< The maximum Feret diameter
dfloat minDiameter = 0.0; ///< The minimum Feret diameter
dfloat maxPerpendicular = 0.0; ///< The Feret diameter perpendicular to `minDiameter`
dfloat maxAngle = 0.0; ///< The angle at which `maxDiameter` was measured
dfloat minAngle = 0.0; ///< The angle at which `minDiameter` was measured
};

/// \brief Holds the various output values of the \ref dip::Polygon::RadiusStatistics function.
class DIP_NO_EXPORT RadiusValues {
public:
/// Returns the mean radius.
dfloat Mean() const { return vacc.Mean(); }
/// Returns the standard deviation of radii.
dfloat StandardDeviation() const { return vacc.StandardDeviation(); }
/// Returns the variance of radii.
dfloat Variance() const { return vacc.Variance(); }
/// Returns the maximum radius.
dfloat Maximum() const { return macc.Maximum(); }
/// Returns the minimum radius.
dfloat Minimum() const { return macc.Minimum(); }

/// Computes a circularity measure given by the coefficient of variation of the radii of the object.
dfloat Circularity() const {
return vacc.Mean() == 0.0 ? 0.0 : vacc.StandardDeviation() / vacc.Mean();
}

/// Multiple `RadiusValues` objects can be added together.
RadiusValues& operator+=( RadiusValues const& other ) {
vacc += other.vacc;
macc += other.macc;
return *this;
}

void Push( dfloat x ) {
vacc.Push( x );
macc.Push( x );
}

private:
VarianceAccumulator vacc;
MinMaxAccumulator macc;
};

/// \brief Represents a circle, returned by \ref dip::Polygon::FitCircle.
struct DIP_NO_EXPORT CircleParameters {
VertexFloat center = {0.0, 0.0}; ///< The center coordinates
dfloat diameter = 0.0; ///< The diameter
};

/// \brief Represents an ellipse, returned by \ref dip::CovarianceMatrix::Ellipse and \ref dip::Polygon::FitEllipse.
struct DIP_NO_EXPORT EllipseParameters {
VertexFloat center = {0.0, 0.0}; ///< The center coordinates
dfloat majorAxis = 0.0; ///< Length of the major axis (longest diameter)
dfloat minorAxis = 0.0; ///< Length of the minor axis (shortest diameter)
dfloat orientation = 0.0; ///< Orientation of the major axis (in radian)
dfloat eccentricity = 0.0; ///< Ellipse eccentricity, defined as $\sqrt{1 - b^2 / a^2}$, with $a$ equal to `majorAxis` and $b$ equal to `minorAxis`.
};


//
// Covariance matrix
//
Expand Down Expand Up @@ -538,13 +559,8 @@ class DIP_NO_EXPORT CovarianceMatrix {
return { mmu2 + sqroot, mmu2 - sqroot };
}

/// \brief Container for ellipse parameters.
struct EllipseParameters {
dfloat majorAxis; ///< Major axis length
dfloat minorAxis; ///< Minor axis length
dfloat orientation; ///< Orientation of major axis
dfloat eccentricity; ///< Ellipse eccentricity
};
using EllipseParameters = dip::EllipseParameters; // for backwards compatibility

/// \brief Compute parameters of ellipse with same covariance matrix.
///
/// If `solid` is `false` (default), then it is assumed that the covariance matrix corresponds to an ellipse
Expand All @@ -556,6 +572,7 @@ class DIP_NO_EXPORT CovarianceMatrix {
Eigenvalues lambda = Eig();
double scale = solid ? 16.0 : 8.0;
return {
{0.0, 0.0}, // No center coordinates are known here.
std::sqrt( scale * lambda.largest ),
std::sqrt( scale * lambda.smallest ),
// eigenvector is {xy, lambda.largest - xx}, always has an angle in the range [0,pi).
Expand Down Expand Up @@ -692,6 +709,46 @@ struct DIP_NO_EXPORT Polygon {
/// Acta Cytologica 21(5):455-464, 1977.
DIP_EXPORT dfloat BendingEnergy() const;

/// \brief Fits a circle to the polygon vertices.
///
/// The circle equation,
///
/// $$
/// (x-c_x)^2 + (y-c_y)^2 = r^2 \quad ,
/// $$
///
/// can be linearized,
///
/// \begin{align}
/// \begin{split}
/// a x + b y + c - x^2 - y^2 &= 0
/// \\ a &= 2 c_x
/// \\ b &= 2 c_y
/// \\ c &= r^2 - c_x^2 - c_y^2
/// \end{split}
/// \end{align}
///
/// We find the least-squares solution to the problem of fitting the vertex coordinates
/// to this linear equation.
DIP_EXPORT CircleParameters FitCircle() const;

/// \brief Fits an ellipse to the polygon vertices.
///
/// We find the least-squares solution to the fit of the polygon vertices to the general equation
/// for an ellipse,
///
/// $$
/// a x^2 + b xy + c y^2 + d x + e y - 1 = 0
/// $$
///
/// From the fitted parameters we can compute the ellipse parameters. If $b^2-4ac >= 0$, the fit
/// does not correspond to an ellipse, and the function will return a default-initialized
/// \ref EllipseParameters struct (all the values in it are zero).
///
/// !!! literature
/// - Wikipedia: ["Ellipse", section "General ellipse"](https://en.wikipedia.org/wiki/Ellipse#General_ellipse).
DIP_EXPORT EllipseParameters FitEllipse() const;

/// \brief Simplifies the polygon using the Douglas-Peucker algorithm.
///
/// For a polygon derived from a chain code, setting tolerance to 0.5 leads to a maximum-length digital straight
Expand Down
44 changes: 25 additions & 19 deletions pydip/src/documentation_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -1468,20 +1468,6 @@ constexpr char const* dip·ExtendImageToSize·Image·CL·Image·L·UnsignedArray
constexpr char const* dip·ExtendRegion·Image·L·RangeArray··BoundaryConditionArray· = "Fills the pixels outside a region in the image using a boundary condition.";
constexpr char const* dip·ExtendRegion·Image·L·RangeArray·CL·StringArray·CL = "Fills the pixels outside a region in the image using a boundary condition.";
constexpr char const* dip·ExtendRegion·Image·L·UnsignedArray··UnsignedArray··StringArray·CL = "Fills the pixels outside a region in the image using a boundary condition.";
constexpr char const* dip·FeretValues = "Contains the various Feret diameters as returned by `dip::ConvexHull::Feret`\nand `dip::ChainCode::Feret`.";
constexpr char const* dip·FeretValues·maxDiameter = "The maximum Feret diameter";
constexpr char const* dip·FeretValues·minDiameter = "The minimum Feret diameter";
constexpr char const* dip·FeretValues·maxPerpendicular = "The Feret diameter perpendicular to `minDiameter`";
constexpr char const* dip·FeretValues·maxAngle = "The angle at which `maxDiameter` was measured";
constexpr char const* dip·FeretValues·minAngle = "The angle at which `minDiameter` was measured";
constexpr char const* dip·RadiusValues = "Holds the various output values of the `dip::Polygon::RadiusStatistics`\nfunction.";
constexpr char const* dip·RadiusValues·Mean·C = "Returns the mean radius.";
constexpr char const* dip·RadiusValues·StandardDeviation·C = "Returns the standard deviation of radii.";
constexpr char const* dip·RadiusValues·Variance·C = "Returns the variance of radii.";
constexpr char const* dip·RadiusValues·Maximum·C = "Returns the maximum radius.";
constexpr char const* dip·RadiusValues·Minimum·C = "Returns the minimum radius.";
constexpr char const* dip·RadiusValues·Circularity·C = "Computes a circularity measure given by the coefficient of variation of the\nradii of the object.";
constexpr char const* dip·RadiusValues·operatorpluseq·RadiusValues·CL = "Multiple `RadiusValues` objects can be added together.";
constexpr char const* dip·Vertex·T = "Encodes a location in a 2D image.";
constexpr char const* dip·Vertex·T·x = "The x-coordinate";
constexpr char const* dip·Vertex·T·y = "The y-coordinate";
Expand Down Expand Up @@ -1539,6 +1525,29 @@ constexpr char const* dip·BoundingBox·T·Contains·VertexFloat· = "Tests to s
constexpr char const* dip·BoundingBox·T·Size·C = "Returns the size of the bounding box.";
constexpr char const* dip·BoundingBoxFloat = "A bounding box with floating-point coordinates.";
constexpr char const* dip·BoundingBoxInteger = "A bounding box with integer coordinates.";
constexpr char const* dip·FeretValues = "Contains the various Feret diameters as returned by `dip::ConvexHull::Feret`\nand `dip::ChainCode::Feret`.";
constexpr char const* dip·FeretValues·maxDiameter = "The maximum Feret diameter";
constexpr char const* dip·FeretValues·minDiameter = "The minimum Feret diameter";
constexpr char const* dip·FeretValues·maxPerpendicular = "The Feret diameter perpendicular to `minDiameter`";
constexpr char const* dip·FeretValues·maxAngle = "The angle at which `maxDiameter` was measured";
constexpr char const* dip·FeretValues·minAngle = "The angle at which `minDiameter` was measured";
constexpr char const* dip·RadiusValues = "Holds the various output values of the `dip::Polygon::RadiusStatistics`\nfunction.";
constexpr char const* dip·RadiusValues·Mean·C = "Returns the mean radius.";
constexpr char const* dip·RadiusValues·StandardDeviation·C = "Returns the standard deviation of radii.";
constexpr char const* dip·RadiusValues·Variance·C = "Returns the variance of radii.";
constexpr char const* dip·RadiusValues·Maximum·C = "Returns the maximum radius.";
constexpr char const* dip·RadiusValues·Minimum·C = "Returns the minimum radius.";
constexpr char const* dip·RadiusValues·Circularity·C = "Computes a circularity measure given by the coefficient of variation of the\nradii of the object.";
constexpr char const* dip·RadiusValues·operatorpluseq·RadiusValues·CL = "Multiple `RadiusValues` objects can be added together.";
constexpr char const* dip·CircleParameters = "Represents a circle, returned by `dip::Polygon::FitCircle`.";
constexpr char const* dip·CircleParameters·center = "The center coordinates";
constexpr char const* dip·CircleParameters·diameter = "The diameter";
constexpr char const* dip·EllipseParameters = "Represents an ellipse, returned by `dip::CovarianceMatrix::Ellipse` and\n`dip::Polygon::FitEllipse`.";
constexpr char const* dip·EllipseParameters·center = "The center coordinates";
constexpr char const* dip·EllipseParameters·majorAxis = "Length of the major axis (longest diameter)";
constexpr char const* dip·EllipseParameters·minorAxis = "Length of the minor axis (shortest diameter)";
constexpr char const* dip·EllipseParameters·orientation = "Orientation of the major axis (in radian)";
constexpr char const* dip·EllipseParameters·eccentricity = "Ellipse eccentricity, defined as $sqrt{1 - b^2 / a^2}$, with $a$ equal to\n`majorAxis` and $b$ equal to `minorAxis`.";
constexpr char const* dip·CovarianceMatrix = "A 2D covariance matrix for computation with 2D vertices.";
constexpr char const* dip·CovarianceMatrix·CovarianceMatrix = "Default-initialized covariance matrix is all zeros.";
constexpr char const* dip·CovarianceMatrix·CovarianceMatrix·VertexFloat· = "Construct a covariance matrix as the outer product of a vector and itself.";
Expand All @@ -1557,11 +1566,6 @@ constexpr char const* dip·CovarianceMatrix·Eigenvalues·largest = "Largest eig
constexpr char const* dip·CovarianceMatrix·Eigenvalues·smallest = "Smallest eigenvalue";
constexpr char const* dip·CovarianceMatrix·Eigenvalues·Eccentricity·C = "Computes eccentricity using the two eigenvalues of the covariance matrix.";
constexpr char const* dip·CovarianceMatrix·Eig·C = "Compute eigenvalues of matrix.";
constexpr char const* dip·CovarianceMatrix·EllipseParameters = "Container for ellipse parameters.";
constexpr char const* dip·CovarianceMatrix·EllipseParameters·majorAxis = "Major axis length";
constexpr char const* dip·CovarianceMatrix·EllipseParameters·minorAxis = "Minor axis length";
constexpr char const* dip·CovarianceMatrix·EllipseParameters·orientation = "Orientation of major axis";
constexpr char const* dip·CovarianceMatrix·EllipseParameters·eccentricity = "Ellipse eccentricity";
constexpr char const* dip·CovarianceMatrix·Ellipse·bool··C = "Compute parameters of ellipse with same covariance matrix.";
constexpr char const* dip·Polygon = "A polygon with floating-point vertices.";
constexpr char const* dip·Polygon·Vertices = "Type used to store the vertices.";
Expand All @@ -1582,6 +1586,8 @@ constexpr char const* dip·Polygon·EllipseVariance·C = "Compares a polygon to
constexpr char const* dip·Polygon·EllipseVariance·VertexFloat·CL·dip·CovarianceMatrix·CL·C = "Compares a polygon to the ellipse described by the given centroid and\ncovariance matrix, returning the coefficient of variation of the distance of\nvertices to the ellipse.";
constexpr char const* dip·Polygon·FractalDimension·dfloat··C = "Computes the fractal dimension of a polygon.";
constexpr char const* dip·Polygon·BendingEnergy·C = "Computes the bending energy of a polygon.";
constexpr char const* dip·Polygon·FitCircle·C = "Fits a circle to the polygon vertices.";
constexpr char const* dip·Polygon·FitEllipse·C = "Fits an ellipse to the polygon vertices.";
constexpr char const* dip·Polygon·Simplify·dfloat· = "Simplifies the polygon using the Douglas-Peucker algorithm.";
constexpr char const* dip·Polygon·Augment·dfloat· = "Adds vertices along each edge of the polygon such that the distance between\ntwo consecutive vertices is never more than `distance`.";
constexpr char const* dip·Polygon·Smooth·dfloat· = "Locally averages the location of vertices of a polygon so it becomes smoother.";
Expand Down
Loading

0 comments on commit e27b1b1

Please sign in to comment.