eFFT
An efficient method for the calculation of the exact Fourier transform of an asynchronous event stream
 
Loading...
Searching...
No Matches
efft.hpp
Go to the documentation of this file.
1
9#ifndef EFFT_HPP
10#define EFFT_HPP
11
12#include <Eigen/Core>
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include <complex>
17#include <cstddef>
18#include <new>
19#include <ostream>
20#include <stdint.h>
21#include <unordered_map>
22#include <utility>
23#include <vector>
24
25#ifdef EFFT_USE_FFTW3
26#include <fftw3.h>
27#include <set>
28#endif
29
30#if EIGEN_MAJOR_VERSION >= 5
31#define EIGEN_LAST Eigen::placeholders::last
32#else
33#define EIGEN_LAST Eigen::last
34#endif
35
36class Stimulus {
37public:
38 unsigned int row{0};
39 unsigned int col{0};
40 bool state{true};
41 Stimulus() = default;
42 Stimulus(const unsigned int row, const unsigned int col) : row{row}, col{col} {};
43 Stimulus(const unsigned int row, const unsigned int col, const bool state) : row{row}, col{col}, state{state} {};
44 bool operator==(const Stimulus &p) const { return (row == p.row && col == p.col); }
45 bool operator!=(const Stimulus &p) const { return (row != p.row || col != p.col); }
46 Stimulus &on() {
47 state = true;
48 return *this;
49 }
50 Stimulus &off() {
51 state = false;
52 return *this;
53 }
54 Stimulus &set(const bool s) {
55 state = s;
56 return *this;
57 }
58 Stimulus &toggle() {
59 state = !state;
60 return *this;
61 }
62 friend std::ostream &operator<<(std::ostream &os, const Stimulus &stimulus) {
63 os << "Stimulus(row: " << stimulus.row << ", col: " << stimulus.col << ", state: " << (stimulus.state ? "on" : "off") << ")";
64 return os;
65 }
66};
67
68class Stimuli : public std::vector<Stimulus> {
69 using std::vector<Stimulus>::vector;
70
71 static inline uint64_t pack(int row, int col) {
72 return (static_cast<uint64_t>(static_cast<uint32_t>(row)) << 32) | static_cast<uint32_t>(col);
73 }
74
75public:
76 void on() {
77 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state = true; });
78 }
79 void off() {
80 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state = false; });
81 }
82 void set(const bool state) {
83 std::for_each(begin(), end(), [state](Stimulus &stimulus) { stimulus.state = state; });
84 }
85 void toggle() {
86 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state = !stimulus.state; });
87 }
88
93 void filter() {
94 std::vector<Stimulus> out;
95 std::unordered_map<uint64_t, size_t> pos;
96 out.reserve(size());
97 pos.reserve(size() * 2);
98
99 for(const auto &s : *this) {
100 uint64_t key = pack(s.row, s.col);
101 auto [it, inserted] = pos.emplace(key, out.size());
102 if(inserted) {
103 out.emplace_back(s);
104 } else {
105 auto &chosen = out[it->second];
106 if(s.state && !chosen.state) {
107 chosen = s; // prefer state true over false
108 }
109 }
110 }
111 this->assign(out.begin(), out.end());
112 }
113};
114
115using cfloat = std::complex<float>;
116using cfloatmat = Eigen::Matrix<cfloat, Eigen::Dynamic, Eigen::Dynamic>;
117
118constexpr unsigned int LOG2(const unsigned int n) { return ((n < 2) ? 0 : 1 + LOG2(n >> 1U)); }
119
120#ifdef __has_builtin
121#if __has_builtin(__builtin_ffs)
122#define EFFT_HAS_BUILTIN_FFS
123#endif
124#endif
125#ifdef EFFT_HAS_BUILTIN_FFS
126inline unsigned int log2i(const unsigned int n) { return __builtin_ffs(static_cast<int>(n)) - 1; }
127#else
128inline unsigned int log2i(const unsigned int n) { return static_cast<unsigned int>(std::log2f(n)); }
129#endif
130
131template <unsigned int N>
132class eFFT {
133private:
134 static constexpr unsigned int LOG2_N = LOG2(N);
135 std::array<std::vector<cfloatmat>, LOG2_N + 1> tree_;
136 std::vector<cfloat> twiddle_;
137#ifdef EFFT_USE_FFTW3
138 fftw_complex *fftwInput_{nullptr};
139 fftw_complex *fftwOutput_{nullptr};
140 fftw_plan plan_{nullptr};
141#endif
142
143public:
144 eFFT() {
145 constexpr float PI = 3.14159265358979323846F;
146 constexpr float MINUS_TWO_PI = -2 * PI;
147 twiddle_.resize(static_cast<std::size_t>(N) * static_cast<std::size_t>(N + 1));
148#ifdef EFFT_USE_FFTW3
149 fftwInput_ = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * static_cast<std::size_t>(N) * static_cast<std::size_t>(N)));
150 fftwOutput_ = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex) * static_cast<std::size_t>(N) * static_cast<std::size_t>(N)));
151 if(!fftwInput_ || !fftwOutput_) throw std::bad_alloc();
152#endif
153 for(unsigned int i = 0; i < N; i++) {
154 twiddle_[i] = cfloat{1.0F, 0.0F};
155 for(unsigned int n = 1; n <= N; n++) {
156 twiddle_[i + N * n] = static_cast<cfloat>(std::polar(1.0F, MINUS_TWO_PI * static_cast<float>(i) / static_cast<float>(n)));
157 }
158 }
159 }
160
161 ~eFFT() {
162#ifdef EFFT_USE_FFTW3
163 if(plan_) fftw_destroy_plan(plan_);
164 if(fftwInput_) fftw_free(fftwInput_);
165 if(fftwOutput_) fftw_free(fftwOutput_);
166#endif
167 }
168
169 eFFT(const eFFT &) = delete;
170 eFFT(eFFT &&) noexcept = default;
171 eFFT &operator=(const eFFT &) = delete;
172 eFFT &operator=(eFFT &&) noexcept = default;
173
178 [[nodiscard]] constexpr unsigned int framesize() const {
179 return N;
180 }
181
185 void initialize() {
186 cfloatmat f0(cfloatmat::Zero(N, N));
187 initialize(f0);
188 }
189
196 void initialize(cfloatmat &x, const int offset = 0) {
197 const unsigned int n = x.rows();
198 if(n == 1) {
199 tree_[0].emplace_back(x);
200 return;
201 }
202 const unsigned int ndiv2 = n >> 1U;
203 const unsigned int idx = log2i(ndiv2);
204
205 cfloatmat s00(x(Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>)));
206 cfloatmat s01(x(Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>)));
207 cfloatmat s10(x(Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>)));
208 cfloatmat s11(x(Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>)));
209 initialize(s00, 4 * offset);
210 initialize(s01, 4 * offset + 4);
211 initialize(s10, 4 * offset + 8);
212 initialize(s11, 4 * offset + 12);
213
214 const cfloatmat &x00 = tree_[idx][offset];
215 const cfloatmat &x01 = tree_[idx][offset + 1];
216 const cfloatmat &x10 = tree_[idx][offset + 2];
217 const cfloatmat &x11 = tree_[idx][offset + 3];
218 const unsigned int Nn = N * n;
219 for(unsigned int i = 0; i < ndiv2; i++) {
220 for(unsigned int j = 0; j < ndiv2; j++) {
221 const cfloat tu = twiddle_[j + Nn] * x01(i, j);
222 const cfloat td = twiddle_[(i + j) + Nn] * x11(i, j);
223 const cfloat ts = twiddle_[i + Nn] * x10(i, j);
224
225 const cfloat a = x00(i, j) + tu;
226 const cfloat b = x00(i, j) - tu;
227 const cfloat c = ts + td;
228 const cfloat d = ts - td;
229
230 x(i, j) = a + c;
231 x(i, j + ndiv2) = b + d;
232 x(i + ndiv2, j) = a - c;
233 x(i + ndiv2, j + ndiv2) = b - d;
234 }
235 }
236 tree_[log2i(n)].emplace_back(x);
237 }
238
245 bool update(const Stimulus &p) { return update(tree_[LOG2_N][0], p); }
246
254 bool update(cfloatmat &x, const Stimulus &p, const unsigned int offset = 0) {
255 const unsigned int n = x.rows();
256 if(n == 1) {
257 return std::exchange(x(0, 0), p.state).real() != static_cast<float>(p.state);
258 }
259 const unsigned int ndiv2 = n >> 1U;
260 const unsigned int nndiv2 = n * ndiv2;
261 const unsigned int idx = log2i(ndiv2);
262
263 bool changed = false;
264 if(static_cast<bool>(p.row & 1U)) {
265 if(static_cast<bool>(p.col & 1U)) {
266 changed = update(tree_[idx][offset + 3], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 12); // odd-odd
267 } else {
268 changed = update(tree_[idx][offset + 2], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 8); // odd-even
269 }
270 } else {
271 if(static_cast<bool>(p.col & 1U)) {
272 changed = update(tree_[idx][offset + 1], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 4); // even-odd
273 } else {
274 changed = update(tree_[idx][offset], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset); // even-even
275 }
276 }
277
278 if(changed) {
279 const cfloat *x00 = tree_[idx][offset].data();
280 const cfloat *x01 = tree_[idx][offset + 1].data();
281 const cfloat *x10 = tree_[idx][offset + 2].data();
282 const cfloat *x11 = tree_[idx][offset + 3].data();
283 cfloat *xp = x.data();
284 const unsigned int Nn = N * n;
285
286 for(unsigned int j = 0; j < ndiv2; j++) {
287 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
288 for(unsigned int i = 0; i < ndiv2; i++) {
289 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
290
291 const cfloat tu = twiddle_[j + Nn] * x01[k];
292 const cfloat td = twiddle_[i + j + Nn] * x11[k];
293 const cfloat ts = twiddle_[i + Nn] * x10[k];
294
295 const cfloat x00_k = x00[k];
296 const cfloat a = x00_k + tu;
297 const cfloat b = x00_k - tu;
298 const cfloat c = ts + td;
299 const cfloat d = ts - td;
300
301 xp[k1] = a + c;
302 xp[k1 + nndiv2] = b + d;
303 xp[k2] = a - c;
304 xp[k2 + nndiv2] = b - d;
305 }
306 }
307 }
308 return changed;
309 }
310
317 bool update(Stimuli &pv) { return update(tree_[LOG2_N][0], pv.begin(), pv.end()); }
318
327 bool update(cfloatmat &x, Stimuli::iterator b0, Stimuli::iterator e0, const unsigned int offset = 0) {
328 const unsigned int n = x.rows();
329 if(n == 1) {
330 if(e0 - b0 == 1) {
331 return std::exchange(x(0, 0), b0->state).real() != static_cast<float>(b0->state);
332 }
333 const bool state = std::any_of(b0, e0, [](const Stimulus &p) { return p.state; });
334 return std::exchange(x(0, 0), state).real() != static_cast<float>(state);
335 }
336 const unsigned int ndiv2 = n >> 1U;
337 const unsigned int nndiv2 = n * ndiv2;
338 const unsigned int idx = log2i(ndiv2);
339
340 Stimuli::iterator e1, e2, e3;
341 e2 = std::partition(b0, e0, [](const Stimulus &p) { return p.row & 1U; });
342 e1 = std::partition(b0, e2, [](const Stimulus &p) { return p.col & 1U; });
343 e3 = std::partition(e2, e0, [](const Stimulus &p) { return p.col & 1U; });
344
345 auto transformStimuli = [](Stimuli::iterator begin, Stimuli::iterator end) {
346 for(auto it = begin; it != end; ++it) {
347 it->row >>= 1U;
348 it->col >>= 1U;
349 }
350 };
351 transformStimuli(b0, e1);
352 transformStimuli(e1, e2);
353 transformStimuli(e2, e3);
354 transformStimuli(e3, e0);
355
356 bool changed = false;
357 if(b0 != e1) {
358 changed = update(tree_[idx][offset + 3], b0, e1, 4 * offset + 12) || changed; // odd-odd
359 }
360 if(e1 != e2) {
361 changed = update(tree_[idx][offset + 2], e1, e2, 4 * offset + 8) || changed; // odd-even
362 }
363 if(e2 != e3) {
364 changed = update(tree_[idx][offset + 1], e2, e3, 4 * offset + 4) || changed; // even-odd
365 }
366 if(e3 != e0) {
367 changed = update(tree_[idx][offset], e3, e0, 4 * offset) || changed; // even-even
368 }
369
370 if(changed) {
371 const cfloat *x00 = tree_[idx][offset].data();
372 const cfloat *x01 = tree_[idx][offset + 1].data();
373 const cfloat *x10 = tree_[idx][offset + 2].data();
374 const cfloat *x11 = tree_[idx][offset + 3].data();
375 cfloat *xp = x.data();
376 const unsigned int Nn = N * n;
377
378 for(unsigned int j = 0; j < ndiv2; j++) {
379 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
380 for(unsigned int i = 0; i < ndiv2; i++) {
381 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
382
383 const cfloat tu = twiddle_[j + Nn] * x01[k];
384 const cfloat td = twiddle_[i + j + Nn] * x11[k];
385 const cfloat ts = twiddle_[i + Nn] * x10[k];
386
387 const cfloat x00_k = x00[k];
388 const cfloat a = x00_k + tu;
389 const cfloat b = x00_k - tu;
390 const cfloat c = ts + td;
391 const cfloat d = ts - td;
392
393 xp[k1] = a + c;
394 xp[k1 + nndiv2] = b + d;
395 xp[k2] = a - c;
396 xp[k2 + nndiv2] = b - d;
397 }
398 }
399 }
400 return changed;
401 }
402
403#ifdef EFFT_USE_FFTW3
409 void initializeGroundTruth(const cfloatmat &image = cfloatmat::Zero(N, N)) {
410 plan_ = fftw_plan_dft_2d(N, N, fftwInput_, fftwOutput_, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_UNALIGNED | FFTW_NO_SIMD | FFTW_PRESERVE_INPUT);
411 for(Eigen::Index i = 0; i < image.rows(); i++) {
412 for(Eigen::Index j = 0; j < image.cols(); j++) {
413 fftwInput_[N * i + j][0] = image(i, j).real();
414 fftwInput_[N * i + j][1] = image(i, j).imag();
415 }
416 }
417 fftw_execute(plan_);
418 }
419
425 void updateGroundTruth(const Stimulus &p) {
426 fftwInput_[N * p.row + p.col][0] = static_cast<double>(p.state);
427 fftwInput_[N * p.row + p.col][1] = 0;
428 fftw_execute(plan_);
429 }
430
436 void updateGroundTruth(const Stimuli &pv) {
437 std::set<std::pair<unsigned int, unsigned int>> activated;
438 for(const Stimulus &p : pv) {
439 if(p.state) {
440 activated.insert({p.row, p.col});
441 } else if(activated.find({p.row, p.col}) != activated.end()) {
442 continue;
443 }
444 fftwInput_[N * p.row + p.col][0] = static_cast<double>(p.state);
445 fftwInput_[N * p.row + p.col][1] = 0;
446 }
447 fftw_execute(plan_);
448 }
449#endif
450
456 [[nodiscard]] inline const cfloatmat &getFFT() const {
457 return tree_[LOG2_N][0];
458 }
459
460#ifdef EFFT_USE_FFTW3
466 [[nodiscard]] inline cfloatmat getGroundTruthFFT() const {
467 return Eigen::Map<const Eigen::Matrix<std::complex<double>, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>(reinterpret_cast<const std::complex<double> *>(fftwOutput_), N, N).template cast<cfloat>();
468 }
469
475 [[nodiscard]] inline double check() const {
476 return (getFFT() - getGroundTruthFFT()).norm();
477 }
478#endif
479};
480
481#endif // EFFT_HPP
Definition efft.hpp:68
void filter()
Filters out stimuli.
Definition efft.hpp:93
Definition efft.hpp:36
void initialize()
Initializes the FFT computation with zero matrix.
Definition efft.hpp:185
bool update(const Stimulus &p)
Updates the FFT with a single stimulus.
Definition efft.hpp:245
bool update(Stimuli &pv)
Updates the FFT with multiple stimuli.
Definition efft.hpp:317
bool update(cfloatmat &x, Stimuli::iterator b0, Stimuli::iterator e0, const unsigned int offset=0)
Updates the FFT with multiple stimuli in the specified matrix.
Definition efft.hpp:327
const cfloatmat & getFFT() const
Get the FFT result as an Eigen matrix of complex floats.
Definition efft.hpp:456
constexpr unsigned int framesize() const
Get the frame size of the FFT.
Definition efft.hpp:178
void initialize(cfloatmat &x, const int offset=0)
Initializes the FFT computation with the provided matrix.
Definition efft.hpp:196
bool update(cfloatmat &x, const Stimulus &p, const unsigned int offset=0)
Updates the FFT with a single stimulus in the specified matrix.
Definition efft.hpp:254