using System;
///
/// One Euro Filter implementation for smoothing eye tracking data
/// Based on the paper "1€ Filter" by Géry Casiez et al.
///
/// The one euro filter is an adaptive low-pass filter that provides smooth filtering
/// while maintaining responsiveness. It smooths more when the signal is stable and
/// less when there's rapid movement.
///
public class OneEuroFilter
{
private float _minCutoff;
private float _beta;
private float _dCutoff;
private float _lastTime;
private float _lastValue;
private float _lastDelta;
private bool _firstTime;
///
/// Initializes a new instance of the OneEuroFilter class
///
/// Minimum cutoff frequency for the low-pass filter (default: 1.0 Hz)
/// Speed coefficient for the adaptive cutoff (default: 0.0)
/// Cutoff frequency for the derivative low-pass filter (default: 1.0 Hz)
public OneEuroFilter(float minCutoff = 1.0f, float beta = 0.0f, float dCutoff = 1.0f)
{
_minCutoff = minCutoff;
_beta = beta;
_dCutoff = dCutoff;
_firstTime = true;
}
///
/// Filters a value using the one euro filter algorithm
///
/// The input value to filter
/// The current time in seconds
/// The filtered value
public float Filter(float value, float time)
{
if (_firstTime)
{
_lastTime = time;
_lastValue = value;
_lastDelta = 0.0f;
_firstTime = false;
return value;
}
// Compute the filtered derivative
float delta = (value - _lastValue) / (time - _lastTime);
float filteredDelta = Alpha(_dCutoff, time - _lastTime) * delta + (1.0f - Alpha(_dCutoff, time - _lastTime)) * _lastDelta;
// Use it to update the speed coefficient
float cutoff = _minCutoff + _beta * Math.Abs(filteredDelta);
// Filter the value
float filteredValue = Alpha(cutoff, time - _lastTime) * value + (1.0f - Alpha(cutoff, time - _lastTime)) * _lastValue;
// Update state
_lastTime = time;
_lastValue = filteredValue;
_lastDelta = filteredDelta;
return filteredValue;
}
///
/// Computes the alpha parameter for the low-pass filter
///
/// The cutoff frequency
/// The time delta
/// The alpha value
private float Alpha(float cutoff, float deltaTime)
{
float tau = 1.0f / (2.0f * (float)Math.PI * cutoff);
return 1.0f / (1.0f + tau / deltaTime);
}
///
/// Resets the filter state
///
public void Reset()
{
_firstTime = true;
}
///
/// Gets or sets the minimum cutoff frequency
///
public float MinCutoff
{
get => _minCutoff;
set => _minCutoff = value;
}
///
/// Gets or sets the speed coefficient
///
public float Beta
{
get => _beta;
set => _beta = value;
}
///
/// Gets or sets the derivative cutoff frequency
///
public float DCutoff
{
get => _dCutoff;
set => _dCutoff = value;
}
}