Skip to content

Commit 9c5f1e6

Browse files
authored
Merge pull request #13 from hbldh/release/v1.5.0
Release/v1.5.0
2 parents 7fb1f50 + 0d512a2 commit 9c5f1e6

File tree

5 files changed

+60
-18
lines changed

5 files changed

+60
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
- Documentation correction. Merged #12.
1616

17+
### Removed
18+
19+
- Deleted broken example script `scikit_image.py`.
20+
1721
## [1.4.1] (2020-09-28)
1822

1923
### Added

docs/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ This section describes the general usage patterns of :py:mod:`pyefd`.
5454
The coefficients returned are the :math:`a_n`, :math:`b_n`, :math:`c_n` and :math:`d_n` of
5555
the following Fourier series representation of the shape.
5656

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

6060
.. code:: python
6161
6262
from pyefd import elliptic_fourier_descriptors
63-
coeffs = elliptic_fourier_descriptors(contour, order=10, normalize=False)
63+
coeffs = elliptic_fourier_descriptors(contour, order=10, normalize=True)
6464
6565
Normalization can also be done afterwards:
6666

pyefd.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,25 @@
3434
_range = range
3535

3636

37-
def elliptic_fourier_descriptors(contour, order=10, normalize=False):
37+
def elliptic_fourier_descriptors(
38+
contour, order=10, normalize=False, return_transformation=False
39+
):
3840
"""Calculate elliptical Fourier descriptors for a contour.
3941
4042
:param numpy.ndarray contour: A contour array of size ``[M x 2]``.
4143
:param int order: The order of Fourier coefficients to calculate.
4244
:param bool normalize: If the coefficients should be normalized;
4345
see references for details.
44-
:return: A ``[order x 4]`` array of Fourier coefficients.
45-
:rtype: :py:class:`numpy.ndarray`
46+
:param bool return_transformation: If the normalization parametres should be returned.
47+
Default is ``False``.
48+
:return: A ``[order x 4]`` array of Fourier coefficients and optionally the
49+
transformation parametres ``scale``, ``psi_1`` (rotation) and ``theta_1`` (phase)
50+
:rtype: ::py:class:`numpy.ndarray` or (:py:class:`numpy.ndarray`, (float, float, float))
4651
4752
"""
4853
dxy = np.diff(contour, axis=0)
4954
dt = np.sqrt((dxy ** 2).sum(axis=1))
50-
t = np.concatenate([([0.]), np.cumsum(dt)])
55+
t = np.concatenate([([0.0]), np.cumsum(dt)])
5156
T = t[-1]
5257

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

7681
if normalize:
77-
coeffs = normalize_efd(coeffs)
82+
coeffs = normalize_efd(coeffs, return_transformation=return_transformation)
7883

7984
return coeffs
8085

8186

82-
def normalize_efd(coeffs, size_invariant=True):
87+
def normalize_efd(coeffs, size_invariant=True, return_transformation=False):
8388
"""Normalizes an array of Fourier coefficients.
8489
8590
See [#a]_ and [#b]_ for details.
8691
8792
:param numpy.ndarray coeffs: A ``[n x 4]`` Fourier coefficient array.
8893
:param bool size_invariant: If size invariance normalizing should be done as well.
8994
Default is ``True``.
90-
:return: The normalized ``[n x 4]`` Fourier coefficient array.
91-
:rtype: :py:class:`numpy.ndarray`
95+
:param bool return_transformation: If the normalization parametres should be returned.
96+
Default is ``False``.
97+
:return: The normalized ``[n x 4]`` Fourier coefficient array and optionally the
98+
transformation parametres ``scale``, :math:`psi_1` (rotation) and :math:`theta_1` (phase)
99+
:rtype: :py:class:`numpy.ndarray` or (:py:class:`numpy.ndarray`, (float, float, float))
92100
93101
"""
94102
# Make the coefficients have a zero phase shift from
@@ -136,11 +144,15 @@ def normalize_efd(coeffs, size_invariant=True):
136144
)
137145
).flatten()
138146

