Skip to content

Commit a5ea1c2

Browse files
committed
Initial commit
0 parents  commit a5ea1c2

File tree

9 files changed

+575
-0
lines changed

9 files changed

+575
-0
lines changed

.gitignore

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
3+
train-images-idx3-ubyte.gz
4+
train-labels-idx1-ubyte.gz
5+
6+
### Python template
7+
# Byte-compiled / optimized / DLL files
8+
__pycache__/
9+
*.py[cod]
10+
*$py.class
11+
12+
# C extensions
13+
*.so
14+
15+
# Distribution / packaging
16+
.Python
17+
env/
18+
build/
19+
develop-eggs/
20+
dist/
21+
downloads/
22+
eggs/
23+
.eggs/
24+
lib/
25+
lib64/
26+
parts/
27+
sdist/
28+
var/
29+
*.egg-info/
30+
.installed.cfg
31+
*.egg
32+
33+
# PyInstaller
34+
# Usually these files are written by a python script from a template
35+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
36+
*.manifest
37+
*.spec
38+
39+
# Installer logs
40+
pip-log.txt
41+
pip-delete-this-directory.txt
42+
43+
# Unit test / coverage reports
44+
htmlcov/
45+
.tox/
46+
.coverage
47+
.coverage.*
48+
.cache
49+
nosetests.xml
50+
coverage.xml
51+
*,cover
52+
53+
# Translations
54+
*.mo
55+
*.pot
56+
57+
# Django stuff:
58+
*.log
59+
60+
# Sphinx documentation
61+
docs/_build/
62+
63+
# PyBuilder
64+
target/
65+
### Windows template
66+
# Windows image file caches
67+
Thumbs.db
68+
ehthumbs.db
69+
70+
# Folder config file
71+
Desktop.ini
72+
73+
# Recycle Bin used on file shares
74+
$RECYCLE.BIN/
75+
76+
# Windows Installer files
77+
*.cab
78+
*.msi
79+
*.msm
80+
*.msp
81+
82+
# Windows shortcuts
83+
*.lnk
84+
### IPythonNotebook template
85+
# Temporary data
86+
.ipynb_checkpoints/
87+
### Linux template
88+
*~
89+
90+
# KDE directory preferences
91+
.directory
92+
93+
# Linux trash folder which might appear on any partition or disk
94+
.Trash-*
95+
### JetBrains template
96+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
97+
98+
*.iml
99+
100+
## Directory-based project format:
101+
.idea/
102+
# if you remove the above rule, at least ignore the following:
103+
104+
# User-specific stuff:
105+
# .idea/workspace.xml
106+
# .idea/tasks.xml
107+
# .idea/dictionaries
108+
109+
# Sensitive or high-churn files:
110+
# .idea/dataSources.ids
111+
# .idea/dataSources.xml
112+
# .idea/sqlDataSources.xml
113+
# .idea/dynamic.xml
114+
# .idea/uiDesigner.xml
115+
116+
# Gradle:
117+
# .idea/gradle.xml
118+
# .idea/libraries
119+
120+
# Mongo Explorer plugin:
121+
# .idea/mongoSettings.xml
122+
123+
## File-based project format:
124+
*.ipr
125+
*.iws
126+
127+
## Plugin-specific files:
128+
129+
# IntelliJ
130+
/out/
131+
132+
# mpeltonen/sbt-idea plugin
133+
.idea_modules/
134+
135+
# JIRA plugin
136+
atlassian-ide-plugin.xml
137+
138+
# Crashlytics plugin (for Android Studio and IntelliJ)
139+
com_crashlytics_export_strings.xml
140+
crashlytics.properties
141+
crashlytics-build.properties
142+
### C template
143+
# Object files
144+
*.o
145+
*.ko
146+
*.obj
147+
*.elf
148+
149+
# Precompiled Headers
150+
*.gch
151+
*.pch
152+
153+
# Libraries
154+
*.lib
155+
*.a
156+
*.la
157+
*.lo
158+
159+
# Shared objects (inc. Windows DLLs)
160+
*.dll
161+
*.so
162+
*.so.*
163+
*.dylib
164+
165+
# Executables
166+
*.exe
167+
*.out
168+
*.app
169+
*.i*86
170+
*.x86_64
171+
*.hex
172+
173+
# Debug files
174+
*.dSYM/
175+

