Sliding Discrete Fourier Transforms

Computation of Sliding Discrete Fourer Transforms over one-dimensional series of values.

Usage

The basic Sliding Discrete Fourier Transform (SDFT) of a one-dimensional series of values x, using a window of length n, is calculated as follows:

Step 1: Setup the method for an SDFT of length n:

sdft = SDFT(n)

Step 2: Apply the created method to the data series x. This is typically used in a loop:

for spectrum in sdft(x)
    # `spectrum` is a `Vector{Complex(eltype(x))}` of length `n`
end

Considerations for stateful iterators

By default, SDFTs are computed traversing sequentially the data series x, which can be any kind of iterator. In the case of stateful iterators (i.e. those that are modified upon each iteration, like Base.Channels), sdft(method, x) will also be a stateful iterator that will "consume" as many items of x as the length of the computed DFT in the first iteration, and one additional item in every subsequent iteration.

Apart from that consideration, it is safe to apply SDFTs to stateful iterators, since past samples of x already used in previous iterations, which are often required for the computations, are temporarily stored in an array — in internal variables that users do not need to deal with.

Methods for SDFTs

FourierTools.SDFTType
SDFT(n)
SDFT(C, n)

Basic method to compute a Sliding Discrete Fourier Transform of window length n, through the recursive formula:

\[X_{i+1}[k] = e^{j2{\pi}k/n} \cdot (X_{i}[k] + x[i+n] - x[i])\]

The transfer function for the k-th bin of this method is:

\[H(z) = \frac{1 - z^{-n}}{1 - e^{j2{\pi}k/n} z^{-1}}\]

Use SDFT(C, n) to obtain results with the precision of the complex data type C. C == ComplexF64 by default.

SDFT is a subtype of AbstractSDFT. See the documentation of that type for further details about its usage.

References

Jacobsen, E. & Lyons, R. (2003). "The sliding DFT," IEEE Signal Processing Magazine, 20(2), 74-80. doi:10.1109/MSP.2003.1184347

source

Developing new SDFTs

Theoretical basis

An SDFT is generally implemented as a recursive equation, such that if $X_{i}[k]$ is the DFT of $x[j]$ for $j = i, \ldots, i+n$ at frequency $k$, the next iteration is:

\[X_{i+1}[k] = f(k, X_{1}[k], \ldots, X_{i}[k], x[i], \ldots x[i+n], x[i+n+1])\]

Such an equation depends on:

  • The frequency $k$
  • The values of the DFT in one or more previous iterations, $X_{p}[k]$ for $p = 1, \ldots, i$.
  • The values of the data series used in the most recent iteration, $x[j]$ for $j = i, \ldots, i+n$.
  • The next value of the data series after the fragment used in the most recent iteration, $x[i+n+1]$.

For instance, the basic definition of the SDFT uses the following formula:

\[X_{i+1}[k] = W[k] \cdot (X_{i}[k] + x[i+n] - x[i]),\]

which depends only on the most recent DFT ($X_{i}[k]$), the first data point of the fragment used in that DFT ($x[i]$), and the next data point after that fragment ($x[i+n+1]$), plus a "twiddle" factor $W[k]$ that only depends on the frequency $k$, equal to $\exp(j2{\pi}k/n)$. Other variations of the SDFT may use formulas that depend on previous iterations or other values of the data series in that fragment.

Implementation in Julia object types

A method to compute an SDFT is defined by three kinds of object types:

  • One for the method (e.g. sdft = SDFT(n) in the previous example), which contains the fixed parameters that are needed by the algorithm to compute the SDFT.
  • Another for the iterator created by calling the method as a function (sdft(x) in the example), which is thus bound with the target data series.
  • And yet another for the state of the iterator, which holds the information needed by the algorithm that depends on the data series and changes at each iteration.

The internals of this package take care of the design of the iterator and state types, and of the creation of their instances. The only thing that has to be defined to create a new kind of SDFT is the struct of the method with the fixed parameters, and a few function methods dispatching on that type.

Before explaining how to define such a struct, it is convenient to know the functions that can be used to extract the information that is stored in the state of SDFT iterators. There is a function for each one of the three kinds of variables presented in the general recursive equation above.

For instance, the values used in the formula of the basic SDFT may be obtained from a state object as:

  • FourierTools.sdft_previousdft(state, 0) for $X_{i}$.
  • FourierTools.sdft_previousdata(state, 0) for $x[i]$.
  • FourierTools.sdft_nextdata(state) for $x[i+n+1]$.

Notice that the second arguments of sdft_previousdft and sdft_previousdata might have been ommited in this case, since they are zero by default.

For methods that need to know how many steps of the SDFT have been done, this can also be extracted with the function FourierTools.sdft_iteration.

The design of the struct representing a new SDFT type is free, but it is required to be a subtype of AbstractSDFT, and implement the following methods dispatching on that type:

Depending on the functions that are used in the particular implementation of sdft_update! for a given type, the following methods should be defined too:

Example

The formula of the basic SDFT formula could be implemented for a type MyBasicSDFT as follows:

import FourierTools: sdft_update!, sdft_windowlength, sdft_nextdata, sdft_previousdata

