Skip to content

Commit

Permalink
Merge pull request #13 from hbldh/release/v1.5.0
Browse files Browse the repository at this point in the history
Release/v1.5.0
  • Loading branch information
hbldh authored Jan 22, 2021
2 parents 7fb1f50 + 0d512a2 commit 9c5f1e6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Documentation correction. Merged #12.

### Removed

- Deleted broken example script `scikit_image.py`.

## [1.4.1] (2020-09-28)

### Added
Expand Down
6 changes: 3 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ This section describes the general usage patterns of :py:mod:`pyefd`.
The coefficients returned are the :math:`a_n`, :math:`b_n`, :math:`c_n` and :math:`d_n` of
the following Fourier series representation of the shape.

The coefficients returned are by default normalized so that they are
rotation and size-invariant. This can be overridden by calling:
The coefficients returned can be normalized so that they are
rotation and size-invariant. This can be achieved by calling:

.. code:: python
from pyefd import elliptic_fourier_descriptors
coeffs = elliptic_fourier_descriptors(contour, order=10, normalize=False)
coeffs = elliptic_fourier_descriptors(contour, order=10, normalize=True)
Normalization can also be done afterwards:

Expand Down
36 changes: 24 additions & 12 deletions pyefd.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,25 @@
_range = range


def elliptic_fourier_descriptors(contour, order=10, normalize=False):
def elliptic_fourier_descriptors(
contour, order=10, normalize=False, return_transformation=False
):
"""Calculate elliptical Fourier descriptors for a contour.
:param numpy.ndarray contour: A contour array of size ``[M x 2]``.
:param int order: The order of Fourier coefficients to calculate.
:param bool normalize: If the coefficients should be normalized;
see references for details.
:return: A ``[order x 4]`` array of Fourier coefficients.
:rtype: :py:class:`numpy.ndarray`
:param bool return_transformation: If the normalization parametres should be returned.
Default is ``False``.
:return: A ``[order x 4]`` array of Fourier coefficients and optionally the
transformation parametres ``scale``, ``psi_1`` (rotation) and ``theta_1`` (phase)
:rtype: ::py:class:`numpy.ndarray` or (:py:class:`numpy.ndarray`, (float, float, float))
"""
dxy = np.diff(contour, axis=0)
dt = np.sqrt((dxy ** 2).sum(axis=1))
t = np.concatenate([([0.]), np.cumsum(dt)])
t = np.concatenate([([0.0]), np.cumsum(dt)])
T = t[-1]

phi = (2 * np.pi * t) / T
Expand All @@ -74,21 +79,24 @@ def elliptic_fourier_descriptors(contour, order=10, normalize=False):
)

if normalize:
coeffs = normalize_efd(coeffs)
coeffs = normalize_efd(coeffs, return_transformation=return_transformation)

return coeffs


def normalize_efd(coeffs, size_invariant=True):
def normalize_efd(coeffs, size_invariant=True, return_transformation=False):
"""Normalizes an array of Fourier coefficients.
See [#a]_ and [#b]_ for details.
:param numpy.ndarray coeffs: A ``[n x 4]`` Fourier coefficient array.
:param bool size_invariant: If size invariance normalizing should be done as well.
Default is ``True``.
:return: The normalized ``[n x 4]`` Fourier coefficient array.
:rtype: :py:class:`numpy.ndarray`
:param bool return_transformation: If the normalization parametres should be returned.
Default is ``False``.
:return: The normalized ``[n x 4]`` Fourier coefficient array and optionally the
transformation parametres ``scale``, :math:`psi_1` (rotation) and :math:`theta_1` (phase)
:rtype: :py:class:`numpy.ndarray` or (:py:class:`numpy.ndarray`, (float, float, float))
"""
# Make the coefficients have a zero phase shift from
Expand Down Expand Up @@ -136,11 +144,15 @@ def normalize_efd(coeffs, size_invariant=True):
)
).flatten()