.travis.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
language: python
2+
sudo: false
3+
python:
4+
- 2.6
5+
- 2.7
6+
- 3.3
7+
- 3.4
8+
- 3.5
9+
- "3.5-dev"
10+
- "nightly"
11+
- pypy
12+
matrix:
13+
allow_failures:
14+
- python: 2.6
15+
- python: "3.5-dev"
16+
- python: "nightly"
17+
- python: pypy
18+
branches:
19+
only:
20+
- master
21+
- develop
22+
install:
23+
- "pip install -r requirements.txt"
24+
script: py.test tests.py

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# PyEFD
2+
3+
[![Build Status](https://travis-ci.org/hbldh/pyefd.svg)](https://travis-ci.org/hbldh/pyefd)
4+
5+
An Python/NumPy implementation of the method described in \[1\].
6+
7+
## Installation
8+
9+
TBD
10+
11+
## Testing
12+
13+
TBD
14+
15+
## Documentation
16+
17+
TBD
18+
19+
## References
20+
21+
\[1\] [Frank P Kuhl, Charles R Giardina, Elliptic Fourier features of a closed contour,
22+
Computer Graphics and Image Processing, Volume 18, Issue 3, 1982, Pages 236-258,
23+
ISSN 0146-664X, http://dx.doi.org/10.1016/0146-664X(82)90034-X.](http://www.sciencedirect.com/science/article/pii/0146664X8290034X)
24+
25+
\[2\] [LeCun et al. (1999): The MNIST Dataset Of Handwritten Digits](http://yann.lecun.com/exdb/mnist/)

pyefd.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
:mod:`efd`
5+
==================
6+
7+
Created by hbldh <henrik.blidh@nedomkull.com>
8+
Created on 2016-01-30
9+
10+
A Python implementation of the method described in [1]_ for
11+
calculating Fourier coefficients for characterizing
12+
closed contours.
13+
14+
References:
15+
-----------
16+
17+
.. [1] F. P. Kuhl and C. R. Giardina, “Elliptic Fourier Features of a
18+
Closed Contour," Computer Vision, Graphics and Image Processing,
19+
Vol. 18, pp. 236-258, 1982.
20+
21+
.. [2] Oivind Due Trier, Anil K. Jain and Torfinn Taxt, “Feature Extraction
22+
Methods for Character Recognition - A Survey”, Pattern Recognition
23+
Vol. 29, No.4, pp. 641-662 (1996)
24+
25+
"""
26+
27+
from __future__ import division
28+
from __future__ import print_function
29+
from __future__ import unicode_literals
30+
from __future__ import absolute_import
31+
32+
import numpy as np
33+
34+
try:
35+
_range = xrange
36+
except NameError:
37+
_range = range
38+
39+
40+
def elliptical_fourier_descriptors(contour, order=10, normalize=False):
41+
"""Calculate elliptical Fourier descriptors for a contour.
42+
43+
:param contour: A contour array of size [M x 2].
44+
:type contour: :py:class:`numpy.ndarray`
45+
:param order: The order of Fourier coefficients to calculate.
46+
:type order: int
47+
:param normalize: If the coefficients should be normalized
48+
as is described in [1]_ and [2]_.
49+
:type normalize: bool
50+
:return: A [n x 4] array of Fourier coefficients.
51+
:rtype: :py:class:`numpy.ndarray`
52+
53+
"""
54+
dxy = np.diff(contour, axis=0)
55+
dt = np.sqrt((dxy ** 2).sum(axis=1))
56+
t = np.cumsum(dt)
57+
T = t[-1]
58+
59+
phi = np.concatenate([([0., ]), (2 * np.pi * t) / T])
60+
61+
coeffs = np.zeros((order, 4))
62+
for n in _range(1, order + 1):
63+
const = T / (2 * n * n * np.pi * np.pi)
64+
phi_n = phi * n
65+
d_cos_phi_n = np.cos(phi_n[1:]) - np.cos(phi_n[:-1])
66+
d_sin_phi_n = np.sin(phi_n[1:]) - np.sin(phi_n[:-1])
67+
a_n = const * np.sum((dxy[:, 0] / dt) * d_cos_phi_n)
68+
b_n = const * np.sum((dxy[:, 0] / dt) * d_sin_phi_n)
69+
c_n = const * np.sum((dxy[:, 1] / dt) * d_cos_phi_n)
70+
d_n = const * np.sum((dxy[:, 1] / dt) * d_sin_phi_n)
71+
coeffs[n - 1, :] = a_n, b_n, c_n, d_n
72+
73+
if normalize:
74+
coeffs = normalize_efd(coeffs)
75+
76+
return coeffs
77+
78+
79+
def normalize_efd(coeffs, size_invariant=True):
80+
"""Normalizes an array of Fourier coefficients.
81+
82+
See details in [1]_ or [2]_.
83+
84+
:param coeffs: A [n x 4] Fourier coefficient array.
85+
:type coeffs: :py:class:`numpy.ndarray`
86+
:return: The normalized [n x 4] Fourier coefficient array.
87+
:rtype: :py:class:`numpy.ndarray`
88+
89+
"""
90+
# Make the coefficients have a zero phase shift from
91+
# the first major axis. Theta_1 is that shift angle.
92+
theta_1 = 0.5 * np.arctan2(
93+
2 * ((coeffs[0, 0] * coeffs[0, 1]) + (coeffs[0, 2] * coeffs[0, 3])),
94+
((coeffs[0, 0] ** 2) - (coeffs[0, 1] ** 2) + (coeffs[0, 2] ** 2) - (coeffs[0, 3] ** 2)))
95+
# Rotate all coefficients by theta_1.
96+
for n in _range(1, coeffs.shape[0] + 1):
97+
coeffs[n - 1, :] = np.dot(
98+
np.array([[coeffs[n - 1, 0], coeffs[n - 1, 1]],
99+
[coeffs[n - 1, 2], coeffs[n - 1, 3]]]),
100+
np.array([[np.cos(n * theta_1), -np.sin(n * theta_1)],
101+
[np.sin(n * theta_1), np.cos(n * theta_1)]])).flatten()
102+
103+
# Make the coefficients rotation invariant by rotating so that
104+
# the semi-major axis is parallel to the x-axis.
105+
psi_1 = np.arctan2(coeffs[0, 2], coeffs[0, 0])
106+
psi_rotation_matrix = np.array([[np.cos(psi_1), np.sin(psi_1)],
107+
[-np.sin(psi_1), np.cos(psi_1)]])
108+
# Rotate all coefficients by -psi_1.
109+
for n in _range(1, coeffs.shape[0] + 1):
110+
coeffs[n - 1, :] = psi_rotation_matrix.dot(
111+
np.array([[coeffs[n - 1, 0], coeffs[n - 1, 1]],
112+
[coeffs[n - 1, 2], coeffs[n - 1, 3]]])).flatten()
113+
114+
if size_invariant:
115+
# Obtain size-invariance by normalizing.
116+
coeffs /= np.abs(coeffs[0, 0])
117+
118+
return coeffs
119+
120+
121+
def plot_efd(contour, coeffs, locus=(0., 0.), n=300):
122+
"""Plot a [2 x (n/2)] grid of successive truncations of the series.
123+
124+
:param coeffs: [n x 4] Fourier coefficient array.
125+
:type coeffs: :py:class:`numpy.ndarray`
126+
:param locus: The A_0 and C_0 elliptic locus in [1]_ and [2]_.
127+
:type locus: list, tuple or :py:class:`numpy.ndarray`
128+
:param n: Number of points to use for plotting of Fourier series.
129+
:type n: int
130+
131+
"""
132+
try:
133+
import matplotlib.pyplot as plt
134+
except ImportError:
135+
print("Cannot plot: matplotlib was not installed.")
136+
return
137+
138+
N = coeffs.shape[0]
139+
t = np.linspace(0, 1.0, n)
140+
xt = np.ones((n,)) * locus[0]
141+
yt = np.ones((n,)) * locus[1]
142+
143+
ax = plt.subplot2grid((3, N // 2), (0, 0), colspan=N//2)
144+
ax.imshow(contour, plt.cm.gray)
145+
for n in _range(coeffs.shape[0]):
146+
xt += (coeffs[n, 0] * np.cos(2 * (n + 1) * np.pi * t)) + \
147+
(coeffs[n, 1] * np.sin(2 * (n + 1) * np.pi * t))
148+
yt += (coeffs[n, 2] * np.cos(2 * (n + 1) * np.pi * t)) + \
149+
(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)))
151+
ax.set_title(str(n + 1))
152+
ax.plot(yt, -xt, 'r')
153+
154+
plt.show()
155+
156+

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
numpy>=1.10.2

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal=1

0 commit comments

Comments
 (0)