Skip to content

Commit

Permalink
write filters section (wpilibsuite#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oblarg authored and sciencewhiz committed Jan 19, 2020
1 parent 3bb40ee commit aad0580
Show file tree
Hide file tree
Showing 11 changed files with 3,933 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions source/docs/software/advanced-control/filters/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Filters
=======

.. note:: The data used to generate the various demonstration plots in this section can be found :download:`here <resources/filterdemo.csv>`.

This section describes a number of filters included with WPILib that are useful for noise reduction and/or input smoothing.

.. toctree::
:maxdepth: 1

introduction
linear-filter
median-filter
slew-rate-limiter
36 changes: 36 additions & 0 deletions source/docs/software/advanced-control/filters/introduction.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Introduction to Filters
=======================

Filters are some of the most common tools used in modern technology, and find numerous applications in robotics in both signal processing and controls. Understanding the notion of a filter is crucial to understanding the utility of the various types of filters provided by WPILib.

What Is a Filter?
-----------------

.. note:: For the sake of this article, we will assume all data are single-dimensional time-series data. Obviously, the concepts involved are more general than this - but a full/rigorous discussion of signals and filtering is out of the scope of this documentation.

So, what exactly *is* a filter, then? Simply put, a filter is a mapping from a stream of inputs to a stream of outputs. That is to say, the value output by a filter (in principle) can depend not only on the *current* value of the input, but on *the entire set of past and future values* (of course, in practice, the filters provided by WPILib are implementable in real-time on streaming data; accordingly, they can only depend on the *past* values of the input, and not on future values). This is an important concept, because generally we use filters to remove/mitigate unwanted *dynamics* from a signal. When we filter a signal, we're interested in modifying *how the signal changes over time*.

Effects of Using a Filter
-------------------------

Noise Reduction
^^^^^^^^^^^^^^^

One of the most typical uses of a filter is for noise reduction. A filter that reduces noise is called a *low-pass* filter (because it allows low frequencies to "pass through," while blocking high-frequencies). Most of the filters currently included in WPILib are effectively low-pass filters.

Rate Limiting
^^^^^^^^^^^^^

Filters are also commonly used to reduce the rate at which a signal can change. This is closely related to noise reduction, and filters that reduce noise also tend to limit the rate of change of their output.

Edge Detection
^^^^^^^^^^^^^^

The counterpart to the low-pass filter is the high-pass filter, which only permits high frequencies to pass through to the output. High-pass filters can be somewhat tricky to build intuition for, but a common usage for a high-pass filter is edge-detection - since high-pass filters will reflect sudden changes in the input while ignoring slower changes, they are useful for determining the location of sharp discontinuities in the signal.

Phase Lag
^^^^^^^^^

An unavoidable negative effect of a real-time low-pass filter is the introduction of "phase lag." Since, as mentioned earlier, a real-time filter can only depend on past values of the signal (we cannot time-travel to obtain the future values), the filtered value takes some time to "catch up" when the input starts changing. The greater the noise-reduction, the greater the introduced delay. This is, in many ways, *the* fundamental trade-off of real-time filtering, and should be the primary driving factor of your filter design.

Interestingly, high-pass filters introduce a phase *lead*, as opposed to a phase lag, as they exacerbate local changes to the value of the input.
119 changes: 119 additions & 0 deletions source/docs/software/advanced-control/filters/linear-filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
Linear Filters
==============

The first (and most commonly-employed) sort of filter that WPILib supports is a *linear filter* - or, more specifically, a linear time-invariant (LTI) filter.

An LTI filter is, put simply, a weighted moving average - the value of the output stream at any given time is a localized, weighted average of the inputs near that time. The difference between different types of LTI filters is thus reduceable to the difference in the choice of the weighting function (also known as a "window function" or an "impulse response") used. Mathematically, this kind of moving average is known as a `convolution <https://en.wikipedia.org/wiki/Convolution>`__.

There are two broad "sorts" of impulse responses: infinite impulse responses (IIR), and finite impulse responses (FIR).

Infinite impulse responses have infinite "support" - that is, they are nonzero over an infinitely-large region. This means, broadly, that they also have infinite "memory" - once a value appears in the input stream, it will influence *all subsequent outputs, forever*. This is typically undesirable from a strict signal-processing perspective, however filters with infinite impulse responses tend to be very easy to compute as they can be expressed by simple recursion relations.

Finite impulse responses have finite "support" - that is, they are nonzero on a bounded region. The "archetypical" FIR filter is a flat moving average - that is, simply setting the output equal to the average of the past n inputs. FIR filters tend to have more-desirable properties than IIR filters, but are more costly to compute.

Linear filters are supported in WPILib through the ``LinearFilter`` class (`Java <https://first.wpi.edu/FRC/roborio/release/docs/java/edu/wpi/first/wpilibj/LinearFilter.html>`__, `C++ <https://first.wpi.edu/FRC/roborio/release/docs/cpp/classfrc_1_1LinearFilter.html>`__).

Creating a LinearFilter
-----------------------

.. note:: The C++ ``LinearFilter`` class is templated on the data type used for the input.

.. note:: Because filters have "memory", each input stream requires its own filter object. Do *not* attempt to use the same filter object for multiple input streams.

While it is possible to directly instantiate ``LinearFilter`` class to build a custom filter, it is far more convenient (and common) to use one of the supplied factory methods, instead:

singlePoleIIR
^^^^^^^^^^^^^

.. image:: images/singlepolefilter.png

The ``singlePoleIIR()`` factory method creates a single-pole infinite impulse response filter (also known as `exponential smoothing <https://en.wikipedia.org/wiki/Exponential_smoothing>`__, on account of having an exponential impulse response). This is the "go-to," "first-try" low-pass filter in most applications; it is computationally trivial and works in most cases.

.. tabs::

.. code-tab:: java

// Creates a new Single-Pole IIR filter
// Time constant is 0.1 seconds
// Period is 0.02 seconds - this is the standard FRC main loop period
LinearFilter filter = LinearFilter.singlePoleIIR(0.1, 0.02);

.. code-tab:: c++

// Creates a new Single-Pole IIR filter
// Time constant is 0.1 seconds
// Period is 0.02 seconds - this is the standard FRC main loop period
frc::LinearFilter<double> filter = frc::LinearFilter<double>::SinglePoleIIR(0.1_s, 0.02_s);

The "time constant" parameter determines the "characteristic timescale" of the filter's impulse response; the filter will cancel out any signal dynamics that occur on timescales significantly shorter than this. Relatedly, it is also the approximate timescale of the introduced :ref:`phase lag <docs/software/advanced-control/filters/introduction:Phase Lag>`. The reciprocal of this timescale, multiplied by 2 pi, is the "cutoff frequency" of the filter.

The "period" parameter is the period at which the filter's `calculate()` method will be called. For the vast majority of implementations, this will be the standard main robot loop period of 0.02 seconds.

movingAverage
^^^^^^^^^^^^^

.. image:: images/firfilter.png

The ``movingAverage`` factory method creates a simple flat moving average filter. This is the simplest possible low-pass FIR filter, and is useful in many of the same contexts as the single-pole IIR filter. It is somewhat more costly to compute, but generally behaves in a somewhat nicer manner.

.. tabs::

.. code-tab:: java

// Creates a new flat moving average filter
// Average will be taken over the last 5 samples
LinearFilter filter = LinearFilter.movingAverage(5);

.. code-tab:: c++

// Creates a new flat moving average filter
// Average will be taken over the last 5 samples
frc::LinearFilter<double> filter = frc::LinearFilter<double>::MovingAverage(5);

The "taps" parameter is the number of samples that will be included in the flat moving average. This behaves similarly to the "time constant" above - the effective time constant is the number of taps times the period at which ``calculate()`` is called.

highPass
^^^^^^^^

.. image:: images/highpassfilter.png

The ``highPass`` factory method creates a simple first-order infinite impulse response high-pass filter. This is the "counterpart" to the `singlePoleIIR`_.

.. tabs::

.. code-tab:: java

// Creates a new high-pass IIR filter
// Time constant is 0.1 seconds
// Period is 0.02 seconds - this is the standard FRC main loop period
LinearFilter filter = LinearFilter.highPass(0.1, 0.02);

.. code-tab:: c++

// Creates a new high-pass IIR filter
// Time constant is 0.1 seconds
// Period is 0.02 seconds - this is the standard FRC main loop period
frc::LinearFilter<double> filter = frc::LinearFilter<double>::HighPass(0.1_s, 0.02_s);

The "time constant" parameter determines the "characteristic timescale" of the filter's impulse response; the filter will cancel out any signal dynamics that occur on timescales significantly longer than this. Relatedly, it is also the approximate timescale of the introduced :ref:`phase lead <docs/software/advanced-control/filters/introduction:Phase lag>`. The reciprocal of this timescale, multiplied by 2 pi, is the "cutoff frequency" of the filter.

The "period" parameter is the period at which the filter's `calculate()` method will be called. For the vast majority of implementations, this will be the standard main robot loop period of 0.02 seconds.

Using a LinearFilter
--------------------

.. note:: In order for the created filter to obey the specified timescale parameter, its ``calculate()`` function *must* be called regularly at the specified period. If, for some reason, a significant lapse in ``calculate()`` calls must occur, the filter's ``reset()`` method should be called before further use.

Once your filter has been created, using it is easy - simply call the ``calculate()`` method with the most recent input to obtain the filtered output:

.. tabs::

.. code-tab:: java

// Calculates the next value of the output
filter.calculate(input);

.. code-tab:: c++

// Calculates the next value of the output
filter.Calculate(input);
48 changes: 48 additions & 0 deletions source/docs/software/advanced-control/filters/median-filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Median Filter
=============

.. image:: images/medianfilter.png

A `robust <https://en.wikipedia.org/wiki/Robust_statistics>`__ alternative to the :ref:`moving-average filter <docs/software/advanced-control/filters/linear-filter:movingAverage>` is the *median filter*. Where a moving average filter takes the arithmetic *mean* of the input over a moving sample window, a median filter (per the name) takes a median instead.

The median filter is most-useful for removing occasional outliers from an input stream. This makes it particularly well-suited to filtering inputs from distance sensors, which are prone to occasional interference. Unlike a moving average, the median filter will remain completely unaffected by small numbers of outliers, no matter how extreme.

The median filter is supported in WPILib through the ``MedianFilter`` class (`Java <https://first.wpi.edu/FRC/roborio/release/docs/java/edu/wpi/first/wpilibj/MedianFilter.html>`__, `C++ <https://first.wpi.edu/FRC/roborio/release/docs/cpp/classfrc_1_1MedianFilter.html>`__).

Creating a MedianFilter
-----------------------

.. note:: The C++ ``MedianFilter`` class is templated on the data type used for the input.

.. note:: Because filters have "memory", each input stream requires its own filter object. Do *not* attempt to use the same filter object for multiple input streams.

Creating a ``MedianFilter`` is simple:

.. tabs::

.. code-tab:: java

// Creates a MedianFilter with a window size of 5 samples
MedianFilter filter = new MedianFilter(5);

.. code-tab:: c++

// Creates a MedianFilter with a window size of 5 samples
frc::MedianFilter<double> filter(5);

Using a MedianFilter
--------------------

Once your filter has been created, using it is easy - simply call the ``calculate()`` method with the most recent input to obtain the filtered output:

.. tabs::

.. code-tab:: java

// Calculates the next value of the output
filter.calculate(input);

.. code-tab:: c++

// Calculates the next value of the output
filter.Calculate(input);
Loading

0 comments on commit aad0580

Please sign in to comment.