Skip to content

Commit 321eaad

Browse files
committed
More documentation added. Setup.py updated.
1 parent a5ea1c2 commit 321eaad

File tree

7 files changed

+168
-51
lines changed

7 files changed

+168
-51
lines changed

HISTORY.rst

Whitespace-only changes.

MANIFEST.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
include LICENSE README.md requirements.txt
2-
include README.rst LICENSE NOTICE HISTORY.rst test_requests.py requirements.txt requests/cacert.pem

README.md

Lines changed: 0 additions & 25 deletions
This file was deleted.

README.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
PyEFD
2+
=====
3+
4+
.. image:: https://travis-ci.org/hbldh/pyefd.svg?branch=master
5+
:target: https://travis-ci.org/hbldh/pyefd
6+
.. image:: http://img.shields.io/pypi/v/pyefd.svg
7+
:target: https://pypi.python.org/pypi/pyefd/
8+
.. image:: http://img.shields.io/pypi/dm/pyefd.svg
9+
:target: https://pypi.python.org/pypi/pyefd/
10+
.. image:: http://img.shields.io/pypi/l/pyefd.svg
11+
:target: https://pypi.python.org/pypi/pyefd/
12+
13+
An Python/NumPy implementation of the method described in \[1\].
14+
15+
Usage
16+
-----
17+
::
18+
19+
$ pip install pyefd
20+
21+
Given a closed contour of a shape, this package can fit a
22+
[Fourier series](https://en.wikipedia.org/wiki/Fourier_series)
23+
approximating the shape of the contour::
24+
25+
from pyefd import elliptic_fourier_descriptors
26+
coeffs = elliptic_fourier_descriptors(contour, order=10)
27+
28+
The coefficients returned are the :math:`a_n`, :math:`b_n`, :math:`c_n` and :math:`d_n` of
29+
the following Fourier series representation of the shape:
30+
31+
.. math::
32+
\begin{align*}
33+
\hat{x}(t) & = A_0 + \sum_{n=1}^N\left( a_n \cos \frac{2n\pi t}{T} + b_n \sin \frac{2n\pi t}{T} \right)
34+
\hat{y}(t) & = C_0 + \sum_{n=1}^N\left( c_n \cos \frac{2n\pi t}{T} + d_n \sin \frac{2n\pi t}{T} \right)
35+
\end{align*}
36+
37+
See \[1\] for more technical details.
38+
39+
Testing
40+
-------
41+
42+
Run tests with::
43+
44+
$ python setup.py test
45+
46+
or with [Pytest](http://pytest.org/latest/)::
47+
48+
$ py.test tests.py
49+
50+
The tests includes a single image from the MNIST dataset of handwritten digits (\[2\]) as a contour to use
51+
for testing.
52+
53+
Documentation
54+
-------------
55+
56+
See the [Github pages](http://hbldh.github.io/pyefd).
57+
58+
References
59+
----------
60+
61+
\[1\] [Frank P Kuhl, Charles R Giardina, Elliptic Fourier features of a closed contour,
62+
Computer Graphics and Image Processing, Volume 18, Issue 3, 1982, Pages 236-258,
63+
ISSN 0146-664X, http://dx.doi.org/10.1016/0146-664X(82)90034-X.](http://www.sciencedirect.com/science/article/pii/0146664X8290034X)
64+
65+
\[2\] [LeCun et al. (1999): The MNIST Dataset Of Handwritten Digits](http://yann.lecun.com/exdb/mnist/)

pyefd.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33
"""
44
:mod:`efd`
5-
==================
5+
==========
66
77
Created by hbldh <henrik.blidh@nedomkull.com>
88
Created on 2016-01-30
@@ -37,7 +37,7 @@
3737
_range = range
3838

3939

40-
def elliptical_fourier_descriptors(contour, order=10, normalize=False):
40+
def elliptic_fourier_descriptors(contour, order=10, normalize=False):
4141
"""Calculate elliptical Fourier descriptors for a contour.
4242
4343
:param contour: A contour array of size [M x 2].
@@ -53,10 +53,10 @@ def elliptical_fourier_descriptors(contour, order=10, normalize=False):
5353
"""
5454
dxy = np.diff(contour, axis=0)
5555
dt = np.sqrt((dxy ** 2).sum(axis=1))
56-
t = np.cumsum(dt)
56+
t = np.concatenate([([0., ]), np.cumsum(dt)])
5757
T = t[-1]
5858

59-
phi = np.concatenate([([0., ]), (2 * np.pi * t) / T])
59+
phi = (2 * np.pi * t) / T
6060

6161
coeffs = np.zeros((order, 4))
6262
for n in _range(1, order + 1):
@@ -83,6 +83,9 @@ def normalize_efd(coeffs, size_invariant=True):
8383
8484
:param coeffs: A [n x 4] Fourier coefficient array.
8585
:type coeffs: :py:class:`numpy.ndarray`
86+
:param size_invariant: If size invariance normalizing should be done as well.
87+
Default is `True`
88+
:type size_invariant: bool
8689
:return: The normalized [n x 4] Fourier coefficient array.
8790
:rtype: :py:class:`numpy.ndarray`
8891
@@ -118,7 +121,31 @@ def normalize_efd(coeffs, size_invariant=True):
118121
return coeffs
119122

120123

121-
def plot_efd(contour, coeffs, locus=(0., 0.), n=300):
124+
def calculate_dc_coefficients(contour):
125+
"""Calculate the A0 and C0 coefficients of the elliptic Fourier series
126+
127+
:param contour: A contour array of size [M x 2].
128+
:type contour: :py:class:`numpy.ndarray`
129+
:return: The A0 and C0 coefficients.
130+
:rtype: tuple
131+
132+
"""
133+
dxy = np.diff(contour, axis=0)
134+
dt = np.sqrt((dxy ** 2).sum(axis=1))
135+
t = np.concatenate([([0., ]), np.cumsum(dt)])
136+
T = t[-1]
137+
138+
xi = np.cumsum(dxy[:, 0]) - (dxy[:, 0] / dt) * t[1:]
139+
A0 = (1 / T) * np.sum(((dxy[:, 0] / (2 * dt)) * np.diff(t ** 2)) + xi * dt)
140+
delta = np.cumsum(dxy[:, 1]) - (dxy[:, 1] / dt) * t[1:]
141+
C0 = (1 / T) * np.sum(((dxy[:, 1] / (2 * dt)) * np.diff(t ** 2)) + delta * dt)
142+
143+
# A0 and CO relate to the first point of the contour array as origin.
144+
# Adding those values to the coefficients to make them relate to true origin.
145+
return contour[0, 0] + A0, contour[0, 1] + C0
146+
147+
148+
def plot_efd(coeffs, locus=(0., 0.), image=None, contour=None, n=300):
122149
"""Plot a [2 x (n/2)] grid of successive truncations of the series.
123150
124151
:param coeffs: [n x 4] Fourier coefficient array.
@@ -136,20 +163,25 @@ def plot_efd(contour, coeffs, locus=(0., 0.), n=300):
136163
return
137164

138165
N = coeffs.shape[0]
166+
N_half = int(np.ceil(N / 2))
167+
n_rows = 2
168+
139169
t = np.linspace(0, 1.0, n)
140170
xt = np.ones((n,)) * locus[0]
141171
yt = np.ones((n,)) * locus[1]
142172

143-
ax = plt.subplot2grid((3, N // 2), (0, 0), colspan=N//2)
144-
ax.imshow(contour, plt.cm.gray)
145173
for n in _range(coeffs.shape[0]):
146174
xt += (coeffs[n, 0] * np.cos(2 * (n + 1) * np.pi * t)) + \
147175
(coeffs[n, 1] * np.sin(2 * (n + 1) * np.pi * t))
148176
yt += (coeffs[n, 2] * np.cos(2 * (n + 1) * np.pi * t)) + \
149177
(coeffs[n, 3] * np.sin(2 * (n + 1) * np.pi * t))
150-
ax = plt.subplot2grid((3, N // 2), (n // (N // 2) + 1, n % (N // 2)))
178+
ax = plt.subplot2grid((n_rows, N_half), (n // N_half, n % N_half))
151179
ax.set_title(str(n + 1))
152-
ax.plot(yt, -xt, 'r')
180+
if contour is not None:
181+
ax.plot(contour[:, 1], contour[:, 0], 'c--', linewidth=2)
182+
ax.plot(yt, xt, 'r', linewidth=2)
183+
if image is not None:
184+
ax.imshow(image, plt.cm.gray)
153185

154186
plt.show()
155187

setup.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,33 @@
1818
from __future__ import absolute_import
1919

2020
import os
21-
from setuptools import setup, find_packages
21+
import sys
22+
from codecs import open
23+
from setuptools import setup
24+
25+
26+
if sys.argv[-1] == 'publish':
27+
os.system('python setup.py register')
28+
os.system('python setup.py sdist upload')
29+
os.system('python setup.py bdist_wheel upload --universal')
30+
sys.exit()
31+
32+
33+
version = '0.1.0.dev1'
34+
requires = ["numpy>=1.7.0"]
35+
36+
37+
def read(f):
38+
return open(f, encoding='utf-8').read()
2239

23-
# Get the long description from the README file
24-
try:
25-
with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.rst')) as f:
26-
long_description = f.read()
27-
except:
28-
with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')) as f:
29-
long_description = f.read()
3040

3141
setup(
3242
name='pyefd',
33-
version='0.1.0.dev1',
43+
version=version,
3444
author='Henrik Blidh',
3545
author_email='henrik.blidh@nedomkull.com',
3646
description='Python implementation of "Elliptic Fourier Features of a Closed Contour"',
37-
long_description=long_description,
47+
long_description=read('README.rst') + '\n\n' + read('HISTORY.rst'),
3848
license='MIT',
3949
url='https://github.com/hbldh/pyefd',
4050
classifiers=[
@@ -53,9 +63,13 @@
5363
'Programming Language :: Python :: 3.4',
5464
'Programming Language :: Python :: 3.5',
5565
],
56-
keywords=["elliptical fourier descriptors", "shape descriptors", "image analysis"],
57-
packages=find_packages(exclude=('tests', )),
58-
install_requires=[],
66+
keywords=["elliptic fourier descriptors", "shape descriptors", "image analysis"],
67+
py_modules=['pyefd'],
68+
test_suite="tests",
69+
zip_safe=False,
70+
include_package_data=True,
71+
platforms='any',
72+
install_requires=requires,
5973
package_data={},
6074
dependency_links=[],
6175
ext_modules=[],

tests.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,41 @@ def tearDown(self):
119119
pass
120120

121121
def test_efd_shape_1(self):
122-
c = pyefd.elliptical_fourier_descriptors(contour_1, order=10)
123-
assert c.shape == (10, 4)
122+
coeffs = pyefd.elliptic_fourier_descriptors(contour_1, order=10)
123+
assert coeffs.shape == (10, 4)
124124

125125
def test_efd_shape_2(self):
126-
c = pyefd.elliptical_fourier_descriptors(contour_1, order=40)
126+
c = pyefd.elliptic_fourier_descriptors(contour_1, order=40)
127127
assert c.shape == (40, 4)
128+
129+
def test_normalizing_1(self):
130+
c = pyefd.elliptic_fourier_descriptors(contour_1, normalize=False)
131+
assert np.abs(c[0,0]) > 0.0
132+
assert np.abs(c[0,1]) > 0.0
133+
assert np.abs(c[0,2]) > 0.0
134+
135+
def test_normalizing_2(self):
136+
c = pyefd.elliptic_fourier_descriptors(contour_1, normalize=True)
137+
np.testing.assert_almost_equal(c[0, 0], 1.0, decimal=14)
138+
np.testing.assert_almost_equal(c[0, 1], 0.0, decimal=14)
139+
np.testing.assert_almost_equal(c[0, 2], 0.0, decimal=14)
140+
141+
def test_locus(self):
142+
locus = pyefd.calculate_dc_coefficients(contour_1)
143+
np.testing.assert_array_almost_equal(locus, np.mean(contour_1, axis=0), decimal=0)
144+
145+
def test_fit_1(self):
146+
n = 300
147+
locus = pyefd.calculate_dc_coefficients(contour_1)
148+
coeffs = pyefd.elliptic_fourier_descriptors(contour_1, order=20)
149+
150+
t = np.linspace(0, 1.0, n)
151+
xt = np.ones((n,)) * locus[0]
152+
yt = np.ones((n,)) * locus[1]
153+
154+
for n in pyefd._range(coeffs.shape[0]):
155+
xt += (coeffs[n, 0] * np.cos(2 * (n + 1) * np.pi * t)) + \
156+
(coeffs[n, 1] * np.sin(2 * (n + 1) * np.pi * t))
157+
yt += (coeffs[n, 2] * np.cos(2 * (n + 1) * np.pi * t)) + \
158+
(coeffs[n, 3] * np.sin(2 * (n + 1) * np.pi * t))
159+

0 commit comments

Comments
 (0)