Notes on Rotations and Quaternions

Author

Oliver Dürr

This is a primer on rotations and quaternions, as needed to describe rotations. Rotations are a constant source of confusion and they have confused me several time. This is kind of a letter to my future self.

This is Work in Progress TODOS

Some handwritten note are also availible as pdf.

1 A short note on rotations

In the figure, you can see a sketch of the situation for gesture recognition. We are interested in “the relative positions” between the palm and thumb on the one side and the laboratory system on the other. As we will see this (and many more things) can be described in the framework of rotations.

Figure 1: Rotations transform between coordinate system, such as a fixed coordinate system in space (toothpicks on table), a body centered coordinate system on the thumb and one on the plam.

1.1 A first rotation

Before we go into some math, lets have a first look how a rotation might look like. On the left side you see data points in the original space. On the right side a rotation by 45 degrees around the z-axis that have been applied to the points. This rotation can be obtained by a rotation matrix R:

Code
R = matrix(c(0.7071068,  0.7071068,  0.0000000, -0.7071068,  0.7071068,  0.0000000,  0.0000000,  0.0000000,  1.0000000), nrow=3)
R = R
round(R,4)
       [,1]    [,2] [,3]
[1,] 0.7071 -0.7071    0
[2,] 0.7071  0.7071    0
[3,] 0.0000  0.0000    1
Code
par(mfrow=c(1,2))

As we will see in a second an example a point \(x\) of the rabid cloud is transformed via:

\[ x_\text{rot} = R \, x \]

Code
par(mfrow=c(1,2))
p3d(t(rabbit), h=NULL, xlab='x', ylab='y', zlab='z', main='rabbit')
p3d(t(R %*% rabbit), h=NULL,main='R * rabbit') #For plotting we need (N,3)

Left: orinial data. Right: data rotated by 45 degrees around y-axis (or coordinate system rotated by -45) degrees. The y-axis points up and the rotation is done in counter clock-wise manner

Code
par(mfrow=c(1,1))

1.2 Introduction

Rotations between rigid objects can be described as a linear transformation from one coordinate system to another, with the same origin. A first example is the bunny from above. Another similar example of such a rotation is the orientation of your cell phone after you picked it up compared to the position on the table, or the orientation of the thumb in figure compared to the coordinate system of the palm.

A very illustrative example is an airplane which is at \(t=0\) on the ground and is at \(t=t\) in the air. Besides the translation, the orientation of the plane at \(t=0\) relative to the plane at \(t=t\) can be described by a different heading (the yaw angle) obtained while taxing, the pitch angle obtained in the climbing phase and a rotation around the axis of flight the roll angle. During this phase many rotations happen but they can all be summarized in a single rotation.

1.3 Parameterization

1.3.1 Definition of rotations

Rotations can be seen as linear transformation from one coordinate system into another. But there are several way to parametrize them. The first is via a rotation matrix.

1.3.2 Rotation matrix (general properties)

In principal a rotation is given by the 9 matrix elements of \(R\). However they cannot be arbitrary, they still need to describe rotations. What are the defining properties of rotations? It turns out that a rotation, has an inverse and the inverse simply the transposed \(R^{-1} = R^T\) (this is the orthogonality). Further, \(det(R)=1\) this is a consequence that the volume in the rotated system stays constant and also the orientation (it’s \(det(R)=1\) and not just \(|det(d)|=1\)). So in matrix notation a rotation is defined by

\[ R=\begin{bmatrix}r_{11} & r_{12} & r_{13} \\r_{21} & r_{22} & r_{23} \\r_{31} & r_{32} & r_{33} \end{bmatrix}, \quad R^TR=RR^T=1,\quad det(R)=1 \tag{1}\]

Multiplying two rotation matrices \(R_1\) and \(R_2\) is a again a rotation matrix, this is why rotations in 3D are also called special orthogonal group SO(3). Note that the order of rotations matter and \(R_1 R_2 \ne R_2 R_1\)

So it’s obvious that is over parameterized using 9 values and there are constrains on the \(r_{ij}\) so that eq. 1 is fulfilled.

