Metrics#
Common metrics used in 3D Orientation representations.
References
Huynh, D.Q. Metrics for 3D Rotations: Comparison and Analysis. J Math Imaging Vis 35, 155-164 (2009).
Kuffner, J.J. Effective Sampling and Distance Metrics for 3D Rigid Body Path Planning. IEEE International Conference on Robotics and Automation (ICRA 2004)
- ahrs.utils.metrics.angular_distance(R1: ndarray, R2: ndarray) float #
Angular distance between two rotations \(\mathbf{R}_1\) and \(\mathbf{R}_2\) in SO(3), as defined in [Hartley]:
\[d(\mathbf{R}_1, \mathbf{R}_2) = \|\log(\mathbf{R}_1\mathbf{R}_2^T)\|\]where \(\|\mathbf{x}\|\) represents the usual euclidean norm of the vector \(\mathbf{x}\).
- Parameters:
R1 (numpy.ndarray) – 3-by-3 rotation matrix.
R2 (numpy.ndarray) – 3-by-3 rotation matrix.
- Returns:
d – Angular distance between rotation matrices
- Return type:
float
Examples
>>> import ahrs >>> R1 = ahrs.DCM(rpy=[0.0, 0.0, 0.0]) >>> R2 = ahrs.DCM(rpy=[90.0, 90.0, 90.0]) >>> ahrs.utils.angular_distance(R1, R2) 1.5707963267948966 >>> R1 = ahrs.DCM(rpy=[10.0, -20.0, 30.0]) >>> R2 = ahrs.DCM(rpy=[-10.0, 20.0, -30.0]) >>> ahrs.utils.angular_distance(R1, R2) 1.282213683073497
- ahrs.utils.metrics.chordal(R1: ndarray, R2: ndarray) float | ndarray #
Chordal Distance
The chordal distance between two rotations \(\mathbf{R}_1\) and \(\mathbf{R}_2\) in SO(3) is the Euclidean distance between them in the embedding space \(\mathbb{R}^{3\times 3}=\mathbb{R}^9\) [Hartley]:
\[d(\mathbf{R}_1, \mathbf{R}_2) = \|\mathbf{R}_1-\mathbf{R}_2\|_F\]where \(\|\mathbf{X}\|_F\) represents the Frobenius norm of the matrix \(\mathbf{X}\).
The error lies within: [0, \(2\sqrt{3}\)]
- Parameters:
R1 (numpy.ndarray) – 3-by-3 rotation matrix.
R2 (numpy.ndarray) – 3-by-3 rotation matrix.
- Returns:
d – Chordal distance between matrices.
- Return type:
float or numpy.ndarray
Examples
>>> import ahrs >>> R1 = ahrs.DCM(rpy=[0.0, 0.0, 0.0]) >>> R2 = ahrs.DCM(rpy=[90.0, 90.0, 90.0]) >>> ahrs.utils.chordal(R1, R2) 2.0 >>> R1 = ahrs.DCM(rpy=[10.0, -20.0, 30.0]) >>> R2 = ahrs.DCM(rpy=[-10.0, 20.0, -30.0]) >>> ahrs.utils.chordal(R1, R2) 1.6916338074634352
- ahrs.utils.metrics.euclidean(x: ndarray, y: ndarray, **kwargs) float #
Euclidean distance between two arrays as described in [Huynh]:
\[d(\mathbf{x}, \mathbf{y}) = \sqrt{(x_0-y_0)^2 + \dots + (x_n-y_n)^2}\]Accepts the same parameters as the function
numpy.linalg.norm()
.This metric gives values in the range [0, \(\pi\sqrt{3}\)]
- Parameters:
x (array) – M-by-N array to compare. Usually a reference array.
y (array) – M-by-N array to compare.
mode (str) – Mode of distance computation.
- Returns:
d – Distance or difference between arrays.
- Return type:
float
Examples
>>> import numpy as np >>> from ahrs.utils.metrics import euclidean >>> num_samples = 5 >>> angles = np.random.uniform(low=-180.0, high=180.0, size=(num_samples, 3)) >>> noisy = angles + np.random.randn(num_samples, 3) >>> euclidean(angles, noisy) 2.585672169476804 >>> euclidean(angles, noisy, axis=0) array([1.36319772, 1.78554071, 1.28032688]) >>> euclidean(angles, noisy, axis=1) # distance per sample array([0.88956871, 1.19727356, 1.5243858 , 0.68765523, 1.29007067])
- ahrs.utils.metrics.identity_deviation(R1: ndarray, R2: ndarray) float #
Deviation from Identity Matrix as defined in [Huynh]:
\[d(\mathbf{R}_1, \mathbf{R}_2) = \|\mathbf{I}-\mathbf{R}_1\mathbf{R}_2^T\|_F\]where \(\|\mathbf{X}\|_F\) represents the Frobenius norm of the matrix \(\mathbf{X}\).
The error lies within: [0, \(2\sqrt{3}\)]
- Parameters:
R1 (numpy.ndarray) – 3-by-3 rotation matrix.
R2 (numpy.ndarray) – 3-by-3 rotation matrix.
- Returns:
d – Deviation from identity matrix.
- Return type:
float
Examples
>>> import ahrs >>> R1 = ahrs.DCM(rpy=[0.0, 0.0, 0.0]) >>> R2 = ahrs.DCM(rpy=[90.0, 90.0, 90.0]) >>> ahrs.utils.identity_deviation(R1, R2) 2.0 >>> R1 = ahrs.DCM(rpy=[10.0, -20.0, 30.0]) >>> R2 = ahrs.DCM(rpy=[-10.0, 20.0, -30.0]) >>> ahrs.utils.identity_deviation(R1, R2) 1.6916338074634352
- ahrs.utils.metrics.qad(q1: ndarray, q2: ndarray) float #
Quaternion Angle Difference as defined in [Thibaud]:
\[d(\mathbf{q}_1, \mathbf{q}_2) = \arccos(2\langle\mathbf{q}_1,\mathbf{q}_2\rangle^2-1)\]The error lies within: [0, \(\pi\)]
- Parameters:
q1 (numpy.ndarray) – First quaternion, or set of quaternions, to compare.
q2 (numpy.ndarray) – Second quaternion, or set of quaternions, to compare.
- Returns:
d – Angle difference between given unit quaternions.
- Return type:
float
Examples
>>> q1 = ahrs.Quaternion(random=True) >>> q1.view() Quaternion([ 0.94185064, 0.04451339, -0.00622856, 0.33301221]) >>> q2 = ahrs.Quaternion(random=True) >>> q2.view() Quaternion([-0.51041283, -0.38336653, 0.76929238, -0.0264211 ]) >>> ahrs.utils.qad(q1, q2) 2.0679949008393335 >>> ahrs.utils.qad(q1, -q1) 0.0
References
[Thibaud]Thibaud Michel, Pierre Genevès, Hassen Fourati, Nabil Layaïda. On Attitude Estimation with Smartphones. IEEE International Conference on Pervasive Computing and Communications, Mar 2017, Kona, United States. ⟨hal-01376745v2⟩
- ahrs.utils.metrics.qcip(q1: ndarray, q2: ndarray) float #
Cosine of inner products as defined in [Huynh]:
\[d(\mathbf{q}_1, \mathbf{q}_2) = \arccos(|\mathbf{q}_1\cdot\mathbf{q}_2|)\]The error lies within: [0, \(\frac{\pi}{2}\)]
- Parameters:
q1 (numpy.ndarray) – First quaternion, or set of quaternions, to compare.
q2 (numpy.ndarray) – Second quaternion, or set of quaternions, to compare.
- Returns:
d – Cosine of inner products of quaternions.
- Return type:
float
Examples
>>> q1 = ahrs.Quaternion(random=True) >>> q1.view() Quaternion([ 0.94185064, 0.04451339, -0.00622856, 0.33301221]) >>> q2 = ahrs.Quaternion(random=True) >>> q2.view() Quaternion([-0.51041283, -0.38336653, 0.76929238, -0.0264211 ]) >>> ahrs.utils.qcip(q1, q2) 1.0339974504196667 >>> ahrs.utils.qcip(q1, -q1) 0.0
- ahrs.utils.metrics.qdist(q1: ndarray, q2: ndarray) float #
Euclidean distance between two unit quaternions as defined in [Huynh] and [Hartley]:
\[d(\mathbf{q}_1, \mathbf{q}_2) = \mathrm{min} \{ \|\mathbf{q}_1-\mathbf{q}_2\|, \|\mathbf{q}_1-\mathbf{q}_2\|\}\]The error lies within [0, \(\sqrt{2}\)]
- Parameters:
q1 (numpy.ndarray) – First quaternion, or set of quaternions, to compare.
q2 (numpy.ndarray) – Second quaternion, or set of quaternions, to compare.
- Returns:
d – Euclidean distance between given unit quaternions.
- Return type:
float
Examples
>>> q1 = ahrs.Quaternion(random=True) >>> q1.view() Quaternion([ 0.94185064, 0.04451339, -0.00622856, 0.33301221]) >>> q2 = ahrs.Quaternion(random=True) >>> q2.view() Quaternion([-0.51041283, -0.38336653, 0.76929238, -0.0264211 ]) >>> ahrs.utils.qdist(q1, q2) 0.9885466801358284 >>> ahrs.utils.qdist(q1, -q1) 0.0
- ahrs.utils.metrics.qeip(q1: ndarray, q2: ndarray) float #
Euclidean distance of inner products as defined in [Huynh] and [Kuffner]:
\[d(\mathbf{q}_1, \mathbf{q}_2) = 1 - |\mathbf{q}_1\cdot\mathbf{q}_2|\]The error lies within: [0, 1]
- Parameters:
q1 (numpy.ndarray) – First quaternion, or set of quaternions, to compare.
q2 (numpy.ndarray) – Second quaternion, or set of quaternions, to compare.
- Returns:
d – Euclidean distance of inner products between given unit quaternions.
- Return type:
float
Examples
>>> q1 = ahrs.Quaternion(random=True) >>> q1.view() Quaternion([ 0.94185064, 0.04451339, -0.00622856, 0.33301221]) >>> q2 = ahrs.Quaternion(random=True) >>> q2.view() Quaternion([-0.51041283, -0.38336653, 0.76929238, -0.0264211 ]) >>> ahrs.utils.qeip(q1, q2) 0.48861226940378377 >>> ahrs.utils.qeip(q1, -q1) 0.0
- ahrs.utils.metrics.rmse(x: ndarray, y: ndarray)#
Root Mean Squared Error
It is computed as:
\[d(\mathbf{x}, \mathbf{y}) = \sqrt{\frac{1}{N}\sum_{i=1}^N (x_i-y_i)^2}\]where \(N\) is the number of elements in \(\mathbf{x}\) and \(\mathbf{y}\).
If \(\mathbf{x}\) and \(\mathbf{y}\) are \(M \times N\) matrices, then the RMSE is computed for each row, yielding a vector of length \(M\).
- Parameters:
x (numpy.ndarray) – First set of values to compare.
y (numpy.ndarray) – Second set of values to compare.
- Returns:
d – Root mean squared error between given values.
- Return type:
float
- ahrs.utils.metrics.rmse_matrices(A: ndarray, B: ndarray, element_wise: bool = False) ndarray #
Root Mean Square Error between two arrays (matrices)
- Parameters:
A (np.ndarray) – First M-by-N matrix or array of k M-by-N matrices.
B (np.ndarray) – Second M-by-N matrix or array of k M-by-N matrices.
element_wise (bool, default: False) – If True, calculate RMSE element-wise, and return an M-by-N array of RMSEs.
- Returns:
rmse – Root Mean Square Error between the two matrices, or array of k RMSEs between the two arrays of matrices.
- Return type:
float or np.ndarray
- Raises:
ValueError – If the comparing arrays do not have the same shape.
Notes
If the input arrays are 2-dimensional matrices, the RMSE is calculated as:
\[RMSE = \sqrt{\frac{1}{MN}\sum_{i=1}^{M}\sum_{j=1}^{N}(A_{ij} - B_{ij})^2}\]If the input arrays are arrays of 2-dimensional matrices (3-dimensional array), the RMSE is calculated as:
\[RMSE = \sqrt{\frac{1}{k}\sum_{l=1}^{k}\frac{1}{MN}\sum_{i=1}^{M}\sum_{j=1}^{N}(A_{ij}^{(l)} - B_{ij}^{(l)})^2}\]where \(k\) is the number of matrices in the arrays.
If the option
element_wise
is set toTrue
, the RMSE is calculated element-wise, and an M-by-N array of RMSEs is returned. The following calls are equivalent:rmse = rmse_matrices(A, B, element_wise=True) rmse = np.sqrt(np.nanmean((A-B)**2, axis=0))
If the inputs are arrays of matrices (3-dimensional arrays), its call is also equivalent to:
rmse = np.zeros_like(A[0]) for i in range(A.shape[1]): for j in range(A.shape[2]): rmse[i, j] = np.sqrt(np.nanmean((A[:, i, j]-B[:, i, j])**2))
If the inputs are 2-dimensional matrices, the following calls would return the same result:
rmse_matrices(A, B) rmse_matrices(A, B, element_wise=False) rmse_matrices(A, B, element_wise=True)
Examples
>>> C = np.random.random((4, 3, 2)) # Array of four 3-by-2 matrices >>> C.view() array([[[0.2816407 , 0.30850589], [0.44618209, 0.33081522], [0.7994625 , 0.07377569]], [[0.35549399, 0.47050713], [0.94168683, 0.50388058], [0.70023837, 0.77216167]], [[0.79897129, 0.28555452], [0.892488 , 0.71476669], [0.19071524, 0.4123666 ]], [[0.86301978, 0.14686002], [0.98784823, 0.26129908], [0.46982206, 0.88037599]]]) >>> D = np.random.random((4, 3, 2)) # Array of four 3-by-2 matrices >>> D.view() array([[[0.71560918, 0.34100321], [0.92518341, 0.50741267], [0.30730944, 0.19173378]], [[0.31846657, 0.08578454], [0.62643489, 0.84014104], [0.7111152 , 0.95428613]], [[0.8101591 , 0.9584096 ], [0.91118705, 0.71203119], [0.58217189, 0.45598271]], [[0.79837603, 0.09954558], [0.26532781, 0.55711476], [0.03909648, 0.10787888]]]) >>> rmse_matrices(C[0], D[0]) # RMSE between first matrices 0.3430603410873006 >>> rmse_matrices(C, D) # RMSE between each of the four matrices array([0.34306034, 0.25662067, 0.31842239, 0.48274156]) >>> rmse_matrices(C, D, element_wise=True) # RMSE element-wise along first dimension array([[0.22022923, 0.3886001 ], [0.46130561, 0.2407136 ], [0.38114819, 0.40178899]]) >>> rmse_matrices(C[0], D[0], element_wise=True) 0.3430603410873006 >>> rmse_matrices(C[0], D[0]) 0.3430603410873006