Digital Signal Processing (DSP) library

The DSP library included with Falcon contains a number of modular and reusable signal processing algorithms that can be used inside processor nodes.

Detect threshold crossing

Given the value of a single data sample, an instance of the ThresholdCrosser class detects if a threshold has been crossed. It does so by keeping track of the previous sample value and testing if the current and previous data samples are on opposite sides of the threshold. For example, for an upward threshold crossing, a crossing is detected if the previous sample is smaller than or equal to the threshold and the current sample is larger than the threshold.

Operation of the ThresholdCrosser is determined by two parameters: the value of the threshold and the direction of the crossing. These parameters are set in the constructor or using member functions (set_threshold and set_slope). Threshold crossing are detected by repeatedly calling the has_crossed member function with the next data sample.

Here is an example of how to use the ThresholdCrosser class:

// create instance with threshold = 10. and slope = UP
auto t = dsp::algorithms::ThresholdCrosser( 10., Slope::UP );

for (double k=0.5; k<20; ++k) {
    if (t.has_crossed(k)) {
        std::cout << "Threshold crossed!";
    }
}
class ThresholdCrosser

Public Functions

inline ThresholdCrosser(double threshold, Slope slope = Slope::UP)
double threshold() const
void set_threshold(double value)
Slope slope() const
void set_slope(Slope value)
bool has_crossed(double sample)
bool has_crossed_up(double sample)
bool has_crossed_down(double sample)

Compute running statistics of signal

The RunningStatistics class supports the computation of running center and dispersion measures of a signal. It is a virtual base class with currently a single concrete subclass RunningMeanMAD that overrides the virtual method update_statistics to compute the running mean and mean absolute deviation.

The weight of the most recent data sample is set by the alpha parameter, which take a value between 0 and 1. The update_statistics method takes a data sample and alpha value and updates the internal estimates of the center and dispersion measures. For example, the RunningMeanMad class computes the running mean as \((1-alpha) \times mean + alpha \times sample\).

Optionally, a burn-in period can be set during which the alpha value gradually changes from 1. to the preset alpha value. This has the effect that only weight is given to the data samples and not the initial values of the center and dispersion measures.

Also optionally, data samples that are more than a certain distance (in multiples of the z-score) from the center (i.e. outliers) have reduced influence on the computation of the running statistics.

Here is an example of how to use the RunningMeanMAD class:

std::vector<double> samples{0.1, 2.0, 1.5, 3.2, 1.3, 2.4};

// set alpha parameter to 0.1, no burn-in or outlier detection
r = dsp::algorithms::RunningMeanMAD(0.1);
r.add_samples(samples);

std::cout << "running mean = " << r.mean() << " and running MAD = " << r.mad();
class RunningStatistics

Subclassed by dsp::algorithms::RunningMeanMAD

Public Functions

RunningStatistics(double alpha, uint64_t burn_in = 0, bool outlier_protection = false, double outlier_zscore = 3, double outlier_half_life = 1, double center = 0.0, double dispersion = 0.0)
double alpha() const
uint64_t burn_in() const
double center() const
double dispersion() const
bool outlier_protection() const
double outlier_zscore() const
double outlier_half_life() const
bool is_burning_in() const
double zscore(double value) const
void set_center(double value)
void set_dispersion(double value)
void set_alpha(double value)
void set_burn_in(uint64_t value)
void set_outlier_protection(bool value)
void set_outlier_zscore(double value)
void set_outlier_half_life(double value)
void add_sample(double sample)
void add_samples(std::vector<double> samples)
template<typename Iter>
inline void add_samples(Iter begin, Iter end)

Detect local peaks

The PeakDetector class detects local peaks in a signal, gives access to the timestamp and amplitude of the last detected peak and keeps track of the total number of detected peaks.

class PeakDetector

Public Functions

inline PeakDetector(uint64_t init_timestamp = 0, double init_value = 0.0)
void reset(uint64_t init_timestamp = 0, double init_value = 0.0)
double last_peak_amplitude() const
uint64_t last_peak_timestamp() const
bool is_peak(const uint64_t &timestamp, const double &sample)
bool upslope() const
uint64_t npeaks() const

Exponentially smooth signal

