Skip to content

Commit 0182446

Browse files
committed
Initial checkpoint of affine3d transformation pkg. (WIP)
relies on numpy for underlying rep, and transformations.py (here) for geometry.
1 parent 6e349ac commit 0182446

9 files changed

+2167
-1
lines changed

BuildRaspi.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,9 @@ ff02::2 ip6-allrouters
450450
451451
``` bash
452452
sudo python3 -m pip install picamera
453-
sudo python3 -m pip install pynetworktables
453+
# following may be present in frcvision, but shouldn't hurt
454+
sudo python3 -m pip install numpy
455+
sudo python3 -m pip install pynetworktables
454456
```
455457
456458
### install node and extensions

mathutils/__init__.py

Whitespace-only changes.

mathutils/affine2d.py

Whitespace-only changes.

mathutils/affine3d.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import numpy as np
2+
import math
3+
import transformations as xform
4+
from quaternion import *
5+
from affine2d import *
6+
7+
class Affine3d(object):
8+
"""We represent a 3D affine transformation as a 4x4 matrix."
9+
10+
Lots of places to learn about affine transformations like this:
11+
12+
http://graphics.cs.cmu.edu/nsp/course/15-462/Spring04/slides/04-transform.pdf
13+
14+
Examples
15+
>>> a = Affine3d()
16+
>>> b = Affine3d.fromRotation(30, [1, 0, 0])
17+
>>> c = Affine3d.fromTranslation(.5, 1.5, 2.5)
18+
>>> d = Affine3d.fromQuaternion(1,2,3,4)
19+
>>> e = Affine3d.fromQuaternion([1,2,3,4])
20+
>>> e.equals(d)
21+
True
22+
>>> f = Affine3d.fromQuaternion(Quaternion())
23+
>>> a.equals(f)
24+
True
25+
>>> f = Affine3d.concatenate(b, c, d)
26+
>>> pts = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
27+
>>> npts = f.transformPoints(pts)
28+
>>> len(npts) == len(pts)
29+
True
30+
31+
"""
32+
33+
@staticmethod
34+
def fromIdentity():
35+
return Affine3d(xform.identity_matrix())
36+
37+
@staticmethod
38+
def fromTranslation(x, y, z):
39+
"return Affine3d representing translation of x, y and z"
40+
return Affine3d(xform.translation_matrix([x, y, z]))
41+
42+
@staticmethod
43+
def fromRotation(angle, dir):
44+
"return Affine3d representing rotation of angle (in degrees) around dir"
45+
return Affine3d(xform.rotation_matrix(math.radians(angle), dir))
46+
47+
@staticmethod
48+
def fromQuaternion(*q):
49+
"""return Affine3d representing rotation via quaternion.
50+
See Quaternion for a variety of methods to describe rotations.
51+
"""
52+
if(len(q) == 1):
53+
q = q[0]
54+
if isinstance(q, Quaternion):
55+
q = q.q # member of Quaternion
56+
return Affine3d(xform.quaternion_matrix(q))
57+
58+
@staticmethod
59+
def concatenate(*objs):
60+
"return Affine3d representing concatenation of Affine3ds"
61+
mats = [x.matrix for x in objs]
62+
return Affine3d(xform.concatenate_matrices(*mats))
63+
64+
# XXX: add more factories (scale, reflection, shear)
65+
66+
def __init__(self, initmat=None):
67+
if initmat is None:
68+
self.matrix = xform.identity_matrix()
69+
else:
70+
self.matrix = initmat
71+
72+
def __repr__(self):
73+
"for interactive prompt inspection"
74+
return self.matrix.__repr__()
75+
76+
def __str__(self):
77+
"for print"
78+
return self.matrix.__str__()
79+
80+
def equals(self, other):
81+
return np.allclose(self.matrix, other.matrix)
82+
83+
def transformPoints(self, pts):
84+
"""return an array of points transformed by my transformation matrix.
85+
pts is assumed to be an array of triples.
86+
"""
87+
result = np.array(pts, dtype=np.float32)
88+
i = 0
89+
for p3 in pts:
90+
# p3 to p4 back to p3 (so dot works)
91+
p4 = p3.tolist() + [1]
92+
npt = self.matrix.dot(p4)[:3]
93+
result[i] = npt # npt type s np.array, should we issue tolist()?
94+
i+=1
95+
return result
96+
97+
def asInverse(self):
98+
return Affine3d(xform.inverse_matrix(self.matrix))
99+
100+
def asAffine2d(self):
101+
"""return a 2d representation of
102+
"""
103+
104+
if __name__ == "__main__":
105+
import doctest
106+
doctest.testmod()

mathutils/quaternion.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import transformations as xform
2+
import numpy as np
3+
import math
4+
5+
class Quaternion:
6+
"""Robust representation of 3D rotation
7+
8+
Quaternions w+ix+jy+kz are represented as [w, x, y, z].
9+
10+
Examples:
11+
>>> a = Quaternion()
12+
>>> np.allclose(np.identity(4), a.asMatrix())
13+
True
14+
>>> b = Quaternion.fromAxisAngle(45, [0, 0, 1])
15+
>>> m = b.asMatrix()
16+
>>> q1 = Quaternion.fromMatrix(np.identity(4))
17+
>>> a.equals(q1)
18+
True
19+
>>> q2 = Quaternion.fromAngles(45, 0, 0)
20+
>>> q3 = Quaternion.fromAngles(0, 45, 0)
21+
>>> q4 = Quaternion.fromAngles(0, 0, 45)
22+
"""
23+
24+
@staticmethod
25+
def fromMatrix(matrix, isprecise=False):
26+
return Quaternion(xform.quaternion_from_matrix(matrix, isprecise))
27+
28+
@staticmethod
29+
def fromAxisAngle(angle, axis):
30+
""" return quaternion for rotation (degrees) about axis
31+
"""
32+
return Quaternion(xform.quaternion_about_axis(math.radians(angle), axis))
33+
34+
@staticmethod
35+
def fromAngles(ai, aj, ak, axes='sxyz'):
36+
m = xform.euler_matrix(math.radians(ai),
37+
math.radians(aj),
38+
math.radians(ak),
39+
axes)
40+
q = xform.quaternion_from_matrix(m)
41+
return Quaternion(q)
42+
43+
def __init__(self, initq=None):
44+
if initq is None:
45+
self.q = np.array([1,0,0,0], dtype=np.float32) # produces identity matrix
46+
else:
47+
self.q = initq
48+
49+
def __repr__(self):
50+
"for interactive prompt inspection"
51+
return self.q.__repr__()
52+
53+
def __str__(self):
54+
"for print"
55+
return self.q.__str__()
56+
57+
def asMatrix(self):
58+
""" return a numpy array, to convert to Affine3d, use Affine3d.fromQuaternion
59+
"""
60+
return xform.quaternion_matrix(self.q)
61+
62+
def equals(self, other):
63+
return xform.is_same_quaternion(self.q, other.q)
64+
65+
def multiply(self, other):
66+
"""
67+
>>> q1 = Quaternion(4, 1, -2, 3)
68+
>>> q2 = Quaternion(8, -5, 6, 7)
69+
>>> q3 = q1.multiply(q2)
70+
>>> q3.equals(Quaternion(28, -44, -14, 48))
71+
True
72+
"""
73+
return xform.quaternion_multiply(self.q, other.q)
74+
75+
if __name__ == "__main__":
76+
import doctest
77+
doctest.testmod()

0 commit comments

Comments
 (0)