function sdft_udpatedft!(dft, x, method::MyBasicSDFT, state)
    n = sdft_windowlength(method)
    for k in eachindex(dft)
        X_i = dft[k]
        x_iplusn = sdft_nextdata(state)
        x_i = sdft_previousdata(state)
        Wk = exp(2π*im*k/n)
        dft[k] = Wk * (X_i + x_iplusn - x_i)
    end
end

(The type SDFT actually has as a similar, but not identical definition.)

The implementation of sdft_update! given in the previous example does use sdft_previousdata - with the default offset value, equal to zero - so the following is required in this case:

FourierTools.sdft_dataoffsets(::MyBasicSDFT) = 0

On the other hand there is no need to define FourierTools.sdft_backindices in this case, since the interface of of sdft_update! assumes that the most recent DFT is already contained in its first argument dft, so it is not necessary to use the function sdft_previousdft to get it.

Alternative to subtyping AbstractSDFT

Types that represent SDFT methods are required to be subtypes of AbstractSDFT in order to make them callable and return an iterator of the SDFT (i.e. objects of the type FourierTools.SDFTIterator). If for some reason that subtyping is not desired or possible (e.g. if the said type already has another supertype), the same behavior can be obtained by defining them explicitly as functors, in the following fashion:

(m::MyBasicSDFT)(args...) = FourierTools.SDFTIterator(args...)

SDFT development API

FourierTools.AbstractSDFTType
(m::AbstractSDFT)(x[, safe=true])

AbstractSDFT is an abstract type for callable structs that implement Sliding Discrete Fourier Transforms.

If typeof(m) <: AbstractSDFT, then m(x) will return an iterator that produces a sliding DFT of x according to the method m.

By default that iterator produces a new vector at each iteration. Set the optional argument safe=false to improve performance by reducing allocations, at the expense of unexpected behavior if the resulting vector is mutated between iterations.

Example

sdft = SDFT(10) # basic [`SDFT`](@ref) with a window of length 10

for dft in sdft(x)
    # here `dft` is a vector with 10 complex values
end

Available methods:

See some methods for sliding DFTs at: https://nanoimaging.de/FourierTools.jl/stable/slidingdft/#Methods-for-SDFTs

source
FourierTools.SDFTIteratorType
SDFTIterator(method, x[, safe=true])

Return an iterator to produce a sliding DFT of x using the given method. If safe == true (the default value), this iterator produces a new vector at each iteration; otherwise the vector produced in the first iteration can be reused in subsequent iterations.

source
FourierTools.sdft_backindicesMethod
sdft_backindices(method)

Return an integer or a vector of positive integers with the indices of the previous iterations that are needed by the given method to compute a sliding DFT.

If the code of FourierTools.updatepdf! for the type of method uses the function FourierTools.sdft_previousdft, this function must return the integers that are used as the third argument (back) of that function.

If that function is not needed, this one may return nothing to reduce memory allocations.

source
FourierTools.sdft_dataoffsetsMethod
sdft_dataoffsets(method)

Return an integer or a vector of integers with the offsets of data samples that are needed by the given method to compute a sliding DFT.

If the code of FourierTools.updatepdf! that dispatches on the type of method uses the function FourierTools.sdft_previousdata, this function must return the integers that are used as the third argument (offset) of that function.

If that function is not needed (no past samples are used), this one may return nothing to reduce memory allocations.

source
FourierTools.sdft_iterationMethod
sdft_iteration(state)

Return the number of iterations done for the sliding DFT represented by state.

If the DFT computed in the most recent iteration corresponds to the fragment of the data series between its positions i and i+n, then this function returns the number i.

source
FourierTools.sdft_nextdataMethod
sdft_nextdata(state)

Return the next value after the fragment of the data series that was used in the most recent iteration of the sliding DFT represented by state.

If the DFT computed in the most recent iteration corresponds to the fragment of the data series between its positions i and i+n, then this function returns the i+n+1-th value.

There is no defined behavior if such value does not exist (i.e. if the end of a finite data series was reached).

source
FourierTools.sdft_previousdataFunction
sdft_previousdata(state[, offset=0])

Return the first value of the fragment of the data series that was used in the most recent iteration of the sliding DFT represented by state, or at offset positions after the beginning of that fragment.

If the DFT computed in the most recent iteration corresponds to the fragment of the data series between its positions i and i+n, then this function returns the i+offset-th value.

source
FourierTools.sdft_previousdftFunction
sdft_previousdft(state[, back=0])

Return the DFT computed in the most recent iteration of the sliding DFT represented by state, or in a number of previous iterations equal to back.

If the DFT computed in the most recent iteration corresponds to the fragment of the data series between its positions i and i+n, then this function returns its DFT for the fragment between i-back and i+n-back.

source
FourierTools.sdft_update!Function
sdft_update!(dft, x, method, state)

Update the values of a sliding Discrete Fourier Transform (DFT) of a data series, according to the algorithm of the provided method, for a given state of the sliding DFT.

dft is a mutable collection of complex values with length equal to sdft_windowlength(method), containing the value returned by the last iteration of the sliding DFT.

x is the data series for which the sliding DFT is computed, at least as long as sdft_windowlength(method).

method is the object that defines the method to compute the sliding DFT.

state is an object generated automatically at each iteration of an SDFT iterator made from method and x, (an object of the type FourierTools.SDFTIterator).

The information that is needed to update the sliding DFT can be extracted from state with the following functions:

source