The ExponentialSmoother class smooths a signal sample by sample. The integration window for smoothing is determined by the alpha parameter that sets the weight of the new data sample, i.e. \(value = value * (1-alpha) + sample * alpha\). Here is an example:

double smooth_sample;

// create smoother with alpha = 0.1
auto s = dsp::algorithms::ExponentialSmoother(0.1);

std::vector<double> samples{0.1, 2.0, 1.5, 3.2, 1.3, 2.4};

for (auto k : samples) {
    smooth_sample = s.smooth(k);
}
class ExponentialSmoother

Public Functions

ExponentialSmoother(double alpha, double init_value = 0.0)
double smooth(double value)
double alpha() const
void set_alpha(double value)
double value() const
void set_value(double value)

Detect spikes

This algorithm operates sample by sample and looks for upwards deflections in at least one of the channels above a certain threshold.

A spike is detected if the signal of at least one channel crosses the threshold and a local maxima is found in at least one channel (not necessarily the same of that of threshold crossing) within a certain duration (determined by the peak lifetime) The timestamp of the detected spike corresponds to that one of the first sample that crossed the threshold first (independently on whether that sample belongs to the current or previous buffers) In case a proper maximum is found on all channels, the peak values are returned, together with the threshold-crossing timestamp; however, if on one or more channels no peaks were found, the values of the signals at the threshold-crossing sample will be returned.

class SpikeDetector

Public Functions

SpikeDetector(unsigned int nchannels, double threshold, unsigned int peak_life_time)
inline ~SpikeDetector()
void reset()
unsigned int nchannels() const
double threshold() const
void set_threshold(double value)
unsigned int peak_life_time() const
void set_peak_life_time(unsigned int value)
uint64_t timestamp_detected_spike() const
const std::vector<double> &amplitudes_detected_spike() const
template<typename ForwardIterator>
inline bool is_spike(const uint64_t timestamp, const ForwardIterator sample)

Spike detection algorithm:

  • operates sample by sample

  • looks for upwards deflections in at least one of the channels above a certain threshold

  • a spike is detected if the signal of at least one channel crosses the threshold and a local maxima is found in at least one channel (not necessarily the same of that of threshold crossing) within a certain duration (determined by the peak lifetime)

  • the timestamp of the detected spike corresponds to that one of the first sample that crossed the threshold first (independently on whether that sample belongs to the current or previous buffers)

  • in case a proper maximum is found on all channels, the peak values are returned, together with the threshold-crossing timestamp; however, if on one or more channels no peaks were found, the values of the signals at the threshold-crossing sample will be returned.

bool is_spike(const uint64_t timestamp, const std::vector<double> &sample)
uint64_t nspikes() const

Filtering

Finite impulse response (FIR) filters

Infinite impulse response (IIR) filters

namespace filter

Functions

std::map<std::string, std::string> parse_file_header(std::istream &stream)
IFilter *construct_from_file(std::string file)
IFilter *construct_from_yaml(const YAML::Node &node)
class IFilter

Subclassed by dsp::filter::BiquadFilter, dsp::filter::FirFilter

Public Functions

inline IFilter(std::string description)
virtual ~IFilter()
std::string description() const
virtual IFilter *clone() = 0
virtual unsigned int order() const = 0
unsigned int nchannels() const
bool realized() const
void realize(unsigned int nchannels, double init = 0.0)
void unrealize()
virtual double process_channel(double, unsigned int channel) = 0
virtual void process_sample(std::vector<double>&, std::vector<double>&) = 0
virtual void process_sample(std::vector<double>::iterator, std::vector<double>::iterator) = 0
virtual void process_sample(double*, double*) = 0
virtual void process_channel(std::vector<double>&, std::vector<double>&, unsigned int channel = 0) = 0
virtual void process_channel(uint64_t nsamples, std::vector<double>::iterator, std::vector<double>::iterator, unsigned int channel = 0) = 0
virtual void process_channel(uint64_t nsamples, double*, double*, unsigned int channel) = 0
virtual void process_by_channel(std::vector<std::vector<double>>&, std::vector<std::vector<double>>&) = 0
virtual void process_by_sample(std::vector<std::vector<double>>&, std::vector<std::vector<double>>&) = 0
virtual void process_by_channel(uint64_t nsamples, double**, double**) = 0
virtual void process_by_sample(uint64_t nsamples, double**, double**) = 0
virtual void process_by_channel(uint64_t nsamples, std::vector<double>&, std::vector<double>&) = 0
virtual void process_by_sample(uint64_t nsamples, std::vector<double>&, std::vector<double>&) = 0
class FirFilter : public dsp::filter::IFilter