147+
size = coeffs[0, 0]
139148
if size_invariant:
140149
# Obtain size-invariance by normalizing.
141-
coeffs /= np.abs(coeffs[0, 0])
150+
coeffs /= np.abs(size)
142151

143-
return coeffs
152+
if return_transformation:
153+
return coeffs, (size, psi_1, theta_1)
154+
else:
155+
return coeffs
144156

145157

146158
def calculate_dc_coefficients(contour):
@@ -153,7 +165,7 @@ def calculate_dc_coefficients(contour):
153165
"""
154166
dxy = np.diff(contour, axis=0)
155167
dt = np.sqrt((dxy ** 2).sum(axis=1))
156-
t = np.concatenate([([0.]), np.cumsum(dt)])
168+
t = np.concatenate([([0.0]), np.cumsum(dt)])
157169
T = t[-1]
158170

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

201213

202-
def plot_efd(coeffs, locus=(0., 0.), image=None, contour=None, n=300):
214+
def plot_efd(coeffs, locus=(0.0, 0.0), image=None, contour=None, n=300):
203215
"""Plot a ``[2 x (N / 2)]`` grid of successive truncations of the series.
204216
205217
.. note::

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
2323
AUTHOR = "Henrik Blidh"
2424
REQUIRES_PYTHON = ">=2.7.10"
25-
VERSION = "1.4.1"
25+
VERSION = "1.5.0"
2626

2727
REQUIRED = ["numpy>=1.7.0"]
2828

tests.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import numpy as np
2020
from scipy.spatial.distance import directed_hausdorff
21+
from math import pi
2122

2223
import pyefd
2324

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

10101011

1012+
def test_normalizing_3():
1013+
# rotate and scale contour_1 by a known amount
1014+
theta = np.radians(30)
1015+
c, s = np.cos(theta), np.sin(theta)
1016+
R = np.array(((c, -s), (s, c))) * 1.5
1017+
contour_2 = np.transpose(np.dot(R, np.transpose(contour_1)))
1018+
1019+
c1, t1 = pyefd.elliptic_fourier_descriptors(
1020+
contour_1, normalize=True, return_transformation=True
1021+
)
1022+
c2, t2 = pyefd.elliptic_fourier_descriptors(
1023+
contour_2, normalize=True, return_transformation=True
1024+
)
1025+
1026+
# check if coefficients are equal (invariance)
1027+
np.testing.assert_almost_equal(c1, c2, decimal=12)
1028+
# check if normalization parametres match the initial transform
1029+
np.testing.assert_almost_equal(t1[0] * 1.5, t2[0], decimal=12)
1030+
np.testing.assert_almost_equal(
1031+
(t1[1] + np.radians(30)) % (2 * pi), t2[1], decimal=12
1032+
)
1033+
1034+
10111035
def test_locus():
10121036
locus = pyefd.calculate_dc_coefficients(contour_1)
10131037
np.testing.assert_array_almost_equal(locus, np.mean(contour_1, axis=0), decimal=0)
10141038

10151039

10161040
def test_reconstruct_simple_contour():
1017-
simple_polygon = np.array([[1., 1.], [0., 1.], [0., 0.], [1., 0.], [1., 1.]])
1041+
simple_polygon = np.array(
1042+
[[1.0, 1.0], [0.0, 1.0], [0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]
1043+
)
10181044
number_of_points = simple_polygon.shape[0]
10191045
locus = pyefd.calculate_dc_coefficients(simple_polygon)
10201046
coeffs = pyefd.elliptic_fourier_descriptors(simple_polygon, order=30)
@@ -1049,7 +1075,7 @@ def for_loop_efd(contour, order=10, normalize=False):
10491075
"""
10501076
dxy = np.diff(contour, axis=0)
10511077
dt = np.sqrt((dxy ** 2).sum(axis=1))
1052-
t = np.concatenate([([0.]), np.cumsum(dt)])
1078+
t = np.concatenate([([0.0]), np.cumsum(dt)])
10531079
T = t[-1]
10541080

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

0 commit comments

Comments
 (0)