1.3.2.1 Rotation-Matrices (“the from to” pitfall)

When we condescend ourselves to the depth of matrix multiplication, the first of two pitfalls lingers around. This is due to the fact that matrix multiplication introduces an order. Say, we want to transform a vector \(\vec{f}\) from a “from coordinate system” into a vector \(\vec{t}\) given in coordinates of the “to coordinate system”. We can do this via:

\[ \vec{t}=R \,\vec{f} \]

To go the other way around, use \(\vec{f}=R^{-1} \vec{t}\). Figure XXX gives an example.

Figure 2: A rotation, transforms the coordinates of a point, from the red coordinate system into the green.

1.3.2.2 The body and the lab frame

Rotations are just transformation between to coordinate system. Some coordinate system have special meanings. It’s good to think of the airplane again and think of two coordinate systems. One is fixed with the airplane, this is called body frame one is fixed with the earth this is called lab frame.

  • In the body frame of points of the plane have the same x,y,z coordinates (if nothing bad happens), a plane is rigid body.

  • In the lab frame the gravity stays constant (it points down). People not sitting in the plane see the coordinates of the plane in this system.

1.3.2.3 Choosing the direction of the rotation

The question is with is the “to system” and which is the “from system”. As often when there is no mathematical reason for a certain way to do things, it gets ugly. There is no natural way to choose one over the other but for some applications a certain order is quite handy.

  • Body to Lab: If you do visualizations, it is a least concepually nicer to describe the rotation from body to lab. Knowing the points of the airplane and the rotation, you can draw a picture of the rotated airplane. They are also called frame rotations, as they involve rotating an object or coordinate frame around a specific axis. Some call them also just rotations (Großekatthöfer and Yoon 2012).

  • Lab to body: In the lab frame the gravity stays constant, often chosen in z-direction as \((0,0,-g)\), then knowing the rotation allows to calculate the gravity on the x-component of an IMU sensor in the plane. These are also called point rotations since they are made relative to a fix point in space (such as the gravity). Sometimes they go under the name (coordinate) transformations, see (Großekatthöfer and Yoon 2012).

It is important to state the direction of the transformation and ideally stick to that order. I find it easier use body to lab and use this transformation here.

We use body to lab (a.k.a. frame) rotations in this text.

1.3.3 Parameterization using elementary rotation.

Many repeated rotations are again a rotation (closure). Moreover it can be shown that an arbitrary rotation can be build out of three elementary rotations. Here the mess begins, there are many ways to define such as decomposition. For example one possibility is: First, a counter-clockwise rotation \(R_z(\alpha)\) around the \(z\)-axis by an angle of \(\alpha\). Second, \(R_x(\beta)\) rotates around “the new” \(x\)-axis and finally by \(R_z(\gamma)\) around the new \(z\)-axis.

Figure 3: Animation showing the sequence of elementary rotation. Taken from wikepedia

The animation in Figure 3 shows three consecutive rotations. Is is important not to forget that, we use column vector and do matrix times vector hence. The complete rotation for first \(R_1\) then \(R_2\) then \(R_3\) is written as

Danger

\[ R = R_3 R_2 R_1 \]

Going the other way around is also possible, you just need to redefine matrix multiplications.

Here is an explanation (taken from stackexchange)

The product \(AB\) of matrices is the effect of applying \(A\) after applying \(B\), simply because we write the “effect” of a matrix \(A\) on a vector \(v\) as \(Av\) (i.e., the convention is to work with column vectors). Thus \((AB)v = A(Bv)\) is \(A\) applied to \(Bv\). Note that this convention matches the usual notation for (nested) functions: \(\log(\sin(x))\) is the logarithm function applied to the result from applying the sine function to \(x\).

Of course, the rules for computing the product of matrices are just right for this convention. Or, if one wanted \(AB\) to stand for “first apply \(A\), then apply \(B\)”, one would use row vectors instead and write “A applied to \(v\)” as \(vA\) - which is very uncommon (and in effect just means that one works with the transposed matrices, relative to the usual convention).

1.3.3.1 Deriving the elementary transformations

