@@ -1452,6 +1452,73 @@ def Rt(cls, R, t, check=True):
1452
1452
1453
1453
return cls (base .rt2tr (R , t ))
1454
1454
1455
+ def angdist (self , other , metric = 6 ):
1456
+ r"""
1457
+ Angular distance metric between poses
1458
+
1459
+ :param other: second rotation
1460
+ :type other: SE3 instance
1461
+ :param metric: metric, default is 6
1462
+ :type metric: int
1463
+ :raises TypeError: if other is not an SE3
1464
+ :return: angle in radians
1465
+ :rtype: float or ndarray
1466
+
1467
+ ``T1.angdist(T2)`` is the geodesic norm, or geodesic distance between the
1468
+ rotational parts of the two poses.
1469
+
1470
+ Several metrics are supported, the first 5 are computed after conversion
1471
+ to unit quaternions.
1472
+
1473
+ ====== ===============================================================
1474
+ Metric Details
1475
+ ====== ===============================================================
1476
+ 0 :math:`1 - | \q_1 \bullet \q_2 | \in [0, 1]`
1477
+ 1 :math:`\cos^{-1} | \q_1 \bullet \q_2 | \in [0, \pi/2]`
1478
+ 2 :math:`\cos^{-1} | \q_1 \bullet \q_2 | \in [0, \pi/2]`
1479
+ 3 :math:`2 \tan^{-1} \| \q_1 - \q_2\| / \|\q_1 + \q_2\| \in [0, \pi/2]`
1480
+ 4 :math:`\cos^{-1} \left( 2 (\q_1 \bullet \q_2)^2 - 1\right) \in [0, 1]`
1481
+ 5 :math:`\|I - \mat{R}_1 \mat{R}_2^T\| \in [0, 2]`
1482
+ 6 :math:`\|\log \mat{R}_1 \mat{R}_2^T\| \in [0, \pi]`
1483
+ ====== ===============================================================
1484
+
1485
+ Example:
1486
+
1487
+ .. runblock:: pycon
1488
+
1489
+ >>> from spatialmath import UnitQuaternion
1490
+ >>> T1 = SE3.Rx(0.3)
1491
+ >>> T2 = SE3.Ry(0.3)
1492
+ >>> print(T1.angdist(T1))
1493
+ >>> print(T1.angdist(T2))
1494
+
1495
+ .. note::
1496
+ - metrics 1, 2, 4 can throw ValueError "math domain error" due to
1497
+ numeric errors which push the argument of ``acos()`` marginally
1498
+ outside its domain [0, 1].
1499
+ - metrics 2 and 3 are equivalent, but 3 is more robust
1500
+
1501
+ :seealso: :func:`UnitQuaternion.angdist`
1502
+ """
1503
+
1504
+ if metric < 5 :
1505
+ from spatialmath .quaternion import UnitQuaternion
1506
+
1507
+ return UnitQuaternion (self ).angdist (UnitQuaternion (other ), metric = metric )
1508
+
1509
+ elif metric == 5 :
1510
+ op = lambda T1 , T2 : np .linalg .norm (np .eye (3 ) - T1 [:3 ,:3 ] @ T2 [:3 ,:3 ].T )
1511
+ elif metric == 6 :
1512
+ op = lambda T1 , T2 : base .norm (base .trlog (T1 [:3 ,:3 ] @ T2 [:3 ,:3 ].T , twist = True ))
1513
+ else :
1514
+ raise ValueError ('unknown metric' )
1515
+
1516
+ ad = self ._op2 (other , op )
1517
+ if isinstance (ad , list ):
1518
+ return np .array (ad )
1519
+ else :
1520
+ return ad
1521
+
1455
1522
# @classmethod
1456
1523
# def SO3(cls, R, t=None, check=True):
1457
1524
# if isinstance(R, SO3):
0 commit comments