Skip to content
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

Quaternion.distance seems to produce incorrect result #59

Open
rgolovanov opened this issue Jul 3, 2020 · 3 comments
Open

Quaternion.distance seems to produce incorrect result #59

rgolovanov opened this issue Jul 3, 2020 · 3 comments

Comments

@rgolovanov
Copy link

rgolovanov commented Jul 3, 2020

I'm evaluating the angular distance between two quaternions and it seems that values produced by Quaternion.distance are twice less in comparison with this formula:

The UTs checking angular distance for original and modified distance functions (test_angular_distance.py):

import pyquaternion as quat
import unittest
import math
import numpy as np


def angle_distance_orig(q0, q1):
    return quat.Quaternion.distance(q0, q1) / math.pi * 180


def angle_distance(q0, q1):
    q0 = q0.elements
    q1 = q1.elements
    dotProd = q0[0]*q1[0] + q0[1]*q1[1] + q0[2]*q1[2] + q0[3]*q1[3]
    return 2.0 * math.acos(max(0.0, min(abs(dotProd), 1.0))) / math.pi * 180.0


if __name__ == '__main__':

    orig_quat = quat.Quaternion(axis=[1, 0, 0], degrees=0)
    for angle in range(0, 361, 15):
        for ax in range(0, 3):
            new_quat = quat.Quaternion(axis=np.roll(
                [1, 0, 0], ax), degrees=angle)

            a0 = angle_distance_orig(orig_quat, new_quat) # 0 - 360
            a1 = angle_distance(orig_quat, new_quat) # 0 - 180
            print(f"Angle: {min(angle, 360 - angle):5}, axis: {ax}, original={a0:5.1f}, modified={a1:5.1f}")

The output:

$ python test_angular_distance.py