The individual transformations can be derived by looking where the unit vectors are transformed. In the figure below you see a derivation of the rotation matrix \(R_z(\alpha)\) around the z axis with an angle of \(\alpha>0\) . We move the body centric coordinate system w.r.t the fixed system the direction is determined by the right-hand rule (thumb show in direction of the rotation and fingers indicate the direction)

Rotation around z-axis with positve \(\alpha\) the dots from the airplane in the fixed body system.

So in this case we have for a rotation about \(\alpha\) around the z-Axis.

\[ R_z(\alpha) = R_{L \leftarrow B}^z(\alpha) =\begin{bmatrix}\cos(\alpha) & -\sin(\alpha) & 0 \\\sin(\alpha) &\cos(\alpha) & 0 \\0 & 0 & 1 \end{bmatrix} \tag{2}\]

For moving the rabbit , we get the following rotation matrix:

Code
Rz = function(a){
  return(
    matrix(
        c(cos(a), -sin(a), 0, sin(a), cos(a), 0, 0, 0, 1)
        , nrow=3, byrow = R+TRUE) 
  )
}
Rz(45*2*pi/360)
          [,1]       [,2] [,3]
[1,] 0.7071068 -0.7071068    0
[2,] 0.7071068  0.7071068    0
[3,] 0.0000000  0.0000000    1

which is same rotation matrix as used earlier in Figure XXX to transform the data.

1.3.3.2 A worked out example (Yaw-Pitch-Roll)

Using an airplane as example, we have a pretty good understanding what happens (taxiing, take-off, then rolling) and we can derive a nice chain of elementary rotations, which will serve as reference for later. The derivation can also be found at the following notes.

In the example, we use the following angles:

Code
yaw = 60 * 2 * pi / 360     #This is w.r.t. the tower / labframe
pitch = -50 * 2 * pi / 360  #Angle of climb
roll = 40 * 2 * pi / 360    #Angle of roll
print(paste('In rad yaw ', yaw, ' pitch ', pitch, ' roll ',roll))
[1] "In rad yaw  1.0471975511966  pitch  -0.872664625997165  roll  0.698131700797732"

The pitch needs to be negative in order that a the rotation leads to a ascent (see later).

1.3.3.2.1 Taxiing yaw \(\alpha\)

For the given yaw we have:

Code
R.yaw = Rz(yaw) 
R.yaw
          [,1]       [,2] [,3]
[1,] 0.5000000 -0.8660254    0
[2,] 0.8660254  0.5000000    0
[3,] 0.0000000  0.0000000    1
1.3.3.2.2 Construction of data points for the airplane.

We create \(3 \times N\) data points so that it is easier for plotting. When we want to use the normal data points in statistics, we could use vector times matrix and do proper transposing.

Code
set.seed(42)
n = 1100
x = runif(n,-1,1)
y = runif(n, -0.4,0.4)
z = runif(n,-0.1,0.1)
col = rep('blue',n)
col[x>0.5] = 'red'
col[x<= -0.5] = 'green'
col[y<= -0.2] = 'gray30'

plane = as.matrix(data.frame(x=x,y=y,z=z))
plane = t(plane) #3xN
library(rgl)
library(scatterplot3d) # load
 

make_plot = function(p1, p2, l1=c('x','y','z'), l2=c('x','y','z'), angle = 55) {
  par(mfrow=c(1,2))
  p1 = t(p1)
  p2 = t(p2)
  #First plot
  scatterplot3d(p1[,1],p1[,2],p1[,3], 
                xlim=c(-1,1), ylim=c(-1,1), zlim=c(-1,1), 
                color = col,
                xlab=l1[1], ylab=l1[2], zlab = l1[3],angle = angle, pch=16)
  scatterplot3d(p2[,1],p2[,2],p2[,3], 
                xlim=c(-1,1), ylim=c(-1,1), zlim=c(-1,1), 
                color = col,
                xlab=l2[1], ylab=l2[2], zlab = l2[3],angle = angle, pch=16)
}

To visualize, we need the body coordinates, transformed to lab coordinates.

