|
28 | 28 | import spatialmath.base as smb |
29 | 29 | from spatialmath.base.types import * |
30 | 30 | from spatialmath.base.transformsNd import rt2tr |
| 31 | +from spatialmath.base.vectors import unitvec |
31 | 32 |
|
32 | 33 | _eps = np.finfo(np.float64).eps |
33 | 34 |
|
@@ -679,6 +680,72 @@ def trexp2( |
679 | 680 | raise ValueError(" First argument must be SO(2), 1-vector, SE(2) or 3-vector") |
680 | 681 |
|
681 | 682 |
|
| 683 | +@overload # pragma: no cover |
| 684 | +def trnorm2(R: SO2Array) -> SO2Array: |
| 685 | + ... |
| 686 | + |
| 687 | + |
| 688 | +def trnorm2(T: SE2Array) -> SE2Array: |
| 689 | + r""" |
| 690 | + Normalize an SO(2) or SE(2) matrix |
| 691 | +
|
| 692 | + :param T: SE(2) or SO(2) matrix |
| 693 | + :type T: ndarray(3,3) or ndarray(2,2) |
| 694 | + :return: normalized SE(2) or SO(2) matrix |
| 695 | + :rtype: ndarray(3,3) or ndarray(2,2) |
| 696 | + :raises ValueError: bad arguments |
| 697 | +
|
| 698 | + - ``trnorm(R)`` is guaranteed to be a proper orthogonal matrix rotation |
| 699 | + matrix (2,2) which is *close* to the input matrix R (2,2). |
| 700 | + - ``trnorm(T)`` as above but the rotational submatrix of the homogeneous |
| 701 | + transformation T (3,3) is normalised while the translational part is |
| 702 | + unchanged. |
| 703 | +
|
| 704 | + The steps in normalization are: |
| 705 | +
|
| 706 | + #. If :math:`\mathbf{R} = [a, b]` |
| 707 | + #. Form unit vectors :math:`\hat{b} |
| 708 | + #. Form the orthogonal planar vector :math:`\hat{a} = [\hat{b}_y -\hat{b}_x]` |
| 709 | + #. Form the normalized SO(2) matrix :math:`\mathbf{R} = [\hat{a}, \hat{b}]` |
| 710 | +
|
| 711 | + .. runblock:: pycon |
| 712 | +
|
| 713 | + >>> from spatialmath.base import trnorm, troty |
| 714 | + >>> from numpy import linalg |
| 715 | + >>> T = trot2(45, 'deg', t=[3, 4]) |
| 716 | + >>> linalg.det(T[:2,:2]) - 1 # is a valid SO(3) |
| 717 | + >>> T = T @ T @ T @ T @ T @ T @ T @ T @ T @ T @ T @ T @ T |
| 718 | + >>> linalg.det(T[:2,:2]) - 1 # not quite a valid SE(2) anymore |
| 719 | + >>> T = trnorm2(T) |
| 720 | + >>> linalg.det(T[:2,:2]) - 1 # once more a valid SE(2) |
| 721 | +
|
| 722 | + .. note:: |
| 723 | +
|
| 724 | + - Only the direction of a-vector (the z-axis) is unchanged. |
| 725 | + - Used to prevent finite word length arithmetic causing transforms to |
| 726 | + become 'unnormalized', ie. determinant :math:`\ne 1`. |
| 727 | + """ |
| 728 | + |
| 729 | + if not ishom2(T) and not isrot2(T): |
| 730 | + raise ValueError("expecting SO(2) or SE(2)") |
| 731 | + |
| 732 | + a = T[:, 0] |
| 733 | + b = T[:, 1] |
| 734 | + |
| 735 | + b = unitvec(b) |
| 736 | + # fmt: off |
| 737 | + R = np.array([ |
| 738 | + [ b[1], b[0]], |
| 739 | + [-b[0], b[1]] |
| 740 | + ]) |
| 741 | + # fmt: on |
| 742 | + |
| 743 | + if ishom2(T): |
| 744 | + return rt2tr(cast(SO2Array, R), T[:2, 2]) |
| 745 | + else: |
| 746 | + return R |
| 747 | + |
| 748 | + |
682 | 749 | @overload # pragma: no cover |
683 | 750 | def tradjoint2(T: SO2Array) -> R1x1: |
684 | 751 | ... |
|
0 commit comments