pfc-geometry 0.1.2__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/PKG-INFO +4 -1
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/point.py +8 -20
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/quaternion.py +44 -21
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/pfc_geometry.egg-info/PKG-INFO +4 -1
- pfc_geometry-0.1.4/pfc_geometry.egg-info/top_level.txt +1 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/setup.cfg +1 -1
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_point.py +13 -9
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_quaternion.py +2 -1
- pfc_geometry-0.1.2/pfc_geometry.egg-info/top_level.txt +0 -1
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/LICENSE +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/README.md +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/__init__.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/base.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/coordinate_frame.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/gps.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/mass.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/testing.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/geometry/transformation.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/pfc_geometry.egg-info/SOURCES.txt +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/pfc_geometry.egg-info/dependency_links.txt +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/pfc_geometry.egg-info/requires.txt +1 -1
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/setup.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_base.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_coord.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_gps.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_mass.py +0 -0
- {pfc_geometry-0.1.2 → pfc_geometry-0.1.4}/tests/test_transform.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pfc_geometry
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A package for handling 3D geometry with a nice interface
|
|
5
5
|
Home-page: https://github.com/PyFlightCoach/geometry
|
|
6
6
|
Author: Thomas David
|
|
7
7
|
Author-email: thomasdavid0@gmail.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
License-File: LICENSE
|
|
10
12
|
|
|
@@ -22,3 +24,4 @@ Many convenience methods and constructors are available. Documentation is limite
|
|
|
22
24
|
now available on pypi:
|
|
23
25
|
|
|
24
26
|
pip install pfc-geometry
|
|
27
|
+
|
|
@@ -9,6 +9,7 @@ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
9
9
|
You should have received a copy of the GNU General Public License along with
|
|
10
10
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
11
11
|
"""
|
|
12
|
+
from __future__ import annotations
|
|
12
13
|
from .base import Base
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
@@ -23,15 +24,13 @@ class Point(Base):
|
|
|
23
24
|
"arcsin","arccos","arctan",
|
|
24
25
|
]
|
|
25
26
|
|
|
26
|
-
def scale(self, value):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
res[
|
|
30
|
-
|
|
27
|
+
def scale(self, value) -> Point:
|
|
28
|
+
with np.errstate(divide="ignore"):
|
|
29
|
+
res = value/abs(self)
|
|
30
|
+
res[res==np.inf] = 0
|
|
31
|
+
return self * res
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def unit(self):
|
|
33
|
+
def unit(self) -> Point:
|
|
35
34
|
return self.scale(1)
|
|
36
35
|
|
|
37
36
|
def remove_outliers(self, nstds = 2):
|
|
@@ -141,31 +140,22 @@ def cross(a, b) -> Point:
|
|
|
141
140
|
|
|
142
141
|
@ppmeth
|
|
143
142
|
def cos_angle_between(a: Point, b: Point) -> np.ndarray:
|
|
144
|
-
if a == 0 or b == 0:
|
|
145
|
-
raise ValueError("cannot measure the angle to a zero length vector")
|
|
146
143
|
return a.unit().dot(b.unit())
|
|
147
144
|
|
|
148
|
-
|
|
149
145
|
@ppmeth
|
|
150
146
|
def angle_between(a: Point, b: Point) -> np.ndarray:
|
|
151
147
|
return np.arccos(a.cos_angle_between(b))
|
|
152
148
|
|
|
153
149
|
@ppmeth
|
|
154
150
|
def scalar_projection(a: Point, b: Point) -> Point:
|
|
155
|
-
if a==0 or b==0:
|
|
156
|
-
return 0
|
|
157
151
|
return a.cos_angle_between(b) * abs(a)
|
|
158
152
|
|
|
159
153
|
@ppmeth
|
|
160
154
|
def vector_projection(a: Point, b: Point) -> Point:
|
|
161
|
-
if abs(a) == 0:
|
|
162
|
-
return Point.zeros()
|
|
163
155
|
return b.scale(a.scalar_projection(b))
|
|
164
156
|
|
|
165
157
|
@ppmeth
|
|
166
158
|
def is_parallel(a: Point, b: Point, tolerance=1e-6):
|
|
167
|
-
if a.unit() == b.unit():
|
|
168
|
-
return True
|
|
169
159
|
return abs(a.cos_angle_between(b) - 1) < tolerance
|
|
170
160
|
|
|
171
161
|
@ppmeth
|
|
@@ -182,9 +172,7 @@ def angle_between(a: Point, b: Point) -> float:
|
|
|
182
172
|
return np.arccos(cos_angle_between(a, b))
|
|
183
173
|
|
|
184
174
|
def arbitrary_perpendicular(v: Point) -> Point:
|
|
185
|
-
|
|
186
|
-
return Point(0, 1, 0)
|
|
187
|
-
return Point(-v.y, v.x, 0).unit
|
|
175
|
+
return Point(-v.y, v.x, 0).unit()
|
|
188
176
|
|
|
189
177
|
def vector_norm(point: Point):
|
|
190
178
|
return abs(point)
|
|
@@ -9,7 +9,8 @@ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
9
9
|
You should have received a copy of the GNU General Public License along with
|
|
10
10
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
11
11
|
"""
|
|
12
|
-
from
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
from .point import Point, P0
|
|
13
14
|
from .base import Base
|
|
14
15
|
from geometry import PZ
|
|
15
16
|
from typing import Union, Tuple
|
|
@@ -23,7 +24,7 @@ class Quaternion(Base):
|
|
|
23
24
|
cols=["w", "x", "y", "z"]
|
|
24
25
|
|
|
25
26
|
@staticmethod
|
|
26
|
-
def zero(count=1):
|
|
27
|
+
def zero(count=1) -> Quaternion:
|
|
27
28
|
return Quaternion(np.tile([1,0,0,0], (count,1)))
|
|
28
29
|
|
|
29
30
|
@property
|
|
@@ -31,13 +32,13 @@ class Quaternion(Base):
|
|
|
31
32
|
return np.array([self.x, self.y, self.z, self.w]).T
|
|
32
33
|
|
|
33
34
|
@property
|
|
34
|
-
def axis(self):
|
|
35
|
+
def axis(self) -> Point:
|
|
35
36
|
return Point(self.data[:,1:])
|
|
36
37
|
|
|
37
|
-
def norm(self):
|
|
38
|
+
def norm(self) -> Quaternion:
|
|
38
39
|
return self / abs(self)
|
|
39
40
|
|
|
40
|
-
def conjugate(self):
|
|
41
|
+
def conjugate(self) -> Quaternion:
|
|
41
42
|
return Quaternion(self.w, -self.x, -self.y, -self.z)
|
|
42
43
|
|
|
43
44
|
def inverse(self):
|
|
@@ -53,7 +54,7 @@ class Quaternion(Base):
|
|
|
53
54
|
elif isinstance(other, Number):
|
|
54
55
|
return Quaternion(self.data * other)
|
|
55
56
|
elif isinstance(other, np.ndarray):
|
|
56
|
-
return
|
|
57
|
+
return Quaternion(self.data * self._dprep(other))
|
|
57
58
|
|
|
58
59
|
raise TypeError(f"cant multiply a quaternion by a {other.__class__.__name__}")
|
|
59
60
|
|
|
@@ -61,7 +62,7 @@ class Quaternion(Base):
|
|
|
61
62
|
#either it should have been picked up by the left hand object or it should commute
|
|
62
63
|
return self * other
|
|
63
64
|
|
|
64
|
-
def transform_point(self, point: Point):
|
|
65
|
+
def transform_point(self, point: Point) -> Point:
|
|
65
66
|
'''Transform a point by the rotation described by self'''
|
|
66
67
|
a, b = Base.length_check(self, point)
|
|
67
68
|
|
|
@@ -70,7 +71,7 @@ class Quaternion(Base):
|
|
|
70
71
|
return (a * Quaternion(qdata) * a.inverse()).axis
|
|
71
72
|
|
|
72
73
|
@staticmethod
|
|
73
|
-
def from_euler(eul: Point):
|
|
74
|
+
def from_euler(eul: Point) -> Quaternion:
|
|
74
75
|
eul = Point.type_check(eul)
|
|
75
76
|
# xyz-fixed Euler angle convention: matches ArduPilot AP_Math/Quaternion::from_euler
|
|
76
77
|
half = eul * 0.5
|
|
@@ -86,7 +87,7 @@ class Quaternion(Base):
|
|
|
86
87
|
]).T
|
|
87
88
|
)
|
|
88
89
|
|
|
89
|
-
def to_euler(self):
|
|
90
|
+
def to_euler(self) -> Point:
|
|
90
91
|
|
|
91
92
|
# roll (x-axis rotation)
|
|
92
93
|
sinr_cosp = 2 * (self.w * self.x + self.y * self.z)
|
|
@@ -111,7 +112,7 @@ class Quaternion(Base):
|
|
|
111
112
|
return Point(roll, pitch, yaw)
|
|
112
113
|
|
|
113
114
|
@staticmethod
|
|
114
|
-
def from_axis_angle(axangles: Point):
|
|
115
|
+
def from_axis_angle(axangles: Point) -> Quaternion:
|
|
115
116
|
small = 1e-6
|
|
116
117
|
angles = abs(axangles)
|
|
117
118
|
|
|
@@ -133,27 +134,49 @@ class Quaternion(Base):
|
|
|
133
134
|
return Quaternion(qdat)
|
|
134
135
|
|
|
135
136
|
def to_axis_angle(self):
|
|
137
|
+
a = (-self)._to_axis_angle()
|
|
138
|
+
b = self._to_axis_angle()
|
|
139
|
+
|
|
140
|
+
res = a.data
|
|
141
|
+
res[abs(a)>abs(b), :] = -b.data[abs(a)>abs(b), :]
|
|
142
|
+
|
|
143
|
+
return Point(res)
|
|
144
|
+
|
|
145
|
+
def _to_axis_angle(self) -> Point:
|
|
136
146
|
"""to a point of axis angles. must be normalized first."""
|
|
137
147
|
angle = 2 * np.arccos(self.w)
|
|
138
148
|
s = np.sqrt(1 - self.w**2)
|
|
139
149
|
np.array(s)[np.array(s) < 1e-6] = 1.0
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
with np.errstate(divide="ignore"):
|
|
151
|
+
sangle = angle / s
|
|
152
|
+
sangle[sangle==np.inf] = 0
|
|
153
|
+
res = self.axis * sangle
|
|
154
|
+
return res
|
|
142
155
|
|
|
143
156
|
@staticmethod
|
|
144
|
-
def axis_rates(q, qdot) -> Point:
|
|
157
|
+
def axis_rates(q: Quaternion, qdot: Quaternion) -> Point:
|
|
145
158
|
wdash = qdot * q.conjugate()
|
|
146
159
|
return wdash.norm().to_axis_angle()
|
|
147
160
|
|
|
148
161
|
@staticmethod
|
|
149
|
-
def
|
|
162
|
+
def _axis_rates(q: Quaternion, qdot: Quaternion) -> Point:
|
|
163
|
+
wdash = qdot * q.conjugate()
|
|
164
|
+
return wdash.norm()._to_axis_angle()
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def body_axis_rates(q: Quaternion, qdot: Quaternion) -> Point:
|
|
150
168
|
wdash = q.conjugate() * qdot
|
|
151
169
|
return wdash.norm().to_axis_angle()
|
|
152
170
|
|
|
153
|
-
|
|
171
|
+
@staticmethod
|
|
172
|
+
def _body_axis_rates(q: Quaternion, qdot: Quaternion) -> Point:
|
|
173
|
+
wdash = q.conjugate() * qdot
|
|
174
|
+
return wdash.norm()._to_axis_angle()
|
|
175
|
+
|
|
176
|
+
def rotate(self, rate: Point) -> Quaternion:
|
|
154
177
|
return (Quaternion.from_axis_angle(rate) * self).norm()
|
|
155
178
|
|
|
156
|
-
def body_rotate(self, rate: Point):
|
|
179
|
+
def body_rotate(self, rate: Point) -> Quaternion:
|
|
157
180
|
return (self * Quaternion.from_axis_angle(rate)).norm()
|
|
158
181
|
|
|
159
182
|
def diff(self, dt: np.array) -> Point:
|
|
@@ -161,7 +184,7 @@ class Quaternion(Base):
|
|
|
161
184
|
assert len(dt) == len(self)
|
|
162
185
|
dt = dt * len(dt) / (len(dt) - 1)
|
|
163
186
|
|
|
164
|
-
ps = Quaternion.
|
|
187
|
+
ps = Quaternion._axis_rates(
|
|
165
188
|
Quaternion(self.data[:-1, :]),
|
|
166
189
|
Quaternion(self.data[1:, :])
|
|
167
190
|
) / dt[:-1]
|
|
@@ -172,7 +195,7 @@ class Quaternion(Base):
|
|
|
172
195
|
assert len(dt) == len(self)
|
|
173
196
|
dt = dt * len(dt) / (len(dt) - 1)
|
|
174
197
|
|
|
175
|
-
ps = Quaternion.
|
|
198
|
+
ps = Quaternion._body_axis_rates(
|
|
176
199
|
Quaternion(self.data[:-1, :]),
|
|
177
200
|
Quaternion(self.data[1:, :])
|
|
178
201
|
) / dt[:-1]
|
|
@@ -193,7 +216,7 @@ class Quaternion(Base):
|
|
|
193
216
|
]).T
|
|
194
217
|
|
|
195
218
|
@staticmethod
|
|
196
|
-
def from_rotation_matrix(matrix: np.ndarray):
|
|
219
|
+
def from_rotation_matrix(matrix: np.ndarray) -> Quaternion:
|
|
197
220
|
# This method assumes row-vector and postmultiplication of that vector
|
|
198
221
|
m = matrix.conj().transpose()
|
|
199
222
|
if m[2, 2] < 0:
|
|
@@ -220,12 +243,12 @@ class Quaternion(Base):
|
|
|
220
243
|
def __str__(self):
|
|
221
244
|
return "W:{w:.2f}\nX:{x:.2f}\nY:{y:.2f}\nZ:{z:.2f}".format(w=self.w, x=self.x, y=self.y, z=self.z)
|
|
222
245
|
|
|
223
|
-
def closest_principal(self):
|
|
246
|
+
def closest_principal(self) -> Quaternion:
|
|
224
247
|
eul = self.to_euler()
|
|
225
248
|
rads = eul * (2 / np.pi)
|
|
226
249
|
return Quaternion.from_euler(rads.round(0) * np.pi/2)
|
|
227
250
|
|
|
228
|
-
def is_inverted(self):
|
|
251
|
+
def is_inverted(self) -> bool:
|
|
229
252
|
# does the rotation reverse the Z axis?
|
|
230
253
|
return np.sign(self.transform_point(PZ()).z) > 0
|
|
231
254
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pfc-geometry
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A package for handling 3D geometry with a nice interface
|
|
5
5
|
Home-page: https://github.com/PyFlightCoach/geometry
|
|
6
6
|
Author: Thomas David
|
|
7
7
|
Author-email: thomasdavid0@gmail.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
License-File: LICENSE
|
|
10
12
|
|
|
@@ -22,3 +24,4 @@ Many convenience methods and constructors are available. Documentation is limite
|
|
|
22
24
|
now available on pypi:
|
|
23
25
|
|
|
24
26
|
pip install pfc-geometry
|
|
27
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -5,7 +5,7 @@ author_email = thomasdavid0@gmail.com
|
|
|
5
5
|
description = A package for handling 3D geometry with a nice interface
|
|
6
6
|
long_description = file: README.md
|
|
7
7
|
long_description_content_type = text/markdown
|
|
8
|
-
version = 0.1.
|
|
8
|
+
version = 0.1.4
|
|
9
9
|
url = https://github.com/PyFlightCoach/geometry
|
|
10
10
|
|
|
11
11
|
[options]
|
|
@@ -35,15 +35,17 @@ def test_scale():
|
|
|
35
35
|
assert Point(1,0,0).scale(5) == Point(5,0,0)
|
|
36
36
|
assert Point(1,0,0).scale(5).data.shape == Point(5,0,0).data.shape
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
with np.errstate(divide="raise"):
|
|
39
|
+
assert Point.concatenate([PX(1,10), P0(10)]).scale(5) == Point.concatenate([PX(5,10), P0(10)])
|
|
40
|
+
|
|
39
41
|
|
|
40
42
|
def test_unit():
|
|
41
43
|
assert Point(5,0,0).unit() == Point(1,0,0)
|
|
42
44
|
assert Point(5,0,0).unit().data.shape == (1,3)
|
|
43
45
|
|
|
44
46
|
def test_angle_between():
|
|
45
|
-
with raises(ValueError):
|
|
46
|
-
|
|
47
|
+
#with raises(ValueError):
|
|
48
|
+
Point(1,2,3).cos_angle_between(Point.zeros()) == P0()
|
|
47
49
|
|
|
48
50
|
assert PX().cos_angle_between(PY()) == 0
|
|
49
51
|
|
|
@@ -59,12 +61,6 @@ def test_is_parallel():
|
|
|
59
61
|
assert not Point(1,0, 0).is_parallel(Point(0,1, 0))
|
|
60
62
|
|
|
61
63
|
|
|
62
|
-
@mark.skip("this is going to be picked up later with coord and Quaternion stuff")
|
|
63
|
-
def test_rotate():
|
|
64
|
-
assert PX().rotate(np.identity(3)) == PX()
|
|
65
|
-
|
|
66
|
-
assert PX().rotate(np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]])) == Point(0, 1, 0)
|
|
67
|
-
|
|
68
64
|
def test_scalar_projection():
|
|
69
65
|
assert Point(1, 1, 0).scalar_projection(PX()) == 1
|
|
70
66
|
assert P0().scalar_projection(Point(1, 1, 0)) == 0
|
|
@@ -90,3 +86,11 @@ def test_X():
|
|
|
90
86
|
|
|
91
87
|
def test_to_rotation_matrix():
|
|
92
88
|
np.testing.assert_array_equal(P0().to_rotation_matrix()[0],np.identity(3))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_vector_projection():
|
|
92
|
+
res = Point.vector_projection(PX(1,20), PY(1))
|
|
93
|
+
assert res == P0()
|
|
94
|
+
|
|
95
|
+
res = Point.vector_projection(PZ(20), PZ(20,20))
|
|
96
|
+
assert res == PZ(20)
|
|
@@ -138,7 +138,8 @@ def test_body_rotate_zero():
|
|
|
138
138
|
qinit = Quaternion.from_euler(Point(0, 0, 0))
|
|
139
139
|
qdot = qinit.body_rotate(Point(0, 0, 0))
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
assert qinit == qdot
|
|
142
|
+
# np.testing.assert_array_equal(list(qinit), list(qdot))
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
def test_to_from_axis_angle():
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
geometry
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|