Continous angles in C++ (eq unwrap function in matlab)
Asked Answered
K

4

10

I guess it is not that hard, but I have been stuck on that one for a while.

I have a joint that can rotate both direction. A sensor gives me the angle of the joint in the range -pi and +pi.

I would like to convert it in the range -infinity and +infinity. Meaning that if for example the joint rotate clockwise forever, the angle would start at 0 and then increase to infinity. In matlab, the unwrap function does that very well:

newAngle = unwrap([previousAngle newAngle]);
previousAngle = newAngle;

Note: it is assumed the angle does not make big jump, nothing superior to PI for sure.

Note: I really looked hard before asking ...

Thanks !

Korikorie answered 26/3, 2013 at 10:13 Comment(0)
K
4

After some work, came up with this. Seems to be working fine.

//Normalize to [-180,180):
inline double constrainAngle(double x){
    x = fmod(x + M_PI,M_2PI);
    if (x < 0)
        x += M_2PI;
    return x - M_PI;
}
// convert to [-360,360]
inline double angleConv(double angle){
    return fmod(constrainAngle(angle),M_2PI);
}
inline double angleDiff(double a,double b){
    double dif = fmod(b - a + M_PI,M_2PI);
    if (dif < 0)
        dif += M_2PI;
    return dif - M_PI;
}
inline double unwrap(double previousAngle,double newAngle){
    return previousAngle - angleDiff(newAngle,angleConv(previousAngle));
}

I used code from this post: Dealing with Angle Wrap in c++ code

Korikorie answered 27/3, 2013 at 3:32 Comment(2)
In angleConv() function, angle has been constrained to [-180, 180], so it is obviously in [-360, 360]. I could not see the necessity.Abigael
I actually don't think this returns the desired result. First, the angleConv function maps from [0,2*pi], not [-2*pi,2*pi]. Also in testing this code it doesn't return the correct result for negative anglesEll
S
5

The following function does the job, assuming the absolute difference between the input angles is less than 2*pi:

float unwrap(float previous_angle, float new_angle) {
    float d = new_angle - previous_angle;
    d = d > M_PI ? d - 2 * M_PI : (d < -M_PI ? d + 2 * M_PI : d);
    return previous_angle + d;
}

If you need to unwrap an array, you can use this routine:

void unwrap_array(float *in, float *out, int len) {
    out[0] = in[0];
    for (int i = 1; i < len; i++) {
        float d = in[i] - in[i-1];
        d = d > M_PI ? d - 2 * M_PI : (d < -M_PI ? d + 2 * M_PI : d);
        out[i] = out[i-1] + d;
    }
}
Swig answered 18/3, 2016 at 22:52 Comment(0)
K
4

After some work, came up with this. Seems to be working fine.

//Normalize to [-180,180):
inline double constrainAngle(double x){
    x = fmod(x + M_PI,M_2PI);
    if (x < 0)
        x += M_2PI;
    return x - M_PI;
}
// convert to [-360,360]
inline double angleConv(double angle){
    return fmod(constrainAngle(angle),M_2PI);
}
inline double angleDiff(double a,double b){
    double dif = fmod(b - a + M_PI,M_2PI);
    if (dif < 0)
        dif += M_2PI;
    return dif - M_PI;
}
inline double unwrap(double previousAngle,double newAngle){
    return previousAngle - angleDiff(newAngle,angleConv(previousAngle));
}

I used code from this post: Dealing with Angle Wrap in c++ code

Korikorie answered 27/3, 2013 at 3:32 Comment(2)
In angleConv() function, angle has been constrained to [-180, 180], so it is obviously in [-360, 360]. I could not see the necessity.Abigael
I actually don't think this returns the desired result. First, the angleConv function maps from [0,2*pi], not [-2*pi,2*pi]. Also in testing this code it doesn't return the correct result for negative anglesEll
B
4
// wrap to [-pi,pi]
inline double angle_norm(double x)
{
    x = fmod(x + M_PI, M_2PI);
    if (x < 0)
        x += M_2PI;
    return x - M_PI;
}

double phase_unwrap(double prev, double now)
{
    return prev + angle_norm(now - prev);
}

This works.

Bellringer answered 29/2, 2016 at 11:13 Comment(1)
Since -pi and pi map to the same value, we need to handle those separately. Before the fmod, you can add: if (fabs(x) == M_PI) return x;Stoat
J
0

None worked. Had to implement it.

#include <iostream>
#include <iomanip>
#include <numbers>
#include <vector>
#include <cmath>

// Normalize to [0,2PI):
double phaseNorm(double x)
{
    x = fmod(x, 2*std::numbers::pi);
    if (x < 0)
        x += 2*std::numbers::pi;
    return x;
};

// unwrap phase [-PI,PI]
std::vector<double> phaseUnwrap(std::vector<double> in)
{
    // Normalize to [0,2PI):
    std::transform(in.begin(),in.end(),in.begin(),[&](double d){ return phaseNorm(d); });

    // unwrap iteration
    for(size_t i = 0; i < in.size()-1; ++i)
    {
        int n2PiJump = in[i] / (2*std::numbers::pi);
        in[i+1] += n2PiJump * 2*std::numbers::pi;
        if(in[i]-in[i+1] > std::numbers::pi)
        {
            in[i+1] += 2*std::numbers::pi;
        }
    }
    return in;
}

int main() {

    // create phase vector
    int n = 3;
    std::vector<double> phase;
    for(int h = 0; h < 3; ++h)
    {
        for(int i = n; i > -n; --i)
        {
            phase.push_back(-i*std::numbers::pi/n);
        }
    }

    // print phase vector
    std::cout << std::setw(25) << "Input vector: ";
    for(auto& p : phase)
    {
        std::cout << std::setw(8) << p << " ";
    }
    std::cout << std::endl;

    //  normalize phase vector
    std::cout << std::setw(25) << "Normalized vector: ";
    for(auto& p : phase)
    {
        p = phaseNorm(p);
        std::cout << std::setw(8) << p << " ";
    }
    std::cout << std::endl;

    // unwrap phase vector
    std::cout << std::setw(25) << "Unwraped norm. vector: ";
    std::vector<double> phaseUnwraped = phaseUnwrap(phase);
    for(auto& p : phaseUnwraped)
    {
        std::cout << std::setw(8) << p << " ";
    }    
    std::cout << std::endl;

    return 0;
}

           Input vector: -3.14159  -2.0944  -1.0472        0   1.0472   2.0944 -3.14159  -2.0944  -1.0472        0   1.0472   2.0944 -3.14159  -2.0944  -1.0472        0   1.0472   2.0944 
      Normalized vector:  3.14159  4.18879  5.23599        0   1.0472   2.0944  3.14159  4.18879  5.23599        0   1.0472   2.0944  3.14159  4.18879  5.23599        0   1.0472   2.0944 
  Unwraped norm. vector:  3.14159  4.18879  5.23599  6.28319  7.33038  8.37758  9.42478   10.472  11.5192  12.5664  13.6136  14.6608   15.708  16.7552  17.8024  18.8496  19.8968   20.944

To compile don't forget the c++20 flag -std=c++20 due to std::numbers::pi or use M_PI as included in <math.h>.

Jameljamerson answered 11/3 at 18:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.