Subclassed by dsp::filter::SlopeFilter

Public Functions

FirFilter(const std::vector<double> &coefficients, std::string description = "")
virtual IFilter *clone()
virtual unsigned int order() const final
std::size_t group_delay() const
virtual double process_channel(double input, unsigned int channel = 0) final
virtual void process_sample(std::vector<double> &input, std::vector<double> &output) final
virtual void process_sample(std::vector<double>::iterator input, std::vector<double>::iterator output) final
virtual void process_sample(double *input, double *output) final
virtual void process_channel(std::vector<double> &input, std::vector<double> &output, unsigned int channel = 0) final
virtual void process_channel(uint64_t nsamples, std::vector<double>::iterator input, std::vector<double>::iterator output, unsigned int channel = 0)
virtual void process_channel(uint64_t nsamples, double *input, double *output, unsigned int channel = 0)
virtual void process_by_channel(std::vector<std::vector<double>> &input, std::vector<std::vector<double>> &output) final
virtual void process_by_sample(std::vector<std::vector<double>> &input, std::vector<std::vector<double>> &output) final
virtual void process_by_channel(uint64_t nsamples, double **input, double **output) final
virtual void process_by_sample(uint64_t nsamples, double **input, double **output) final
virtual void process_by_channel(uint64_t nsamples, std::vector<double> &input, std::vector<double> &output)
virtual void process_by_sample(uint64_t nsamples, std::vector<double> &input, std::vector<double> &output)

Public Static Functions

static FirFilter *FromStream(std::istream &stream, std::string description, bool binary = false)
class SlopeFilter : public dsp::filter::FirFilter

Public Functions

inline SlopeFilter(uint32_t window_size, uint32_t order, uint32_t derivative_order, std::string description = "")
inline virtual IFilter *clone()

Public Static Functions

static SlopeFilter *FromStream(std::istream &stream, std::string description, bool binary)

Public Static Attributes

static constexpr uint16_t DEFAULT_WINDOW_SIZE = 4
static constexpr uint8_t DEFAULT_ORDER = 1
static constexpr uint8_t DEFAULT_DERIVATIVE_ORDER = 1
class BiquadFilter : public dsp::filter::IFilter

Public Functions

BiquadFilter(double gain, std::vector<std::array<double, 6>> &coefficients, std::string description = "")
virtual IFilter *clone()
virtual unsigned int order() const final
virtual double process_channel(double x, unsigned int c = 0) final
inline virtual void process_sample(std::vector<double> &input, std::vector<double> &output) final
virtual void process_sample(std::vector<double>::iterator input, std::vector<double>::iterator output) final
virtual void process_sample(double *input, double *output) final
virtual void process_channel(std::vector<double> &input, std::vector<double> &output, unsigned int channel = 0) final
virtual void process_channel(uint64_t nsamples, std::vector<double>::iterator input, std::vector<double>::iterator output, unsigned int channel = 0) final
virtual void process_channel(uint64_t nsamples, double *input, double *output, unsigned int channel = 0) final
virtual void process_by_channel(std::vector<std::vector<double>> &input, std::vector<std::vector<double>> &output) final
virtual void process_by_sample(std::vector<std::vector<double>> &input, std::vector<std::vector<double>> &output) final
virtual void process_by_channel(uint64_t nsamples, double **input, double **output) final
virtual void process_by_sample(uint64_t nsamples, double **input, double **output) final
virtual void process_by_channel(uint64_t nsamples, std::vector<double> &input, std::vector<double> &output)
virtual void process_by_sample(uint64_t nsamples, std::vector<double> &input, std::vector<double> &output)

Public Static Functions

static BiquadFilter *FromStream(std::istream &stream, std::string description, bool binary)