Skip to content
Commits on Source (8)
......@@ -8,6 +8,7 @@ __pycache__/
# Distribution / packaging
.Python
env/
venv/
bin/
build/
develop-eggs/
......@@ -31,7 +32,6 @@ htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
......@@ -42,6 +42,9 @@ coverage.xml
.project
.pydevproject
# PyCharm
.idea/
# Rope
.ropeproject
......
sudo: false
language: python
cache: pip
python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- 2.7
- 3.6
install:
- "pip install pytest pytest-cov nose"
- "pip install coveralls"
- "pip install -e ."
- pip install wheel --upgrade
- pip install .[test]
script:
- python -m pytest affine/tests --cov affine --cov-report term-missing
- pytest --cov affine --cov-report term-missing
after_success:
- pip install coveralls
- coveralls
deploy:
on:
......
CHANGES
=======
2.2.0 (2018-03-20)
------------------
- Addition of permutation matrix (#35).
2.1.0 (2017-07-12)
------------------
- Addition of new ``eccentricity`` and ``rotation_angle`` properties (#28).
......
exclude *.rst *.txt *.py
include CHANGES.txt AUTHORS.txt LICENSE.txt VERSION.txt README.rst setup.py setup.cfg
recursive-include affine/tests *.py
exclude MANIFEST.in
......@@ -47,7 +47,7 @@ import math
__all__ = ['Affine']
__author__ = "Sean Gillies"
__version__ = "2.1.0"
__version__ = "2.2.0"
EPSILON = 1e-5
......@@ -256,6 +256,19 @@ class Affine(
sa, ca, py - px * sa - py * ca,
0.0, 0.0, 1.0))
@classmethod
def permutation(cls, *scaling):
"""Create the permutation transform. For 2x2 matrices, there is only one permutation matrix that is not the identity.
:rtype: Affine
"""
return tuple.__new__(
cls,
(0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 1.0))
def __str__(self):
"""Concise string representation."""
return ("|% .2f,% .2f,% .2f|\n"
......@@ -557,6 +570,3 @@ def dumpsw(obj):
"""
center = obj * Affine.translation(0.5, 0.5)
return '\n'.join(repr(getattr(center, x)) for x in list('adbecf')) + '\n'
# vim: ai ts=4 sts=4 et sw=4 tw=78
......@@ -30,12 +30,15 @@
from __future__ import division
import os
import math
import unittest
from textwrap import dedent
from nose.tools import assert_equal, assert_almost_equal, raises
import textwrap
from affine import Affine, EPSILON, UndefinedRotationError
import pytest
import affine
from affine import Affine, EPSILON
def seq_almost_equal(t1, t2, error=0.00001):
......@@ -46,33 +49,33 @@ def seq_almost_equal(t1, t2, error=0.00001):
class PyAffineTestCase(unittest.TestCase):
@raises(TypeError)
def test_zero_args(self):
with pytest.raises(TypeError):
Affine()
@raises(TypeError)
def test_wrong_arg_type(self):
with pytest.raises(TypeError):
Affine(None)
@raises(TypeError)
def test_args_too_few(self):
with pytest.raises(TypeError):
Affine(1, 2)
@raises(TypeError)
def test_args_too_many(self):
with pytest.raises(TypeError):
Affine(*range(10))
@raises(TypeError)
def test_args_members_wrong_type(self):
with pytest.raises(TypeError):
Affine(0, 2, 3, None, None, "")
def test_len(self):
t = Affine(1, 2, 3, 4, 5, 6)
assert_equal(len(t), 9)
assert len(t) == 9
def test_slice_last_row(self):
t = Affine(1, 2, 3, 4, 5, 6)
assert_equal(t[-3:], (0, 0, 1))
assert t[-3:] == (0, 0, 1)
def test_members_are_floats(self):
t = Affine(1, 2, 3, 4, 5, 6)
......@@ -81,67 +84,76 @@ class PyAffineTestCase(unittest.TestCase):
def test_getitem(self):
t = Affine(1, 2, 3, 4, 5, 6)
assert_equal(t[0], 1)
assert_equal(t[1], 2)
assert_equal(t[2], 3)
assert_equal(t[3], 4)
assert_equal(t[4], 5)
assert_equal(t[5], 6)
assert_equal(t[6], 0)
assert_equal(t[7], 0)
assert_equal(t[8], 1)
assert_equal(t[-1], 1)
@raises(TypeError)
assert t[0] == 1
assert t[1] == 2
assert t[2] == 3
assert t[3] == 4
assert t[4] == 5
assert t[5] == 6
assert t[6] == 0
assert t[7] == 0
assert t[8] == 1
assert t[-1] == 1
def test_getitem_wrong_type(self):
t = Affine(1, 2, 3, 4, 5, 6)
with pytest.raises(TypeError):
t['foobar']
def test_str(self):
assert_equal(
str(Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)),
"| 1.11, 2.22, 3.33|\n|-4.44,-5.55, 6.67|\n| 0.00, 0.00, 1.00|")
assert \
str(Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)) \
== "| 1.11, 2.22, 3.33|\n|-4.44,-5.55, 6.67|\n| 0.00, 0.00, 1.00|"
def test_repr(self):
assert_equal(
repr(Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)),
("Affine(1.111, 2.222, 3.456,\n"
" 4.444, 5.5, 6.25)"))
assert \
repr(Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)) == \
os.linesep.join(["Affine(1.111, 2.222, 3.456,", " 4.444, 5.5, 6.25)"])
def test_identity_constructor(self):
ident = Affine.identity()
assert isinstance(ident, Affine)
assert_equal(
tuple(ident),
assert \
tuple(ident) == \
(1, 0, 0,
0, 1, 0,
0, 0, 1))
0, 0, 1)
assert ident.is_identity
def test_permutation_constructor(self):
perm = Affine.permutation()
assert isinstance(perm, Affine)
assert \
tuple(perm) == \
(0, 1, 0,
1, 0, 0,
0, 0, 1)
assert (perm*perm).is_identity
def test_translation_constructor(self):
trans = Affine.translation(2, -5)
assert isinstance(trans, Affine)
assert_equal(
tuple(trans),
assert \
tuple(trans) == \
(1, 0, 2,
0, 1, -5,
0, 0, 1))
0, 0, 1)
def test_scale_constructor(self):
scale = Affine.scale(5)
assert isinstance(scale, Affine)
assert_equal(
tuple(scale),
assert \
tuple(scale) == \
(5, 0, 0,
0, 5, 0,
0, 0, 1))
0, 0, 1)
scale = Affine.scale(-1, 2)
assert_equal(
tuple(scale),
assert \
tuple(scale) == \
(-1, 0, 0,
0, 2, 0,
0, 0, 1))
assert_equal(tuple(Affine.scale(1)), tuple(Affine.identity()))
0, 0, 1)
assert tuple(Affine.scale(1)) == tuple(Affine.identity())
def test_shear_constructor(self):
shear = Affine.shear(30)
......@@ -172,11 +184,11 @@ class PyAffineTestCase(unittest.TestCase):
assert isinstance(rot, Affine)
r = math.radians(60)
s, c = math.sin(r), math.cos(r)
assert_equal(
tuple(rot),
assert \
tuple(rot) == \
(c, -s, 0,
s, c, 0,
0, 0, 1))
0, 0, 1)
rot = Affine.rotation(337)
r = math.radians(337)
s, c = math.sin(r), math.cos(r)
......@@ -185,82 +197,80 @@ class PyAffineTestCase(unittest.TestCase):
(c, -s, 0,
s, c, 0,
0, 0, 1))
assert_equal(tuple(Affine.rotation(0)), tuple(Affine.identity()))
assert tuple(Affine.rotation(0)) == tuple(Affine.identity())
def test_rotation_constructor_quadrants(self):
assert_equal(
tuple(Affine.rotation(0)),
assert \
tuple(Affine.rotation(0)) == \
(1, 0, 0,
0, 1, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(90)),
0, 0, 1)
assert \
tuple(Affine.rotation(90)) == \
(0, -1, 0,
1, 0, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(180)),
0, 0, 1)
assert \
tuple(Affine.rotation(180)) == \
(-1, 0, 0,
0, -1, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(-180)),
0, 0, 1)
assert \
tuple(Affine.rotation(-180)) == \
(-1, 0, 0,
0, -1, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(270)),
0, 0, 1)
assert \
tuple(Affine.rotation(270)) == \
(0, 1, 0,
-1, 0, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(-90)),
0, 0, 1)
assert \
tuple(Affine.rotation(-90)) == \
(0, 1, 0,
-1, 0, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(360)),
0, 0, 1)
assert \
tuple(Affine.rotation(360)) == \
(1, 0, 0,
0, 1, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(450)),
0, 0, 1)
assert \
tuple(Affine.rotation(450)) == \
(0, -1, 0,
1, 0, 0,
0, 0, 1))
assert_equal(
tuple(Affine.rotation(-450)),
0, 0, 1)
assert \
tuple(Affine.rotation(-450)) == \
(0, 1, 0,
-1, 0, 0,
0, 0, 1))
0, 0, 1)
def test_rotation_constructor_with_pivot(self):
assert_equal(tuple(Affine.rotation(60)),
tuple(Affine.rotation(60, pivot=(0, 0))))
assert tuple(Affine.rotation(60)) == tuple(Affine.rotation(60, pivot=(0, 0)))
rot = Affine.rotation(27, pivot=(2, -4))
r = math.radians(27)
s, c = math.sin(r), math.cos(r)
assert_equal(
tuple(rot),
assert \
tuple(rot) == \
(c, -s, 2 - 2 * c - 4 * s,
s, c, -4 - 2 * s + 4 * c,
0, 0, 1))
assert_equal(tuple(Affine.rotation(0, (-3, 2))),
tuple(Affine.identity()))
0, 0, 1)
assert tuple(Affine.rotation(0, (-3, 2))) == tuple(Affine.identity())
@raises(TypeError)
def test_rotation_contructor_wrong_arg_types(self):
with pytest.raises(TypeError):
Affine.rotation(1, 1)
def test_determinant(self):
assert_equal(Affine.identity().determinant, 1)
assert_equal(Affine.scale(2).determinant, 4)
assert_equal(Affine.scale(0).determinant, 0)
assert_equal(Affine.scale(5, 1).determinant, 5)
assert_equal(Affine.scale(-1, 1).determinant, -1)
assert_equal(Affine.scale(-1, 0).determinant, 0)
assert_almost_equal(Affine.rotation(77).determinant, 1)
assert_almost_equal(Affine.translation(32, -47).determinant, 1)
assert Affine.identity().determinant == 1
assert Affine.scale(2).determinant == 4
assert Affine.scale(0).determinant == 0
assert Affine.scale(5, 1).determinant == 5
assert Affine.scale(-1, 1).determinant == -1
assert Affine.scale(-1, 0).determinant == 0
assert Affine.rotation(77).determinant == pytest.approx(1)
assert Affine.translation(32, -47).determinant == pytest.approx(1)
def test_is_rectilinear(self):
assert Affine.identity().is_rectilinear
......@@ -304,9 +314,9 @@ class PyAffineTestCase(unittest.TestCase):
assert isinstance(a, tuple)
assert isinstance(b, tuple)
assert isinstance(c, tuple)
assert_equal(a, (2, 5))
assert_equal(b, (3, 6))
assert_equal(c, (4, 7))
assert a == (2, 5)
assert b == (3, 6)
assert c == (4, 7)
def test_almost_equals(self):
EPSILON = 1e-5
......@@ -345,25 +355,25 @@ class PyAffineTestCase(unittest.TestCase):
assert not t1 == 1
assert t1 != 1
@raises(TypeError)
def test_gt(self):
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) > Affine(6, 5, 4, 3, 2, 1)
@raises(TypeError)
def test_lt(self):
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) < Affine(6, 5, 4, 3, 2, 1)
@raises(TypeError)
def test_add(self):
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) + Affine(6, 5, 4, 3, 2, 1)
@raises(TypeError)
def test_sub(self):
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) - Affine(6, 5, 4, 3, 2, 1)
def test_mul_by_identity(self):
t = Affine(1, 2, 3, 4, 5, 6)
assert_equal(tuple(t * Affine.identity()), tuple(t))
assert tuple(t * Affine.identity()) == tuple(t)
def test_mul_transform(self):
t = Affine.rotation(5) * Affine.rotation(29)
......@@ -376,13 +386,12 @@ class PyAffineTestCase(unittest.TestCase):
pts = [(4, 1), (-1, 0), (3, 2)]
r = Affine.scale(-2).itransform(pts)
assert r is None, r
assert_equal(pts, [(-8, -2), (2, 0), (-6, -4)])
assert pts == [(-8, -2), (2, 0), (-6, -4)]
@raises(TypeError)
def test_mul_wrong_type(self):
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) * None
@raises(TypeError)
def test_mul_sequence_wrong_member_types(self):
class NotPtSeq:
@classmethod
......@@ -392,6 +401,7 @@ class PyAffineTestCase(unittest.TestCase):
def __iter__(self):
yield 0
with pytest.raises(TypeError):
Affine(1, 2, 3, 4, 5, 6) * NotPtSeq()
def test_imul_transform(self):
......@@ -410,51 +420,47 @@ class PyAffineTestCase(unittest.TestCase):
seq_almost_equal(~t * t, Affine.identity())
def test_cant_invert_degenerate(self):
from affine import TransformNotInvertibleError
t = Affine.scale(0)
self.assertRaises(TransformNotInvertibleError, lambda: ~t)
with pytest.raises(affine.TransformNotInvertibleError):
~t
@raises(TypeError)
def test_bad_type_world(self):
from affine import loadsw
# wrong type, i.e don't use readlines()
loadsw(['1.0', '0.0', '0.0', '1.0', '0.0', '0.0'])
"""wrong type, i.e don't use readlines()"""
with pytest.raises(TypeError):
affine.loadsw(['1.0', '0.0', '0.0', '1.0', '0.0', '0.0'])
@raises(ValueError)
def test_bad_value_world(self):
from affine import loadsw
# wrong number of parameters
loadsw('1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0')
"""Wrong number of parameters."""
with pytest.raises(ValueError):
affine.loadsw('1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0')
def test_simple_world(self):
from affine import loadsw, dumpsw
s = '1.0\n0.0\n0.0\n-1.0\n100.5\n199.5\n'
a = loadsw(s)
self.assertEqual(
a,
a = affine.loadsw(s)
assert \
a == \
Affine(
1.0, 0.0, 100.0,
0.0, -1., 200.0))
self.assertEqual(dumpsw(a), s)
0.0, -1., 200.0)
assert affine.dumpsw(a) == s
def test_real_world(self):
from affine import loadsw, dumpsw
s = dedent('''\
s = textwrap.dedent('''\
39.9317755024
30.0907511581
30.0907511576
-39.9317755019
2658137.2266720217
5990821.7039887439''') # no EOL
a1 = loadsw(s)
self.assertTrue(a1.almost_equals(
a1 = affine.loadsw(s)
assert a1.almost_equals(
Affine(
39.931775502364644, 30.090751157602412, 2658102.2154086917,
30.090751157602412, -39.931775502364644, 5990826.624500916)))
a1out = dumpsw(a1)
self.assertTrue(isinstance(a1out, str))
a2 = loadsw(a1out)
self.assertTrue(a1.almost_equals(a2))
30.090751157602412, -39.931775502364644, 5990826.624500916))
a1out = affine.dumpsw(a1)
assert isinstance(a1out, str)
a2 = affine.loadsw(a1out)
assert a1.almost_equals(a2)
# We're using pytest for tests added after 1.0 and don't need unittest
......@@ -518,48 +524,42 @@ def test_roundtrip():
def test_eccentricity():
assert_equal(Affine.identity().eccentricity, 0.0)
assert_equal(Affine.scale(2).eccentricity, 0.0)
assert Affine.identity().eccentricity == 0.0
assert Affine.scale(2).eccentricity == 0.0
#assert_equal(Affine.scale(0).eccentricity, ?)
assert_almost_equal(Affine.scale(2, 1).eccentricity, math.sqrt(3) / 2)
assert_almost_equal(Affine.scale(2, 3).eccentricity, math.sqrt(5) / 3)
assert_equal(Affine.scale(1, 0).eccentricity, 1.0)
assert_almost_equal(Affine.rotation(77).eccentricity, 0.0)
assert_almost_equal(Affine.translation(32, -47).eccentricity, 0.0)
assert_almost_equal(Affine.scale(-1, 1).eccentricity, 0.0)
assert Affine.scale(2, 1).eccentricity == pytest.approx(math.sqrt(3) / 2)
assert Affine.scale(2, 3).eccentricity == pytest.approx(math.sqrt(5) / 3)
assert Affine.scale(1, 0).eccentricity == 1.0
assert Affine.rotation(77).eccentricity == pytest.approx(0.0)
assert Affine.translation(32, -47).eccentricity == pytest.approx(0.0)
assert Affine.scale(-1, 1).eccentricity == pytest.approx(0.0)
def test_eccentricity_complex():
assert_almost_equal(
(Affine.scale(2, 3) * Affine.rotation(77)).eccentricity,
math.sqrt(5) / 3
)
assert_almost_equal(
(Affine.rotation(77) * Affine.scale(2, 3)).eccentricity,
math.sqrt(5) / 3
)
assert_almost_equal(
(Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)).eccentricity,
math.sqrt(5) / 3
)
assert \
(Affine.scale(2, 3) * Affine.rotation(77)).eccentricity == \
pytest.approx(math.sqrt(5) / 3)
assert \
(Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \
pytest.approx(math.sqrt(5) / 3)
assert \
(Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \
pytest.approx(math.sqrt(5) / 3)
def test_rotation_angle():
assert_equal(Affine.identity().rotation_angle, 0.0)
assert_equal(Affine.scale(2).rotation_angle, 0.0)
assert_equal(Affine.scale(2, 1).rotation_angle, 0.0)
assert_almost_equal(Affine.translation(32, -47).rotation_angle, 0.0)
assert_almost_equal(Affine.rotation(30).rotation_angle, 30)
assert_almost_equal(Affine.rotation(-150).rotation_angle, -150)
assert Affine.identity().rotation_angle == 0.0
assert Affine.scale(2).rotation_angle == 0.0
assert Affine.scale(2, 1).rotation_angle == 0.0
assert Affine.translation(32, -47).rotation_angle == pytest.approx(0.0)
assert Affine.rotation(30).rotation_angle == pytest.approx(30)
assert Affine.rotation(-150).rotation_angle == pytest.approx(-150)
@raises(UndefinedRotationError)
def test_rotation_improper():
with pytest.raises(affine.UndefinedRotationError):
Affine.scale(-1, 1).rotation_angle
if __name__ == '__main__':
unittest.main()
# vim: ai ts=4 sts=4 et sw=4 tw=78
python-affine (2.1.0-2) UNRELEASED; urgency=medium
python-affine (2.2.0-1) unstable; urgency=medium
* New upstream release.
* Strip trailing whitespace from control file.
* Update copyright-format URL to use HTTPS.
* Add lintian override for vcs-deprecated-in-debian-infrastructure.
* Refresh patches.
* Bump Standards-Version to 4.1.3, no changes.
* Add pytest to build dependencies.
-- Bas Couwenberg <sebastic@debian.org> Tue, 21 Nov 2017 10:06:07 +0100
-- Bas Couwenberg <sebastic@debian.org> Wed, 21 Mar 2018 07:26:07 +0100
python-affine (2.1.0-1) unstable; urgency=medium
......
......@@ -8,11 +8,13 @@ Build-Depends: debhelper (>= 9),
dh-python,
python-all,
python3-all,
python-setuptools,
python3-setuptools,
python-nose,
python3-nose
Standards-Version: 4.0.0
python3-nose,
python-pytest,
python3-pytest,
python-setuptools,
python3-setuptools
Standards-Version: 4.1.3
Vcs-Browser: https://anonscm.debian.org/cgit/pkg-grass/python-affine.git
Vcs-Git: https://anonscm.debian.org/git/pkg-grass/python-affine.git
Homepage: https://github.com/sgillies/affine
......
......@@ -7,9 +7,13 @@ Subject: Add test configuration
1 file changed, 8 insertions(+)
create mode 100644 setup.cfg
--- /dev/null
--- a/setup.cfg
+++ b/setup.cfg
@@ -0,0 +1,8 @@
@@ -3,3 +3,12 @@ universal: 1
[tool:pytest]
testpaths: affine/tests
+
+[nosetests]
+tests=affine/tests
+nocapture=True
......
# Package not officially migrated to Salsa yet.
python-affine source: vcs-deprecated-in-debian-infrastructure * https://anonscm.debian.org/*
[bdist_wheel]
universal: 1
[tool:pytest]
testpaths: affine/tests
from codecs import open as codecs_open
from setuptools import setup, find_packages
import codecs
import os
from setuptools import find_packages, setup
# Parse the version from the affine module.
with open('affine/__init__.py') as f:
with codecs.open(os.path.join('affine', '__init__.py')) as f:
for line in f:
if "__version__" in line:
version = line.split("=")[1].strip()
version = version.strip('"').strip("'")
break
with codecs_open('README.rst', encoding='utf-8') as f:
with codecs.open('README.rst', encoding='utf-8') as f:
readme = f.read()
setup(name='affine',
version=version,
description="Matrices describing affine transformation of the plane",
description="Matrices describing affine transformation of the plane.",
long_description=readme,
classifiers=[],
keywords='affine transformation matrix',
......@@ -26,6 +28,6 @@ setup(name='affine',
license='BSD',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
extras_require = {'test': ['pytest']}
zip_safe=True,
extras_require={'test': ['pytest>=3.0', 'pytest-cov']}
)
[tox]
envlist =
py27,py34,py35
py27,py36
[testenv]
usedevelop = true
deps =
nose
pytest-cov
responses
commands =
......