Code
##Since we have column vector, we have to transpose once more yielding
plane.yaw  = R.yaw %*% plane 
make_plot(plane, plane.yaw, l2=c("x'","y'","z'"))

Left: orinial data. Right: data rotated by yaw degrees around z-axis

Code
if (FALSE){#Debuggin
  print('Maximal difference, in x-direction')
  max(abs(plane.yaw[,3] - plane[,3]))
}
1.3.3.2.3 Take-off the pitch \(\beta\)

When the plane takes off, all rotates rotated about \(\beta\) about the y-axis. This is given by

\[ R^y(\beta) = \begin{bmatrix}\cos(\beta) & 0 & +\sin(\beta)\\ 0 & 1 & 0 \\ -\sin(\beta) & 0 & \cos(\beta)\end{bmatrix} \]

Note that the rotation is about the y-axis (use the right hand the thumb is the axis and the fingers shows the direction). Acceding, corresponds to negative values (that’s the reason we choose negative value)

Code
Ry = function(b) matrix(
      c(cos(b), 0, +sin(b), 0, 1, 0, -sin(b), 0, cos(b))
      , nrow=3, byrow = TRUE) 
R.pitch = Ry(pitch) 
R.pitch
          [,1] [,2]       [,3]
[1,] 0.6427876    0 -0.7660444
[2,] 0.0000000    1  0.0000000
[3,] 0.7660444    0  0.6427876
1.3.3.2.3.1 Takeoff without taxiing
Code
make_plot(plane, R.pitch %*% plane)

Left: orinial data. Right: data rotated by XXX degrees around y-axis

1.3.3.2.3.2 Takeoff with taxiing

Here, we combine the transformations (note the order)

