added butterworth filter \n added activity rec for baro using butter \n added moduel for walker with activity
This commit is contained in:
312
math/filter/Butterworth.h
Normal file
312
math/filter/Butterworth.h
Normal file
@@ -0,0 +1,312 @@
|
||||
#ifndef BUTTERWORTHLP_H
|
||||
#define BUTTERWORTHLP_H
|
||||
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
namespace Filter {
|
||||
|
||||
/** butterworth lowpass filter*/
|
||||
template <typename Scalar> class ButterworthLP {
|
||||
|
||||
private:
|
||||
|
||||
/** Cascaded second-order sections */
|
||||
class SOS{
|
||||
|
||||
public:
|
||||
|
||||
SOS(const Scalar b0, const Scalar b1, const Scalar b2, const Scalar a1, const Scalar a2, const Scalar gain) :
|
||||
_b0(b0), _b1(b1), _b2(b2), _a1(a1), _a2(a2), _gain(gain), _z1(0), _z2(0),
|
||||
_preCompStateSpaceOutputVec1(_b1 - _b0*_a1),
|
||||
_preCompStateSpaceOutputVec2(_b2 - _b0*_a2){
|
||||
|
||||
}
|
||||
|
||||
void safeRestoreState(Scalar &z1, Scalar &z2, const bool restore){
|
||||
|
||||
if (restore)
|
||||
{
|
||||
_z1 = z1;
|
||||
_z2 = z2;
|
||||
}
|
||||
else
|
||||
{
|
||||
z1 = _z1;
|
||||
z2 = _z2;
|
||||
}
|
||||
}
|
||||
|
||||
void stepInitialization(const Scalar value){
|
||||
|
||||
// Set second-order section state to steady state w.r.t. given step response value
|
||||
// http://www.emt.tugraz.at/publications/diplomarbeiten/da_hoebenreich/node21.html
|
||||
_z1 = _z2 = value / (1.0 + _a1 + _a2);
|
||||
}
|
||||
|
||||
Scalar process(const Scalar input){
|
||||
|
||||
/**
|
||||
SOS state space model
|
||||
z' = A * z + b * x
|
||||
y = c^T * z + d * x
|
||||
with A = [-a1 -a2; 1 0] b = [1; 0] c = [b1 - b0*a1; b2 - b0*a2] d = [b0]
|
||||
*/
|
||||
|
||||
Scalar x = input;
|
||||
Scalar y = x;
|
||||
Scalar z1_new, z2_new;
|
||||
|
||||
z1_new = -_a1 * _z1 - _a2 * _z2 + x;
|
||||
z2_new = _z1;
|
||||
y = _preCompStateSpaceOutputVec1 * _z1 + _preCompStateSpaceOutputVec2 * _z2 + _b0 * x;
|
||||
_z1 = z1_new;
|
||||
_z2 = z2_new;
|
||||
|
||||
// Include SOS gain factor
|
||||
y *= _gain;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
~SOS()
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
const Scalar _b0, _b1, _b2, _a1, _a2, _gain;
|
||||
|
||||
const Scalar _preCompStateSpaceOutputVec1, _preCompStateSpaceOutputVec2;
|
||||
|
||||
Scalar _z1, _z2;
|
||||
|
||||
};
|
||||
|
||||
|
||||
size_t _numSOS;
|
||||
std::vector<SOS> _sosVec;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
/**
|
||||
Generates a Butterworth lowpass filter with a given normalized cutoff frequency and filter order.
|
||||
@param normalizedCutoffFrequency (0, 1) Normalized cutoff frequency := cuttoffFreq / samplingFreq.
|
||||
@param filterOrder [1, inf] Butterworth filter order.
|
||||
*/
|
||||
ButterworthLP(const Scalar normalizedCutoffFrequency, const size_t filterOrder) :
|
||||
_numSOS(0)
|
||||
{
|
||||
if (!coefficients(normalizedCutoffFrequency, filterOrder))
|
||||
{
|
||||
throw std::domain_error(std::string("Failed to design a filter due to invalid parameters (normalized cutoff frequency and / or filter order) or instability of the resulting digitalized filter."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Generates a Butterworth lowpass filter of a given order with a given cutoff frequency [Hz] in respect of the data sampling frequency [Hz].
|
||||
@param samplingFrequency [1, inf] [Hz] Sampling frequency of the data.
|
||||
@param cutoffFrequency [1, samplingFrequency - 1] [Hz] Cutoff frequency
|
||||
@param filterOrder [1, inf] Butterworth filter order.
|
||||
*/
|
||||
ButterworthLP(const Scalar samplingFrequency, const Scalar cutoffFrequency, const size_t filterOrder) :
|
||||
ButterworthLP(cutoffFrequency / samplingFrequency, filterOrder)
|
||||
{ }
|
||||
|
||||
/**
|
||||
Set the filter state to a steady state w.r.t. to the given input value (assuming a constant filter input for infinite time steps in the past;
|
||||
see also http://www.emt.tugraz.at/publications/diplomarbeiten/da_hoebenreich/node21.html).
|
||||
@param value Desired steady state output value
|
||||
*/
|
||||
void stepInitialization(const Scalar value){
|
||||
Scalar stepResponseValue = value;
|
||||
|
||||
// Propagate step initialization through all second-order sections
|
||||
for (size_t i = 0; i < _numSOS; ++i)
|
||||
{
|
||||
_sosVec[i].stepInitialization(stepResponseValue);
|
||||
stepResponseValue = _sosVec[i].process(stepResponseValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Processes the input value online depending on the current filter state.
|
||||
@param input [-inf, inf] Input value
|
||||
@return [-inf, inf] Filter response
|
||||
*/
|
||||
Scalar process(const Scalar input){
|
||||
Scalar x = input;
|
||||
Scalar y = x;
|
||||
|
||||
// Cascade all second-order sections s.t. output of SOS i is input for SOS i+1
|
||||
for (size_t i = 0; i < _numSOS; ++i)
|
||||
{
|
||||
y = _sosVec[i].process(x);
|
||||
x = y;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Processes the input data offline.
|
||||
@param *input Ptr to input data.
|
||||
@param *output Ptr to output data.
|
||||
@param size [0, inf] Length of data.
|
||||
@param initialConditionValue [-inf, inf] Initializes the filter state to a steady state w.r.t. the given initial value (see stepInitialization).
|
||||
@param forwardBackward {true, false} Eliminate phase delay by filtering twice: forward and backward.
|
||||
Note that forward-backward filtering corresponds to filtering with a 2n-th order filter.
|
||||
*/
|
||||
void filter(const Scalar *input, Scalar *output, const size_t size, const Scalar initialConditionValue, const bool forwardBackward){
|
||||
|
||||
// Save all current SOS states before offline filtering
|
||||
std::vector<Scalar> zSaved(_numSOS * 2);
|
||||
for (size_t i = 0; i < _numSOS; ++i)
|
||||
{
|
||||
_sosVec[i].safeRestoreState(zSaved[i], zSaved[i + 1], false);
|
||||
}
|
||||
|
||||
// Set initial step response conditions
|
||||
stepInitialization(initialConditionValue);
|
||||
|
||||
// Filtering on input data
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
output[i] = process(input[i]);
|
||||
}
|
||||
|
||||
// Additional backward filtering on filtered output data if requested
|
||||
if (forwardBackward)
|
||||
{
|
||||
for (size_t i = size; i > 0;)
|
||||
{
|
||||
output[--i] = process(output[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore all SOS states
|
||||
for (size_t i = 0; i < _numSOS; ++i)
|
||||
{
|
||||
_sosVec[i].safeRestoreState(zSaved[i], zSaved[i + 1], true);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool coefficients(const Scalar normalizedCutoffFrequency, const size_t filterOrder){
|
||||
|
||||
// Assure valid parameters
|
||||
if (filterOrder < 1 || normalizedCutoffFrequency <= 0 || normalizedCutoffFrequency > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::complex<Scalar>> poles(filterOrder);
|
||||
|
||||
// Prewarp the analog prototype's cutoff frequency
|
||||
Scalar omegaCutoff = 2 * tan(M_PI * normalizedCutoffFrequency);
|
||||
|
||||
Scalar gain = pow(omegaCutoff, filterOrder);
|
||||
Scalar initialGain = gain;
|
||||
|
||||
std::complex<Scalar> two(2.0, 0);
|
||||
|
||||
for (size_t i = 0, i_end = (filterOrder + 1) / 2; i < i_end; ++i){
|
||||
|
||||
size_t i2 = 2 * i;
|
||||
|
||||
/**
|
||||
Design the analog prototype Butterworth lowpass filter
|
||||
*/
|
||||
|
||||
// Generate s-poles of prototype filter
|
||||
Scalar phi = static_cast<Scalar>(i2 + 1) * M_PI / (2 * filterOrder);
|
||||
Scalar real = -sin(phi);
|
||||
Scalar imag = cos(phi);
|
||||
|
||||
std::complex<Scalar> pole = std::complex<Scalar>(real, imag);
|
||||
|
||||
/**
|
||||
Customize analog prototype filter w.r.t cutoff frequency
|
||||
*/
|
||||
|
||||
// Scale s-pole with the cutoff frequency
|
||||
pole *= omegaCutoff;
|
||||
|
||||
/**
|
||||
Digitalize the analog filter
|
||||
*/
|
||||
|
||||
// Map pole from s-plane to z-plane using bilinear transform
|
||||
std::complex<Scalar> s = pole;
|
||||
pole = (two + s) / (two - s);
|
||||
|
||||
// Update overall gain in respect of z-pole gain
|
||||
gain *= abs((two - s));
|
||||
|
||||
// Ensure z-pole lies in unit circle of z-plane
|
||||
if (abs(pole) > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add stable z-pole
|
||||
poles[i2] = pole;
|
||||
|
||||
// Odd filter order: ignore the second complex conjugate pole
|
||||
if (i2 + 1 >= filterOrder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Do the same as above with the conjugate complex pole
|
||||
pole = std::complex<Scalar>(real, -imag);
|
||||
pole *= omegaCutoff;
|
||||
s = pole;
|
||||
pole = (two + s) / (two - s);
|
||||
gain *= abs((two - s));
|
||||
if (abs(pole) > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
poles[i2 + 1] = pole;
|
||||
}
|
||||
|
||||
// Distribute the overall gain over all z-poles
|
||||
Scalar overallGain = initialGain * (initialGain / gain);
|
||||
Scalar distributedPoleGain = pow(overallGain, 1.0 / static_cast<Scalar>(filterOrder));
|
||||
Scalar distributedPolePairGain = distributedPoleGain * distributedPoleGain;
|
||||
|
||||
/**
|
||||
Generate second-order sections from conjugate complex z-pole pairs
|
||||
*/
|
||||
|
||||
for (size_t i = 0, i_end = filterOrder - 1; i < i_end; i += 2){
|
||||
|
||||
addSOS(SOS(1.0, 2.0, 1.0, -(poles[i] + poles[i + 1]).real(), (poles[i] * poles[i + 1]).real(), distributedPolePairGain));
|
||||
}
|
||||
|
||||
// Odd filter order: remaining single z-pole requires additional second-order section
|
||||
if (filterOrder % 2 == 1){
|
||||
|
||||
addSOS(SOS(1.0, 1.0, 0.0, -poles[filterOrder - 1].real(), 0.0, distributedPoleGain));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void addSOS(const SOS sos){
|
||||
|
||||
_sosVec.push_back(sos);
|
||||
++_numSOS;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif // BUTTERWORTHLP_H
|
||||
Reference in New Issue
Block a user