-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix quaternion comparison #8
Changes from 8 commits
2c7c7a6
1d61426
2d2251c
cccc1d0
0bc788a
4b960ff
75f583c
0af64dd
38495b6
a680ae2
17a258f
a091777
be0a40d
ca5bd83
a58c4d0
16d0b12
27e10e2
c2c5bb8
1b5577c
25c3eee
6d260cc
ff0e95e
15329d0
932d7e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Satellogic SA Copyright 2017. All our code is GPLv3 licensed. | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ class QuaternionError(Exception): | |
class Quaternion(object): | ||
''' A class that holds quaternions. It actually holds Q^op, as | ||
this is the way Schaub-Jenkins work with them. | ||
Note: Quaternion is equal up to scale, but it's not stored normalized. | ||
''' | ||
tolerance = 1e-8 | ||
|
||
|
@@ -42,7 +43,7 @@ def __mul__(self, p): | |
[self.qj, -self.qk, self.qr, self.qi], # noqa | ||
[self.qk, self.qj, -self.qi, self.qr] # noqa | ||
]) | ||
result = mat.dot(np.array(p.coordinates)) | ||
result = mat.dot(p.normalized().coordinates) | ||
return Quaternion(*result) | ||
elif isinstance(p, Iterable): | ||
return self.matrix.dot(p) | ||
|
@@ -84,8 +85,8 @@ def is_equal(self, other): | |
compares as quaternions up to tolerance. | ||
Note: tolerance in coords, not in quaternions metrics. | ||
""" | ||
q1 = np.asarray(self.normalized().coordinates) | ||
q2 = np.asarray(other.normalized().coordinates) | ||
q1 = self.normalized().coordinates | ||
q2 = other.normalized().coordinates | ||
dist = min(np.linalg.norm(q1 - q2), np.linalg.norm(q1 - (-q2))) | ||
return dist < self.tolerance | ||
|
||
|
@@ -95,40 +96,58 @@ def __eq__(self, other): | |
def norm(self): | ||
return np.sqrt(self._squarenorm()) | ||
|
||
def exp(self): | ||
exp_norm = np.exp(self.qr) | ||
@classmethod | ||
def exp(cls, arr): | ||
""" | ||
exponent quaternion | ||
:param arr: list of 4 items or Quaternion | ||
:return: Quaternion | ||
""" | ||
if isinstance(arr, Quaternion): | ||
real, imag = arr.coordinates[0], arr.coordinates[1:] | ||
else: | ||
real, imag = arr[0], np.asarray(arr[1:]) | ||
|
||
exp_norm = np.exp(real) | ||
|
||
imag = np.array([self.qi, self.qj, self.qk]) | ||
imag_norm = np.linalg.norm(imag) | ||
if imag_norm == 0: | ||
return Quaternion(exp_norm, 0, 0, 0) | ||
|
||
imag_renorm = np.sin(imag_norm) * imag / imag_norm | ||
q = Quaternion(np.cos(imag_norm), *imag_renorm) | ||
j, k, l = np.sin(imag_norm) * imag / imag_norm | ||
q = Quaternion(np.cos(imag_norm), j, k, l) | ||
|
||
return exp_norm * q | ||
|
||
def log(self): | ||
""" | ||
logarithm of quaternion | ||
:return: np.array with 4 items | ||
""" | ||
norm = self.norm() | ||
imag = np.array((self.qi, self.qj, self.qk)) / norm | ||
imag_norm = np.linalg.norm(imag) | ||
if imag_norm == 0: | ||
i_part = 0 if self.qr > 0 else np.pi | ||
return Quaternion(np.log(norm), i_part, 0, 0) | ||
imag = imag / imag_norm * np.arctan2(imag_norm, self.qr / norm) | ||
return Quaternion(np.log(norm), *imag) | ||
return np.array([np.log(norm), i_part, 0, 0]) | ||
|
||
j, k, l = imag / imag_norm * np.arctan2(imag_norm, self.qr / norm) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
return np.array([np.log(norm), j, k, l]) | ||
|
||
def distance(self, other): | ||
'''Returns the distance in radians between two unitary quaternions''' | ||
quot = (self * other.conjugate()).positive_representant | ||
return 2 * quot.log().norm() | ||
quot = (self * other.conjugate()).normalized().positive_representant | ||
return np.linalg.norm(np.multiply(2, quot.log())) | ||
|
||
def is_unitary(self): | ||
return abs(self._squarenorm() - 1) < self.tolerance | ||
|
||
@property | ||
def coordinates(self): | ||
return self.qr, self.qi, self.qj, self.qk | ||
""" | ||
:return: np.array of length 4 | ||
""" | ||
return np.array([self.qr, self.qi, self.qj, self.qk]) | ||
|
||
@property | ||
def positive_representant(self): | ||
|
@@ -186,7 +205,7 @@ def matrix(self): | |
|
||
@property | ||
def rotation_vector(self): | ||
return (2 * self.log()).coordinates[1:] | ||
return (2 * self.log())[1:] | ||
|
||
@property | ||
def ra_dec_roll(self): | ||
|
@@ -242,8 +261,8 @@ def from_rotation_vector(xyz): | |
This corresponds to the exponential of the quaternion with | ||
real part 0 and imaginary part 1/2 * xyz. | ||
''' | ||
xyz_half = .5 * np.array(xyz) | ||
return Quaternion(0, *xyz_half).exp() | ||
a, b, c = .5 * np.array(xyz) | ||
return Quaternion.exp([0, a, b, c ]) | ||
|
||
@staticmethod | ||
def _first_eigenvector(matrix): | ||
|
@@ -331,12 +350,9 @@ def from_ra_dec_roll(ra, dec, roll): | |
dec stands for declination, and usually lies in [-90, 90] | ||
roll stands for rotation/rolling, and usually lies in [0, 360] | ||
''' | ||
raq = Quaternion.exp(Quaternion(0, 0, 0, -np.deg2rad(ra) / 2, | ||
validate_numeric_stability=False)) | ||
decq = Quaternion.exp(Quaternion(0, 0, -np.deg2rad(dec) / 2, 0, | ||
validate_numeric_stability=False)) | ||
rollq = Quaternion.exp(Quaternion(0, -np.deg2rad(roll) / 2, 0, 0, | ||
validate_numeric_stability=False)) | ||
raq = Quaternion.exp([0, 0, 0, -np.deg2rad(ra) / 2]) | ||
decq = Quaternion.exp([0, 0, -np.deg2rad(dec) / 2, 0]) | ||
rollq = Quaternion.exp([0, -np.deg2rad(roll) / 2, 0, 0]) | ||
return rollq * decq * raq | ||
|
||
@staticmethod | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# https://www.python.org/dev/peps/pep-0440/ | ||
__version__ = '0.1.3.dev0' | ||
__version__ = '0.1.3.dev1' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sure @matiasg wants to release a stable version sooner than later :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, i think there are 2 more PRs pending, after which 0.1.4? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[metadata] | ||
description-file = README.md | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing newline There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops spillover, is it needed?? @Juanlu001 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not mandatory, but when doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, i mean - i added setup.cfg half-accidentally, spillover from some other project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed: https://github.com/satellogic/quaternions/blob/master/LICENSE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops spillover, will remove