0

I obtain sensor data from an AR headset, which outputs the user's head orientation as quaternions. When I inspected the raw data, I saw that there are several significant jumps in the consecutive quaternion samples, especially for qw and qy components as shown below.

enter image description here

Since I want to use some machine learning algorithms on this data, continuity is important. To get rid of the flips, I followed the advice in this answer and flipped the sign of all quaternion components, if qw < 0. This is valid because q and -q denote the same rotation, assuming q is a unit quaternion. With this approach, most of the flips are gone:

enter image description here

However, I noticed that this created another jump for qy at around t=25000 where the magnitude of qy is very close to 1.0. Checking the specific samples where the jump occurs, I converted the quaternions values to Euler angles (yaw, pitch, roll) to get a better understanding:

e1 = [175.84487617, 4.24149047, 170.7215615]
e2 = [175.0441748, -0.47157242, 169.98347392]

It is clear that the angles are very similar except for the zero-crossing in the pitch value which seems to cause the flip in qy. Do I have to live with these discontinuities that occur at the borders of the range or is there a way to make quaternions fully continuous?

chronosynclastic
  • 1,585
  • 3
  • 19
  • 40

2 Answers2

1

Don't flip the signs based on one particular element of the quaternion (e.g., always looking at qw). This is not a good scheme. Instead, flip the signs based on whichever element is the greatest in magnitude at the time. That is, at each step recalculate which quaternion element is the greatest in magnitude and ensure that particular element's sign stays the same across the step. At one time you might be flipping the signs based on qx, and at another time you might be flipping the signs based on qw, etc.

James Tursa
  • 2,242
  • 8
  • 9
  • Thanks but I need some clarification. When I flip the signs, I do it for all elements qx,qy,qw,qz; otherwise I would get a different rotation. What do you mean by "ensure that particular element's sign stays the same across the step"? – chronosynclastic May 12 '22 at 18:48
  • 1
    You ensure that the largest magnitude element of the quaternion maintains a consistent sign from sample to sample. When you flip signs to ensure this, yes you flip the signs of *all* the elements at that time. When you go to the next sample, just make sure you are comparing to the *altered* previous sample, not the original previous sample. – James Tursa May 12 '22 at 18:52
  • Great, this worked perfectly and I could get rid of the sudden jumps! – chronosynclastic May 13 '22 at 13:26
  • @chronosynclastic Would you please share the code solution? thx – StackUnderflow Oct 27 '22 at 22:20
0

I had this issue as well, using an ICM20948 and outputing quaternions from it, and @James Tursa's answer helped me out a lot, thanks. Here's the code I am using, in Python. Signchange variable is 1.

def checkforflipsigns(signchange, currentpacket,previousPacket):

currentpacket = np.array(currentpacket).astype(float)
currentpacket_temp = currentpacket.copy()
previousPacket = np.array(previousPacket).astype(float)  # divide the workingPacket into parts of 4 quaternions 
splitWpacket = np.split(currentpacket,9) # split Wworking Packet
splitPpacket = np.split(previousPacket,9) # Split Previous Packet
splitWPacket_temp = np.split(currentpacket_temp,9) 

for packetnumber, packet in enumerate(splitWpacket): 
  #get abslute values of both packets
  ppacket = splitPpacket[packetnumber] # previouspacket with 4 quats

  wp_abs = np.abs(packet)
  pp_abs = np.abs(ppacket)

  # get indexes of the maximum balue of the absolute values
  index_ofMaxabs_wp = np.argmax(wp_abs )
  index_ofMaxabs_pp = np.argmax(pp_abs )

  if index_ofMaxabs_wp == index_ofMaxabs_pp:  #if absolute maximum remained the same index
    if np.sign(packet[index_ofMaxabs_wp]) !=  np.sign(ppacket[index_ofMaxabs_pp]):
      signchange = -signchange       
    else:  
      signchange = signchange

  splitWPacket_temp[packetnumber] =  packet * signchange  # change the signs of the rest

currentpacket_temp = np.concatenate(splitWPacket_temp) 
return currentpacket_temp

You can see the original signals from the output of the raw quat data and the final output after the flip sign in the figures below. (apologies, in the 2nd figure my qx and qz labels are flipped). Hope this helps

Original raw quat data

Data after signs flipped