Search code examples
scipyeuler-angles

SciPy rotation matrix from/as Euler angles


I'm working with Euler angles and SciPy's implementation of them. I'm having a hard time understanding how SciPy initializes rotations matrices from Eulers, or how it represents matrices as Eulers, or both. From reading the documentation, I would assume that a single call

from_euler("ZYX", (yaw, pitch, roll))

is equivalent to a multiplication of three matrices computed from one axis each, but a simple test invalidates this assumption:

from scipy.spatial.transform import Rotation as R

def r1(roll, pitch, yaw):
    r = R.from_euler("ZYX", (yaw, pitch, roll), degrees=True)
    yaw, pitch, roll = r.as_euler("ZYX", degrees=True)
    print(f"r:{roll:5.1f} p:{pitch:5.1f} y:{yaw:5.1f}")

def r2(roll, pitch, yaw):
    r = R.from_euler("ZYX", (0, 0, roll), degrees=True) * \
        R.from_euler("ZYX", (0, pitch, 0), degrees=True) * \
        R.from_euler("ZYX", (yaw, 0, 0), degrees=True)
    yaw, pitch, roll = r.as_euler("ZYX", degrees=True)
    print(f"r:{roll:5.1f} p:{pitch:5.1f} y:{yaw:5.1f}")

r1(35, 25, 115)  # as expected: "r:  35.0 p: 25.0 y: 115.0"
r2(35, 25, 115)  # surprising:  "r:   5.5 p:-41.8 y: 120.9"

I would be grateful for any pointers to documentation that can elaborate further on who SciPy does, or, alternatively, the obvious-to-everyone-else thing I'm missing about rotation matrices.


Solution

  • Maybe try:

    from scipy.spatial.transform import Rotation as R
    
    def r1(roll, pitch, yaw):
        r = R.from_euler("ZYX", (yaw, pitch, roll), degrees=True)
        yaw, pitch, roll = r.as_euler("ZYX", degrees=True)
        print(f"r:{roll:5.1f} p:{pitch:5.1f} y:{yaw:5.1f}")
    
    def r2(roll, pitch, yaw):
        r = R.from_euler("ZYX", (0, 0, roll), degrees=True) * \
            R.from_euler("ZYX", (0, pitch, 0), degrees=True) * \
            R.from_euler("ZYX", (yaw, 0, 0), degrees=True)
        yaw, pitch, roll = r.as_euler("ZYX", degrees=True)
        print(f"r:{roll:5.1f} p:{pitch:5.1f} y:{yaw:5.1f}")
    
    # solution
    def r3(roll, pitch, yaw):
        r = R.from_euler("ZYX", (yaw, 0, 0), degrees=True) * \
            R.from_euler("ZYX", (0, pitch, 0), degrees=True) * \
            R.from_euler("ZYX", (0, 0, roll), degrees=True)
        yaw, pitch, roll = r.as_euler("ZYX", degrees=True)
        print(f"r:{roll:5.1f} p:{pitch:5.1f} y:{yaw:5.1f}")
    
    r1(35, 25, 115)  # as expected: "r:  35.0 p: 25.0 y: 115.0"
    r2(35, 25, 115)  # surprising:  "r:   5.5 p:-41.8 y: 120.9"
    r3(35, 25, 115)  # as expected: "r:  35.0 p: 25.0 y: 115.0"