Angle:     0, axis: 0, original=  0.0, modified=  0.0
Angle:     0, axis: 1, original=  0.0, modified=  0.0
Angle:     0, axis: 2, original=  0.0, modified=  0.0
Angle:    15, axis: 0, original=  7.5, modified= 15.0
Angle:    15, axis: 1, original=  7.5, modified= 15.0
Angle:    15, axis: 2, original=  7.5, modified= 15.0
Angle:    30, axis: 0, original= 15.0, modified= 30.0
Angle:    30, axis: 1, original= 15.0, modified= 30.0
Angle:    30, axis: 2, original= 15.0, modified= 30.0
Angle:    45, axis: 0, original= 22.5, modified= 45.0
Angle:    45, axis: 1, original= 22.5, modified= 45.0
Angle:    45, axis: 2, original= 22.5, modified= 45.0
Angle:    60, axis: 0, original= 30.0, modified= 60.0
Angle:    60, axis: 1, original= 30.0, modified= 60.0
Angle:    60, axis: 2, original= 30.0, modified= 60.0
Angle:    75, axis: 0, original= 37.5, modified= 75.0
Angle:    75, axis: 1, original= 37.5, modified= 75.0
Angle:    75, axis: 2, original= 37.5, modified= 75.0
Angle:    90, axis: 0, original= 45.0, modified= 90.0
Angle:    90, axis: 1, original= 45.0, modified= 90.0
Angle:    90, axis: 2, original= 45.0, modified= 90.0
Angle:   105, axis: 0, original= 52.5, modified=105.0
Angle:   105, axis: 1, original= 52.5, modified=105.0
Angle:   105, axis: 2, original= 52.5, modified=105.0
Angle:   120, axis: 0, original= 60.0, modified=120.0
Angle:   120, axis: 1, original= 60.0, modified=120.0
Angle:   120, axis: 2, original= 60.0, modified=120.0
Angle:   135, axis: 0, original= 67.5, modified=135.0
Angle:   135, axis: 1, original= 67.5, modified=135.0
Angle:   135, axis: 2, original= 67.5, modified=135.0
Angle:   150, axis: 0, original= 75.0, modified=150.0
Angle:   150, axis: 1, original= 75.0, modified=150.0
Angle:   150, axis: 2, original= 75.0, modified=150.0
Angle:   165, axis: 0, original= 82.5, modified=165.0
Angle:   165, axis: 1, original= 82.5, modified=165.0
Angle:   165, axis: 2, original= 82.5, modified=165.0
Angle:   180, axis: 0, original= 90.0, modified=180.0
Angle:   180, axis: 1, original= 90.0, modified=180.0
Angle:   180, axis: 2, original= 90.0, modified=180.0
Angle:   165, axis: 0, original= 97.5, modified=165.0
Angle:   165, axis: 1, original= 97.5, modified=165.0
Angle:   165, axis: 2, original= 97.5, modified=165.0
Angle:   150, axis: 0, original=105.0, modified=150.0
Angle:   150, axis: 1, original=105.0, modified=150.0
Angle:   150, axis: 2, original=105.0, modified=150.0
Angle:   135, axis: 0, original=112.5, modified=135.0
Angle:   135, axis: 1, original=112.5, modified=135.0
Angle:   135, axis: 2, original=112.5, modified=135.0
Angle:   120, axis: 0, original=120.0, modified=120.0
Angle:   120, axis: 1, original=120.0, modified=120.0
Angle:   120, axis: 2, original=120.0, modified=120.0
Angle:   105, axis: 0, original=127.5, modified=105.0
Angle:   105, axis: 1, original=127.5, modified=105.0
Angle:   105, axis: 2, original=127.5, modified=105.0
Angle:    90, axis: 0, original=135.0, modified= 90.0
Angle:    90, axis: 1, original=135.0, modified= 90.0
Angle:    90, axis: 2, original=135.0, modified= 90.0
Angle:    75, axis: 0, original=142.5, modified= 75.0
Angle:    75, axis: 1, original=142.5, modified= 75.0
Angle:    75, axis: 2, original=142.5, modified= 75.0
Angle:    60, axis: 0, original=150.0, modified= 60.0
Angle:    60, axis: 1, original=150.0, modified= 60.0
Angle:    60, axis: 2, original=150.0, modified= 60.0
Angle:    45, axis: 0, original=157.5, modified= 45.0
Angle:    45, axis: 1, original=157.5, modified= 45.0
Angle:    45, axis: 2, original=157.5, modified= 45.0
Angle:    30, axis: 0, original=165.0, modified= 30.0
Angle:    30, axis: 1, original=165.0, modified= 30.0
Angle:    30, axis: 2, original=165.0, modified= 30.0
Angle:    15, axis: 0, original=172.5, modified= 15.0
Angle:    15, axis: 1, original=172.5, modified= 15.0
Angle:    15, axis: 2, original=172.5, modified= 15.0
Angle:     0, axis: 0, original=180.0, modified=  0.0
Angle:     0, axis: 1, original=180.0, modified=  0.0
Angle:     0, axis: 2, original=180.0, modified=  0.0

Is it a bug in function or I'm confusing the usage of the function?

@KieranWynn
Copy link
Owner

Quaternion.distance() returns the geodesic distance not angular distance. If you provide a source for the equation you are using, I can add an angular_distace() method based on this.

@rgolovanov
Copy link
Author

rgolovanov commented Oct 5, 2020

@KieranWynn thanks for your comment, please have a look at this (see Algorithms section)

Possible implementation:

def angular_distance(q0, q1):
    #
    # Calculates the angular distance between two quaternions in degrees
    #    q0, q1 shall be normalized
    #
    #   According to formula:
    #     theta = 2*acos(|<q0,q1>|)
    #   from https://www.mathworks.com/help/fusion/ref/quaternion.dist.html
    #
    # Returns angle in degrees within the range [0, 180] degrees
    #

    dotProd = q0.x*q1.x + q0.y*q1.y + q0.z*q1.z + q0.w*q1.w
    return np.degrees(2.0 * math.acos(max(0.0, min(abs(dotProd), 1.0))))

@jstmn
Copy link

jstmn commented Jan 19, 2023

I ran into this same issue: the values I'm getting from absolute_distance() are twice as large as the expected value. An 'angular_distance()' function would be great!

There seems to be some confusion about this online as well, see https://stackoverflow.com/a/55553058/5191069

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants