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.Channel
s), 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.SDFT
— TypeSDFT(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
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.
FourierTools.sdft_previousdft
for the values of the DFT in previous iterations.FourierTools.sdft_previousdata
for the values of the data series used in the most recent iteration.FourierTools.sdft_nextdata
for the next value of the data series after the fragment used in the most recent iteration.
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:
FourierTools.sdft_windowlength
to return the length of the DFT window.FourierTools.sdft_update!
with the implementation of the recursive equation, extracting the information stored in the state with the functions commented above (sdft_previousdft
, etc.) .
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:
FourierTools.sdft_backindices
ifFourierTools.sdft_previousdft
is used.FourierTools.sdft_dataoffsets
ifFourierTools.sdft_previousdata
is used.
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.AbstractSDFT
— Type(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
FourierTools.SDFTIterator
— TypeSDFTIterator(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.
FourierTools.sdft_backindices
— Methodsdft_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.
FourierTools.sdft_dataoffsets
— Methodsdft_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.
FourierTools.sdft_iteration
— Methodsdft_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
.
FourierTools.sdft_nextdata
— Methodsdft_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).
FourierTools.sdft_previousdata
— Functionsdft_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.
FourierTools.sdft_previousdft
— Functionsdft_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
.
FourierTools.sdft_update!
— Functionsdft_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:
FourierTools.sdft_previousdft
to get the DFTs of previous iterations.FourierTools.sdft_previousdata
to get a previous value of the data series.FourierTools.sdft_nextdata
to get the next value of the data series.
FourierTools.sdft_windowlength
— Functionsdft_windowlength(method)
Return the length of the window used by method
.