[ANN] Announcing EasyFFTs.jl

I am pleased to announce a simple little package called EasyFFTs.jl. I think the readme explains why this package exists and how to use it well, and will copy it here:

EasyFFTs

Are you sick and tired of always doing the same preprocessing before you can visualize your fft? Look no further. EasyFFTs aims to automate common preprocessing of fft’s, aimed at visual inspection of the frequency spectrum. The main workhorse of this package is a very simple function `easyfft` that modifies the output of `fft` and `rfft` from FFTW.jl slightly.

This function offers four main benefits to using the FFTW functions directly:

• The output is scaled by default, making the absolute value of the response
correspond directly to the amplitude of the sinusoids that make up the signal.
• Simple and short syntax for getting the associated frequencies
• Frequencies and response are sorted by increasing frequency (if you have ever used fftshift you know what I am talking about)
• `rfft` is automatically called for real element vectors, avoiding
the common newbie mistake of always using `fft`. Benefits are faster computation
and automatically discarding half of the symmetric spectrum. If you want both
sides of the spectrum, see the only other exported function `easymirror`.

Introductory examples

First, we need something to analyze:

``````julia> using EasyFFTs

julia> fs = 100;

julia> duration = 1;

julia> timestamps = range(0, duration, step=1 / fs);

julia> f1 = 5;

julia> A1 = 2;

julia> f2 = 10;

julia> A2 = 3;
``````

We then make a signal consisting of 2 pure sinusoids:

``````julia> s = @. A1 * sin(f1 * 2π * timestamps) + A2 * sin(f2 * 2π * timestamps);
``````

`easyfft` acts much like a normal `fft`

``````julia> easyfft(s)[1:5]
5-element Vector{ComplexF64}:
-9.578394722256253e-17 + 0.0im
0.00042622566734221867 - 0.013698436692159435im
0.001865726219729817 - 0.029952195806767286im
0.005060926454320235 - 0.05407756747203356im
0.013611028457149094 - 0.10883117827942629im
``````

, but the input is scaled by the length of the signal (to get the right magnitudes), and
only the positive-frequency part of the spectrum is calculated for real signals by default. This gives a better visual
resolution when plotting, as you do not use half the space on repeating the signal, and also saves computations.

When the sample frequency is passed as the second argument, you get a named tuple with the frequencies and response:

``````julia> s_fft = easyfft(s, fs);

julia> typeof(s_fft)
NamedTuple{(:freq, :resp), Tuple{AbstractFFTs.Frequencies{Float64}, Vector{ComplexF64}}}
``````

You can directly destructure named tuples into variables if preferred:

``````julia> s_freq, s_resp = easyfft(s, fs);

julia> s_freq == s_fft.freq
true

julia> s_resp == s_fft.resp
true
``````

For demonstration, lets get the indices of 5 highest amplitude components:

``````julia> inds_sorted_by_magnitude = sortperm(abs.(s_resp), rev=true)[1:5]
5-element Vector{Int64}:
11
6
12
10
13
``````

We can then visualize the result as frequency => magnitude pairs:

``````julia> s_freq[inds_sorted_by_magnitude] .=> abs.(s_resp)[inds_sorted_by_magnitude]
5-element Vector{Pair{Float64, Float64}}:
9.900990099009901 => 2.8796413948481443
4.9504950495049505 => 1.9997385273893282
10.89108910891089 => 0.3626753646683912
8.910891089108912 => 0.21717288162592593
11.881188118811881 => 0.18856450061284216
``````

Note that the 9.9 Hz corresponds to a 2.88 amplitude. If the discrete
frequencies lined up perfectly with the actual signal, we would get amplitude 3 at 10 Hz.
This is almost the case at 5 Hz.

You can also supply a keyword argument `f` to pass a function that you
want to apply directly to the response. This can be useful if the phase is
not of interest, and you do not want the extra lines or variables to
extract the response after calculating the `easyfft`:

``````julia> easyfft(s, fs, f=abs).resp == abs.(s_resp)
true
``````

That wraps up the basic usage. And that is all the usage there is, as this is a simple package.

Simple plotting

If you are more visual, here is a little plot, essentially showing the same things. Input:

``````using EasyFFTs
using UnicodePlots
let
fs = 1000
duration = 1
timestamps = range(0, duration, step=1 / fs)

f = 5
A = 2
s = @. A * sin(f * 2π * timestamps)

plt1 = scatterplot(timestamps, s; xlabel="t", ylabel="s(t)", border=:dotted)

s_fft = easyfft(s, fs, f=abs)
plt2 = scatterplot(s_fft.freq, s_fft.resp; xlabel="frequencies", ylabel="amplitude", border=:dotted)
display(plt1)
display(plt2)
end
``````

Output:

``````           ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
2 ⡇⠀⡼⢧⠀⠀⠀⠀⠀⠀⡼⢧⠀⠀⠀⠀⠀⠀⡼⢧⠀⠀⠀⠀⠀⠀⡼⢧⠀⠀⠀⠀⠀⠀⡼⢧⠀⠀⠀⠀⠀⢸
⡇⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⢸
⡇⢰⠁⠈⡇⠀⠀⠀⠀⢰⠁⠈⡇⠀⠀⠀⠀⢰⠁⠈⡇⠀⠀⠀⠀⢰⠁⠈⡇⠀⠀⠀⠀⢰⠁⠈⡇⠀⠀⠀⠀⢸
⡇⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸
⡇⡼⠀⠀⢧⠀⠀⠀⠀⡼⠀⠀⢧⠀⠀⠀⠀⡼⠀⠀⢧⠀⠀⠀⠀⡼⠀⠀⢧⠀⠀⠀⠀⡼⠀⠀⢧⠀⠀⠀⠀⢸
⡇⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⢸
⡇⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⢸
s(t)    ⡇⠧⠤⠤⠼⡦⠤⠤⢤⠧⠤⠤⠼⡦⠤⠤⢤⠧⠤⠤⠼⡦⠤⠤⢤⠧⠤⠤⠼⡦⠤⠤⢤⠧⠤⠤⠼⡦⠤⠤⢤⢸
⡇⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⢸
⡇⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⡇⠀⠀⢸⢸
⡇⠀⠀⠀⠀⢳⠀⠀⡞⠀⠀⠀⠀⢳⠀⠀⡞⠀⠀⠀⠀⢳⠀⠀⡞⠀⠀⠀⠀⢳⠀⠀⡞⠀⠀⠀⠀⢳⠀⠀⡞⢸
⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⡇⢸
⡇⠀⠀⠀⠀⠸⡀⢀⡇⠀⠀⠀⠀⠸⡀⢀⡇⠀⠀⠀⠀⠸⡀⢀⡇⠀⠀⠀⠀⠸⡀⢀⡇⠀⠀⠀⠀⠸⡀⢀⡇⢸
⡇⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⠀⠀⠀⠀⠀⡇⢸⠀⢸
-2 ⡇⠀⠀⠀⠀⠀⢳⡞⠀⠀⠀⠀⠀⠀⢳⡞⠀⠀⠀⠀⠀⠀⢳⡞⠀⠀⠀⠀⠀⠀⢳⡞⠀⠀⠀⠀⠀⠀⢳⡞⠀⢸
⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀t⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
2 ⡇⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
amplitude   ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
0 ⡇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⢸
⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀500⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀frequencies⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
``````
9 Likes

`rfft` is automatically called for real element vectors, avoiding
the common newbie mistake of always using `fft`. Benefits are faster computation
and automtically discarding half of the symmetric spectrum. If you want both
sides of the spectrum, see the only other exported function `easymirror`.

Any ideas about what happens there? Thanks.