\[ x=R^{y}(\beta)R^z(\alpha)x'=(R^{y}(\beta)R^z(\alpha)) \; x'=R^{y}(\beta)\; (R^z(\alpha)x') \]

Note that we can get \(x\) in two ways. We could first calculate \(R_{pitch.yaw}=R^{y}(\beta)R^z(\alpha)\) and then apply it to \(x'\) or we could use the values \(x_{yaw}\) and apply \(R^y(\beta)\) on then. To avoid mistakes it’s better to look at the full equation and see the possible associations by setting brackets. An error of this kind in the lab-to-body transformation costed me quite some time to fix.

Code
R = R.pitch %*% R.yaw
plane.pitch.yaw = R %*% plane 
plane.pitch.yaw2 = R.pitch %*% plane.yaw
make_plot(plane.yaw, plane.pitch.yaw,l1=c("x'", "y'", "z''"), l2=c("x''", "y''", "z''")) 

Left: orinial data. Right: data rotated by XXX degrees around y-axis

Code
max(abs(plane.pitch.yaw - plane.pitch.yaw2))
[1] 1.110223e-16
1.3.3.2.4 In the air, the roll \(\gamma\)

Now that we are in the air, we roll the plane about a certain degree around the x-axis. This can be done by

\[ R^x(\gamma) = \begin{bmatrix} 1 & 0 & 0\\ 0 & \cos(\gamma) & -\sin(\gamma) \\ 0 & \sin(\gamma) & \cos(\gamma) \end{bmatrix} \]

Code
Rx = function(g) matrix(
      c(1,0,0, 0, cos(g), -sin(g), 0, sin(g), cos(g))
      , nrow=3, byrow = TRUE) 
R.roll=Rx(roll)
R.roll
     [,1]      [,2]       [,3]
[1,]    1 0.0000000  0.0000000
[2,]    0 0.7660444 -0.6427876
[3,]    0 0.6427876  0.7660444


The final transformation can be found via

\[ x=R(\alpha, \beta, \gamma)\,x= R^{x}(\gamma) R^{y}(\beta)\,R^z(\alpha)x' \]

Code
R = R.roll %*% R.pitch %*% R.yaw
plane.roll.pitch.yaw = R %*% plane 
make_plot(plane.pitch.yaw, plane.roll.pitch.yaw,l1=c("x''", "y''", "z''"), l2=c("x'''", "y'''", "z'''")) 

Left: orinial data. Right: data rotated by degrees around y-axis

Playing around (sorry not in document yet)

Code
#Return the rotion matrix (Lab  <- Fixed)
get_R = function(yaw, pitch, roll){
   Rroll = Rx(roll)
   Ryaw  = Rz(yaw)
   Rp = Ry(pitch) 
   Rani = Rroll %*% Rp %*% Ryaw
   return(Rani)
}
get_R(yaw, pitch, roll)
          [,1]       [,2]       [,3]
[1,] 0.3213938 -0.5566704 -0.7660444
[2,] 0.4172120  0.8094565 -0.4131759
[3,] 0.8500824 -0.1868108  0.4924039
Code
if (FALSE){
  ##### copy paste the code below
  library(manipulate)
  manipulate(
    {
      plane.lab = get_R(yaw=yaw, roll=roll, pitch = pitch) %*% plane
      make_plot(plane, plane.lab,l1=c("x''", "y''", "z''"), l2=c("x'''", "y'''", "z'''"))
    },
      yaw = slider(0, 2*pi, initial = 0),
      pitch = slider(-pi/2,pi/2, initial = 0),
      roll = slider(-pi, pi, initial = 0)
  )
}  

1.3.3.3 Note on airplane system

The choice to have the z-axis pointing up and so on has been arbitrary, the only important thing is that we have right handed coordinate systems. If you don’t like the fact that you need a negative pitch and that you are willing to give up that the z-axis is pointing upwards, you can use a coordinate system like, shown in the next picture.

Alternative coordinate system. Note the rotations stay the same.

1.3.3.4 Gimbal Look

Further, in this parameterization the effect of changing the angles by a bit has different effects depending on the values. In the extreme case changing the angle does nothing anymore, which is known under the name gimbals look (see e.g. here for a nice demonstration).

2 Quaternions

Besides defining a rotation by 3 elementary rotations, with the many implied ambiguities and the gimbal lock there are other possibilities to describe rotations. Quaterions are such a possibility. They used as standard output format of many libraries such as AHRS and the IMU sensors. Quaternions are objects which consists of 4 numbers, and the rotation around a vector can be nicely described with them as we will see in a second. But they also have some mathematical raison d’etre.

2.1 Definition of Quaternions

Complex numbers \(|a|e^{i \theta}\) describe points in the real / imaginary space. Rotations in 2D can be nicely described by them using multiplications. Quaternions can be seen as a generalization of complex numbers with two more imaginary like units \(i,j,k\). Using these, the quaternion can be written as:

\[ q = (q_w, q_1, q_2, q_3) = q_w + q_1 i + q_2 j + q_3 k \]

The basis have some properties (there are more)

\[ i^2=j^2=k^2=ijk=-1 \;\; ij = -ji = k \]

The length of a quaternion can be determined as:

\[ ||q|| = \sqrt{q_w^2 + q_1^2 + q_2^2 + q_3^2 } \]

Here, we are dealing with quaternions of length 1 and almost all quaternions used in Computergraphics are unit quaternions or a.k.a. rotation quaternions.

Note

Here only unit quaternions

All quaternions here are unit / rotation quaternions with length = 1

2.2 Geometric Representation with rotation quaternions

All rotations can be described by unit vector \(\vec{r}=(r_x,r_y,r_z)\) and a counter clock wise rotation (right hand rule) around this vector by an angle \(\theta\). This is shown in the following figure

Figure 4: Rotation of $\theta$ around a vector (Euler rotation). All rotations can be seen as a Euler rotation.

A rotation around \(\vec{r}\) can parameterized with the unit quaternion

\[ q(\theta, v) = \cos(\frac{\theta}{2}) + i \sin(\frac{\theta}{2}) r_1 + j \sin(\frac{\theta}{2}) r_2 + k \sin(\frac{\theta}{2}) r_3 \]

or componentwise

\[ q(\theta, v) = \left(\cos(\frac{\theta}{2}),\; \vec{r} \sin(\frac{\theta}{2}) \right) \]

The length of \(q\) is \(||q||=\cos^2(\theta/2) + \sin^2(\theta/2) (v_1^2 + v_2^2 + v_3^2) = \cos^2(\theta/2) + \sin^2(\theta/2) = 1\)

2.2.1 Some special quaternions

The yawing can be described by a rotation of \(\theta\) around the vector \(\vec{r} = (0,0,1)\) and is given by

\[ q_{\text yaw} = (\cos(\frac{\theta}{2}) \theta, 0, 0, \sin(\frac{1}{2} \theta)) \]

Similarly for pitch and roll. The identity be constructed with \(\theta=0\) about any axis \(q_{id} = (\cos(0), v_1 \sin(0), v_2 \sin(0), v_3 \sin(0) = (1,0,0,0)\).

2.3 Quaterion Algebra

  • Conjugate \(q^*\) \(\theta \rightarrow -\theta\) is inverse for rotation quaternions.

\[ q^* = q(-\theta, v) = \left(\cos(\frac{\theta}{2}),\; -\vec{r} \sin(\frac{\theta}{2}) \right) = q^{-1} \]

  • Multiplication: The multiplication is not commutative \[ q * p \ne p * q = \tt{Complicated Formula} \] but associative

    \[ (q_1 * q_2) * q_3 = q_1 * (q_2 * q_3) = q_1 * q_2 * q_3 \]

  • Multiplication for unit quaternions (\(w_1 = cos(\theta_1/2)\), \(\vec{d_1}=sin(\theta_1/2)\vec{r_1}\))

  • \[ (w_1, \vec{d}_1)(w_2, \vec{d}_2) = (w_1 w_2 - \vec{d}_1 \cdot \vec{d}_2, w_1 \vec{d}_2 + w_2 \vec{d}_1 + \vec{d}_1 \times \vec{d}_2) \]

  • Inverse (in general) \[ q^{-1} = q^*/||q|| \]

2.4 Pure Quaternions (real part = 0)

We can also use quaternions to transform vectors. This is done with so-called pure quaternions. To obtain a pure quaternions, we set the real part to 0 and use the vector in space as the vector part. That is a vector \(\vec{x'}\) in a certain direction without coding a rotation. From this vector, we like to know the position to which it gets rotated by the Euler rotation \(q\). It can be shown that this is

\[ (0, R(q) \vec{x'}) = q \cdot (0,\vec{x'}) \cdot q^{*} \tag{3}\]

Therefore a rotation can be described by a unit quaternion.

Important

\[ (0, \vec{x}) = q\cdot (0,\vec{x'}) \cdot q^* \]

2.4.1 Rotation Matrix from quaternion

We use the fact that the transformation is given by the results of the unit vectors of the “from coordinate” system:

Code
 quat_to_matrix_projection = function(q){
   e1b = q * quaternion(Re = 0, i = 1) * q^-1 
   e2b = q * quaternion(Re = 0, j = 1) * q^-1 
   e3b = q * quaternion(Re = 0, k = 1) * q^-1 
   return(matrix(c(
            e1b@x[2:4],
            e2b@x[2:4],
            e3b@x[2:4])
   ,nrow=3))
 } 

Let’s tests this against code taken from

Code
source('rotation_utils.R')
q = c(0.320,  0.300,  0.290, -0.850) 
q = q / sqrt(sum(q^2))
q = quaternion(Re=q[1], i=q[2], j=q[3], k=q[4])
round(quat_to_mat(q), 4)
        [,1]    [,2]    [,3]
[1,] -0.6148  0.7187 -0.3247
[2,] -0.3704 -0.6266 -0.6857
[3,] -0.6963 -0.3013  0.6515
Code
quat_to_matrix_projection(q) %>% round(4)
        [,1]    [,2]    [,3]
[1,] -0.6148  0.7187 -0.3247
[2,] -0.3704 -0.6266 -0.6857
[3,] -0.6963 -0.3013  0.6515

2.5 Chaining several quaternions

What is the transformation of a product? Simply put \(q=q_1 * q_2\) into equation XXX and we get

\[ (0,x) = q\,(0,x')\, q^*=q_1 q_2 \, (0,x') \, (q_1 q_2)^*=q_1 q_2 \, (0,x') \, q_2^* q_1^* \]

So we first apply transformation 2 and then transformation 1.

TODO build intuition for this expression, this looks like bra-ket

2.5.1 Example Moving the rabbit

Let’s try this out, and move the rabbit. Note we are still considering body to lab rotations. First, we construct a (4 x N) matrix of pure quaternions adding 0 to the real component of the (3xN) data points of the rabbit.

Code
  q_rabbit = rbind(rep(0, ncol(rabbit)), rabbit)
  q_rabbit = as.quaternion(q_rabbit)

Let’s multiply several quaternions. We start with \(q_0 = (1,0,0,0)\).

Code
  q0 = onion::quaternion(Re = 1)
  d = q0 * q_rabbit * q0^(-1)
  d = d@x[2:4,]
  p3d(t(d), h=NULL, main = 'q0 * rabbit * q0*')

We add a first real rotation around the z-axis (\(\theta\)=45 )

Code
  theta = 45*2*pi/360
  q1 = quaternion(Re=cos(theta/2), i = 0, j = 0, k = sin(theta/2)) 
  q = q0 * q1
  d = q * q_rabbit * q^(-1)
  d = d@x[2:4,]
  p3d(t(d), h=NULL, main = 'q0 q1 rabbit q1* q0*') 

We add a second rotation around the x-axis.

Code
  theta = 20*2*pi/360
  q2 = quaternion(Re=cos(theta/2), i = 1*sin(theta/2), j = 0, k = 0) 
  q012 = q0 * q1 * q2 
  d = q012 * q_rabbit * q012^(-1)
  d = d@x[2:4,]
  p3d(t(d), h=NULL, main = 'q0 q1 q2 rabbit q2* q1* q0*') 

Transforming the can be done with

\[ R(q_0 \cdot q_1 \cdot q_2) x = R(q_0) \cdot R(q_1) \cdot R(q_2) \]

or

\[ q_0 \cdot q_1 \cdot q_2 \; (0,x) \; (q_0 \cdot q_1 \cdot q_2 )^* \]

Therefor we have

\[ R(q_0 \cdot q_1 \cdot q_2) = R(q_0) \cdot R(q_1) \cdot R(q_2) \]

Code
abs(quat_to_mat(q012) - quat_to_mat(q0) %*% quat_to_mat(q1) %*% quat_to_mat(q2)) %>% round(4)
     [,1] [,2] [,3]
[1,]    0    0    0
[2,]    0    0    0
[3,]    0    0    0
q1*q2 corresponds to a rotation, where first q2 is applied and then q1

2.5.2 Flight Coordinates from Quaternions

In the definition above has defined as

\[ R_{\text{Lab} \leftarrow \text{Body}} = R_{roll} \, R_{pitch}\, R_{yaw} \]

Let’s check this by comparing \(R_{roll} \, R_{pitch}\, R_{yaw}\) against \(R(q)\):

Code
  qyaw = quaternion(Re=cos(yaw/2), i=0,j=0,k=sin(yaw/2))
  qpitch = quaternion(Re=cos(pitch/2), i=0,j=sin(pitch/2),k=0)
  qroll = quaternion(Re=cos(roll/2), i=sin(roll/2), j=0,k=0)
  R.yaw = Rz(yaw)
  R.pitch = Ry(pitch)
  R.roll = Rx(roll)
  R = R.roll %*% R.pitch %*% R.yaw
  q = qroll * qpitch * qyaw
  
  if (FALSE) { #For debugging
    sum(qyaw@x^2) #1
    sum(qpitch@x^2) #1
    sum(qroll@x^2) #1
    R.yaw = Rz(yaw)
    sum((quat_to_mat(qyaw) - R.yaw)^2) #close to 0
    R.pitch = Ry(pitch)
    sum((quat_to_mat(qpitch) - R.pitch)^2) #close to 0
    R.roll = Rx(roll)
    sum((quat_to_mat(qroll) - R.roll)^2) #close to 0
    R.roll %*% R.pitch %*% R.yaw - R #close to 0
  }
  max(abs(R - quat_to_mat(q))) #1.4
[1] 1.110223e-16
Code
  #qcomb_lab_to_body = qcomb_body_to_lab^-1
  #max(abs(R - quat_to_mat(qcomb_lab_to_body))) #0
  #---> Quat_to_

2.5.2.1 Quaternion to YPR

The following translation of a quaternion to ypr is often found

\[ \begin{eqnarray*} roll =& atan2(2 \cdot (w \cdot x + y \cdot z), 1 - 2 \cdot (x \cdot x + y \cdot y))\\ pitch =& asin(2 \cdot (w \cdot y - x \cdot z))\\ yaw =& atan2(2 \cdot (w \cdot z + x \cdot y), 1 - 2 \cdot (z \cdot z + y \cdot y)) \end{eqnarray*} \]

Code
quart_to_ypr = function(q_lab2body){
    q = q_lab2body
    w = q[1]
    x = q[2]
    y = q[3]
    z = q[4]
    roll = atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y))
    pitch = asin(2 * (w * y - x * z))
    yaw = atan2(2 * (w * z + x * y), 1 - 2 * (z * z + y * y))
    return(c(yaw,pitch,roll))
}

q_1 = q^(-1)
data.frame(
  true = c(yaw, pitch, roll), 
  q = quart_to_ypr(q@x),   #Wrong
  q_inv = quart_to_ypr(q_1@x) #Nearly correct (angles)
) %>%  round(3)
    true      q  q_inv
1  1.047  0.914 -1.047
2 -0.873 -1.016  0.873
3  0.698 -0.363 -0.698

This shows that the code needs a quaternion going from lab-to-body and not body-to-lab. For going from lab to body, we unroll the elementary rotations (play the movie backwards). The respective quaternion is given by:

\[ q=q_\text{yaw}(\theta_{y}) q_\text{pitch}(\theta_{p}) q_\text{roll}(\theta_{r}) \]

Not 100% sure: It seems that angle is now between the body and lab, it stays as it is.

Code
#Lab to Body (angles have negative sign)
yaw_lb = yaw
pitch_lb = pitch
roll_lb = roll
q_lb = quaternion(Re=cos(yaw_lb/2), i=0,j=0,k=sin(yaw_lb/2)) *
  quaternion(Re=cos(pitch_lb/2), i=0,j=sin(pitch_lb/2),k=0) *
  quaternion(Re=cos(roll_lb/2), i=sin(roll_lb/2), j=0,k=0)

#q_lb = q^(-1)
data.frame(
  true_ypr = c(yaw, pitch, roll),
  ypr_q_lb = quart_to_ypr(q_lb@x)
) %>%  round(3)
  true_ypr ypr_q_lb
1    1.047    1.047
2   -0.873   -0.873
3    0.698    0.698

2.5.3 Rotation between two quaternions.

Consider the two IMUs (like the one on the thumb and the one on the palm), the orientation of the first (w.r.t. the laboratory system) is given by \(q_1\) the orientation of the second by \(q_2\). What is the rotation getting you from hand to palm. The questions is: which rotation \(q_{12}\) translates between them. So we ask which \(q_12\) fulfills

\[ q_2 = q_1 \; q_{12} \]

Just calculate the inverse of \(q_1\) and you are done

\[ q_1^{-1} \; q_2 = q_{12} \]

2.6 Software package

There are several software packages helping to deal with rotations and quaterions. Like the onion package in R.

2.6.1 The AHRS Python Package

The AHRS package (https://ahrs.readthedocs.io/en/latest/index.html) used to estimate the rotation of a IMUs (sensors in e.g. cell phones and drones), uses quaternion as lot and also provides some basic operations.

Code
from ahrs import Quaternion
p = Quaternion([0.7071, 0.0, 0.7071, 0.0])
p 
q = Quaternion([0, 0.7071, 0.0, 0.7071])
p * q #0., 1., 0., 0.
q * p #0., 0., 0., 1.
q.conj#0., -0.70710678, -0., -0.70710678
d = q*q.conj 
d

References

Großekatthöfer, Karsten, and Zizung Yoon. 2012. “Introduction into Quaternions for Spacecraft Attitude Representation.” TU Berlin 16.