size = coeffs[0, 0]
if size_invariant:
# Obtain size-invariance by normalizing.
coeffs /= np.abs(coeffs[0, 0])
coeffs /= np.abs(size)

return coeffs
if return_transformation:
return coeffs, (size, psi_1, theta_1)
else:
return coeffs


def calculate_dc_coefficients(contour):
Expand All @@ -153,7 +165,7 @@ def calculate_dc_coefficients(contour):
"""
dxy = np.diff(contour, axis=0)
dt = np.sqrt((dxy ** 2).sum(axis=1))
t = np.concatenate([([0.]), np.cumsum(dt)])
t = np.concatenate([([0.0]), np.cumsum(dt)])
T = t[-1]

xi = np.cumsum(dxy[:, 0]) - (dxy[:, 0] / dt) * t[1:]
Expand Down Expand Up @@ -199,7 +211,7 @@ def reconstruct_contour(coeffs, locus=(0, 0), num_points=300):
return reconstruction


def plot_efd(coeffs, locus=(0., 0.), image=None, contour=None, n=300):
def plot_efd(coeffs, locus=(0.0, 0.0), image=None, contour=None, n=300):
"""Plot a ``[2 x (N / 2)]`` grid of successive truncations of the series.
.. note::
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
EMAIL = "[email protected]"
AUTHOR = "Henrik Blidh"
REQUIRES_PYTHON = ">=2.7.10"
VERSION = "1.4.1"
VERSION = "1.5.0"

REQUIRED = ["numpy>=1.7.0"]

Expand Down
30 changes: 28 additions & 2 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import numpy as np
from scipy.spatial.distance import directed_hausdorff
from math import pi

import pyefd

Expand Down Expand Up @@ -1008,13 +1009,38 @@ def test_normalizing_2():
np.testing.assert_almost_equal(c[0, 2], 0.0, decimal=14)


def test_normalizing_3():
# rotate and scale contour_1 by a known amount
theta = np.radians(30)
c, s = np.cos(theta), np.sin(theta)
R = np.array(((c, -s), (s, c))) * 1.5
contour_2 = np.transpose(np.dot(R, np.transpose(contour_1)))

c1, t1 = pyefd.elliptic_fourier_descriptors(
contour_1, normalize=True, return_transformation=True
)
c2, t2 = pyefd.elliptic_fourier_descriptors(
contour_2, normalize=True, return_transformation=True
)

# check if coefficients are equal (invariance)
np.testing.assert_almost_equal(c1, c2, decimal=12)
# check if normalization parametres match the initial transform
np.testing.assert_almost_equal(t1[0] * 1.5, t2[0], decimal=12)
np.testing.assert_almost_equal(
(t1[1] + np.radians(30)) % (2 * pi), t2[1], decimal=12
)


def test_locus():
locus = pyefd.calculate_dc_coefficients(contour_1)
np.testing.assert_array_almost_equal(locus, np.mean(contour_1, axis=0), decimal=0)


def test_reconstruct_simple_contour():
simple_polygon = np.array([[1., 1.], [0., 1.], [0., 0.], [1., 0.], [1., 1.]])
simple_polygon = np.array(
[[1.0, 1.0], [0.0, 1.0], [0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]
)
number_of_points = simple_polygon.shape[0]
locus = pyefd.calculate_dc_coefficients(simple_polygon)
coeffs = pyefd.elliptic_fourier_descriptors(simple_polygon, order=30)
Expand Down Expand Up @@ -1049,7 +1075,7 @@ def for_loop_efd(contour, order=10, normalize=False):
"""
dxy = np.diff(contour, axis=0)
dt = np.sqrt((dxy ** 2).sum(axis=1))
t = np.concatenate([([0.]), np.cumsum(dt)])
t = np.concatenate([([0.0]), np.cumsum(dt)])
T = t[-1]

phi = (2 * np.pi * t) / T
Expand Down

0 comments on commit 9c5f1e6

Please sign in to comment.