I decided to look at what I did a couple of years ago with kiss FFT and Nim.
My original use case involved FADC (flash ADC; ADC with a sampling
frequency of 1 GHz
) events from my PhD work, which randomly
contained a lot of noise (probably due to EMI).
The FADC has a cyclic register with 2560 elements. My idea was to calculate the FFT of all events and define simple cuts on the contained frequencies. Noisy events should contain frequencies of the order of ~100 MHz.
In the end I didn’t follow along with this to the end, because I couldn’t figure out how to make this work properly (mainly because I was / still am inexperienced at using FFTs I imagine).
First of all let’s install the nim bindings for kissFFT
:
cd /tmp
git clone https://github.com/m13253/nim-kissfft
Note that these bindings directly compile the shipped kiss FFT version, so it doesn’t rely on any static or dynamic libraries.
To have some data to play around with, I created two CSV
files from
one normal and one noisy FADC event:
- fadc_25357.csv: the noisy file
- fadc_27748.csv: the normal FADC event
Using these we can calculate and plot the events plus their FFT with
the code below. It also showcases a nifty way to create an image
combining multiple plots by making use of ginger
directly.
First import some packages:
import os, complex, sequtils, strformat, strutils
import ginger, ggplotnim
import kissfft/kissfft
import kissfft/binding
We use os
later to just get the first user argument to the program
using paramStr
. All other packages should be rather self
explanatory.
Let’s define the samples and sampling frequency as two global constants:
const
samples = 2560
sampleFreq = 1e9
Before we do anything, we can define a proc to calculate the associated frequencies, which
func toFreq[T: SomeNumber](n_samples: T, freq: float): seq[float] =
## returns the frequencies associated to the samples given number of
## `n_samples` and a sampling frequency `freq`.
result = newSeq[float](n_samples)
for i in 0 ..< n_samples:
result[i] = i.float * freq / n_samples.float
Now let’s forward declare our actual FFT / plotting procedure:
proc fftAndPlot(dfIn: DataFrame, suffix: string)
The input will be a DF created from each CSV file and a suffix indicating which file was read to differentiate the two plots we will create.
Assuming both CSV
files are in the same directory as this file, we
might want to write:
var count = 0
for file in walkDir(paramStr(1)):
echo "Plotting: ", file
case file.kind
of pcFile:
if file.path.endsWith(".csv"):
fftAndPlot(toDf(readCsv(file.path)), file.path.extractFilename)
else: discard
With the above in mind, we can write the actual implementation of
fftAndPlot
. We should probably turn that into a separate FFT and
plotting proc, but well.
proc fftAndPlot(dfIn: DataFrame, suffix: string) =
var
# create an FFT object and a seq for the output sequence
kfft = kissfft.newKissFFT(samples, false)
f_out = newSeq[kissfft.Complex](samples)
# and convert the input data from the DF into a `seq[kissfft.Complex]`
let f_in = dfIn["FADC / mV"].vToSeq.mapIt(
# for some reason gotta first transform to: scalar -> (nim) complex -> kissfft complex
fromNimComplex(complex(re = Scalar(it.toFloat)))
)
# perform transformation
transform(kfft, f_in, f_out)
# seqs for real valued results (r_in == f_in I guess?)
var r_in = newSeq[float](samples)
var r_out = newSeq[float](samples)
for i in 0 ..< f_in.len:
r_in[i] = f_in[i].r
r_out[i] = f_out[i].r
# calculate frequencies from samples and sampling frequency
let frqs = toFreq(samples, sampleFreq)
let time = dfIn["time / ns"].vToSeq
# build the final DF
let df = seqsToDf({ "FADC" : r_in,
"FFT" : r_out,
"time / ns" : time,
"freq / Hz" : frqs})
# create individual plots of FADC
let plt1 = ggcreate(
ggplot(df, aes("time / ns", "FADC")) +
geom_line() +
ggtitle("FADC event: " & $suffix)
)
# and its FFT
let plt2 = ggcreate(
ggplot(df, aes("freq / Hz", "FFT")) +
geom_line() +
# xlim(0, 1e8) +
ggtitle("FFT event: " & $suffix)
)
# combine both into a single viewport to draw as one image
var plt = initViewport(wImg = 640.0, hImg = 480.0 * 2.0)
plt.layout(1, rows = 2)
# embed the finished plots into the the new viewport
plt.embedAt(0, plt1.view)
plt.embedAt(1, plt2.view)
plt.draw(&"fadc_fft_{suffix}.pdf")
With all this the program should produce two plots:
In order to run and compile this program you need ntangle and then:
ntangle nimKissFFT.org
to create the correct Nim file. Then when compiling it we have to pass
the location of the kiss_fft.h
header manually (this can certainly
be done automatically):
nim c --passC:"-I/tmp/kissfft/nim-kissfft/kissfft" nimKissFft.nim
and then we just run it using:
./nimKissFft ./