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 <algorithm>
13#include <array>
14#include <cmath>
15#include <complex>
16#include <cstddef>
17#include <eigen3/Eigen/Core>
18#include <set>
19#include <utility>
20#include <vector>
21
22#ifdef EFFT_USE_FFTW3
23#include <fftw3.h>
24#endif
25
26class Stimulus {
27public:
28 unsigned int row{0};
29 unsigned int col{0};
30 bool state{true};
31 Stimulus() = default;
32 Stimulus(const unsigned int row, const unsigned int col) : row{row}, col{col} {};
33 Stimulus(const unsigned int row, const unsigned int col, const bool state) : row{row}, col{col}, state{state} {};
34 bool operator==(const Stimulus &p) const { return (row == p.row && col == p.col); }
35 bool operator!=(const Stimulus &p) const { return (row != p.row || col != p.col); }
36};
37
38class Stimuli : public std::vector<Stimulus> {
39 using std::vector<Stimulus>::vector;
40
41public:
46 void filter() {
47 std::sort(begin(), end(), [](const Stimulus &left, const Stimulus &right) {
48 if(left.row != right.row) {
49 return left.row < right.row;
50 }
51 if(left.col != right.col) {
52 return left.col < right.col;
53 }
54 return left.state && !right.state;
55 });
56 resize(std::unique(begin(), end(), [](const Stimulus &left, const Stimulus &right) { return (left.row == right.row && left.col == right.col); }) - begin());
57 }
58
63 void setState(const bool state) {
64 std::transform(begin(), end(), begin(), [state](Stimulus &p) { p.state = state; return p; });
65 }
66};
67
68using cfloat = std::complex<float>;
69using cfloatmat = Eigen::Matrix<cfloat, Eigen::Dynamic, Eigen::Dynamic>;
70
71constexpr unsigned int LOG2(const unsigned int n) { return ((n < 2) ? 0 : 1 + LOG2(n >> 1U)); }
72
73#ifdef __has_builtin
74#if __has_builtin(__builtin_ffs)
75#define EFFT_HAS_BUILTIN_FFS
76#endif
77#endif
78#ifdef EFFT_HAS_BUILTIN_FFS
79inline unsigned int log2i(const unsigned int n) { return __builtin_ffs(static_cast<int>(n)) - 1; }
80#else
81inline unsigned int log2i(const unsigned int n) { return static_cast<unsigned int>(std::log2f(n)); }
82#endif
83
84template <unsigned int N>
85class eFFT {
86private:
87 static constexpr unsigned int LOG2_N = LOG2(N);
88 std::array<std::vector<cfloatmat>, LOG2_N + 1> tree_;
89 std::array<cfloat, static_cast<std::size_t>(N) * static_cast<std::size_t>(N + 1)> twiddle_;
90#ifdef EFFT_USE_FFTW3
91 std::array<fftw_complex, static_cast<std::size_t>(N) * static_cast<std::size_t>(N)> fftwInput_;
92 std::array<fftw_complex, static_cast<std::size_t>(N) * static_cast<std::size_t>(N)> fftwOutput_;
93 fftw_plan plan_{nullptr};
94#endif
95
96public:
97 eFFT() {
98 constexpr float MINUS_TWO_PI = -2 * M_PI;
99 for(unsigned int i = 0; i < N; i++) {
100 for(unsigned int n = 0; n <= N; n++) {
101 twiddle_[i + N * n] = static_cast<cfloat>(std::polar(1.0F, MINUS_TWO_PI * static_cast<float>(i) / static_cast<float>(n)));
102 }
103 }
104 }
105
106 ~eFFT() {
107#ifdef EFFT_USE_FFTW3
108 if(plan_ != nullptr) {
109 fftw_destroy_plan(plan_);
110 }
111#endif
112 }
113
114 eFFT(const eFFT &) = default;
115 eFFT(eFFT &&) noexcept = default;
116 eFFT &operator=(const eFFT &) = default;
117 eFFT &operator=(eFFT &&) noexcept = default;
118
122 void initialize() {
123 cfloatmat f0(cfloatmat::Zero(N, N));
124 initialize(f0);
125 }
126
133 void initialize(cfloatmat &x, const int offset = 0) {
134 const unsigned int n = x.rows();
135 if(n == 1) {
136 tree_[0].emplace_back(x);
137 return;
138 }
139 const unsigned int ndiv2 = n >> 1U;
140 const unsigned int idx = log2i(ndiv2);
141
142 cfloatmat s00(x(Eigen::seq(Eigen::fix<0>, Eigen::last, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, Eigen::last, Eigen::fix<2>)));
143 cfloatmat s01(x(Eigen::seq(Eigen::fix<0>, Eigen::last, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, Eigen::last, Eigen::fix<2>)));
144 cfloatmat s10(x(Eigen::seq(Eigen::fix<1>, Eigen::last, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, Eigen::last, Eigen::fix<2>)));
145 cfloatmat s11(x(Eigen::seq(Eigen::fix<1>, Eigen::last, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, Eigen::last, Eigen::fix<2>)));
146 initialize(s00, 4 * offset);
147 initialize(s01, 4 * offset + 4);
148 initialize(s10, 4 * offset + 8);
149 initialize(s11, 4 * offset + 12);
150
151 const cfloatmat &x00 = tree_[idx][offset];
152 const cfloatmat &x01 = tree_[idx][offset + 1];
153 const cfloatmat &x10 = tree_[idx][offset + 2];
154 const cfloatmat &x11 = tree_[idx][offset + 3];
155 const unsigned int Nn = N * n;
156 for(unsigned int i = 0; i < ndiv2; i++) {
157 for(unsigned int j = 0; j < ndiv2; j++) {
158 const cfloat tu = twiddle_[j + Nn] * x01(i, j);
159 const cfloat td = twiddle_[(i + j) + Nn] * x11(i, j);
160 const cfloat ts = twiddle_[i + Nn] * x10(i, j);
161
162 const cfloat a = x00(i, j) + tu;
163 const cfloat b = x00(i, j) - tu;
164 const cfloat c = ts + td;
165 const cfloat d = ts - td;
166
167 x(i, j) = a + c;
168 x(i, j + ndiv2) = b + d;
169 x(i + ndiv2, j) = a - c;
170 x(i + ndiv2, j + ndiv2) = b - d;
171 }
172 }
173 tree_[log2i(n)].emplace_back(x);
174 }
175
182 bool update(const Stimulus &p) { return update(tree_[LOG2_N][0], p); }
183
191 bool update(cfloatmat &x, const Stimulus &p, const unsigned int offset = 0) {
192 const unsigned int n = x.rows();
193 if(n == 1) {
194 return std::exchange(x(0, 0), p.state).real() != static_cast<float>(p.state);
195 }
196 const unsigned int ndiv2 = n >> 1U;
197 const unsigned int nndiv2 = n * ndiv2;
198 const unsigned int idx = log2i(ndiv2);
199
200 bool changed = false;
201 if(static_cast<bool>(p.row & 1U)) {
202 if(static_cast<bool>(p.col & 1U)) {
203 changed = update(tree_[idx][offset + 3], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 12); // odd-odd
204 } else {
205 changed = update(tree_[idx][offset + 2], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 8); // odd-even
206 }
207 } else {
208 if(static_cast<bool>(p.col & 1U)) {
209 changed = update(tree_[idx][offset + 1], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 4); // even-odd
210 } else {
211 changed = update(tree_[idx][offset], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset); // even-even
212 }
213 }
214
215 if(changed) {
216 const cfloat *x00 = tree_[idx][offset].data();
217 const cfloat *x01 = tree_[idx][offset + 1].data();
218 const cfloat *x10 = tree_[idx][offset + 2].data();
219 const cfloat *x11 = tree_[idx][offset + 3].data();
220 cfloat *xp = x.data();
221 const unsigned int Nn = N * n;
222
223 for(unsigned int j = 0; j < ndiv2; j++) {
224 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
225 for(unsigned int i = 0; i < ndiv2; i++) {
226 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
227
228 const cfloat tu = twiddle_[j + Nn] * x01[k];
229 const cfloat td = twiddle_[i + j + Nn] * x11[k];
230 const cfloat ts = twiddle_[i + Nn] * x10[k];
231
232 const cfloat x00_k = x00[k];
233 const cfloat a = x00_k + tu;
234 const cfloat b = x00_k - tu;
235 const cfloat c = ts + td;
236 const cfloat d = ts - td;
237
238 xp[k1] = a + c;
239 xp[k1 + nndiv2] = b + d;
240 xp[k2] = a - c;
241 xp[k2 + nndiv2] = b - d;
242 }
243 }
244 }
245 return changed;
246 }
247
254 bool update(Stimuli &pv) { return update(tree_[LOG2_N][0], pv.begin(), pv.end()); }
255
264 bool update(cfloatmat &x, Stimuli::iterator b0, Stimuli::iterator e0, const unsigned int offset = 0) {
265 const unsigned int n = x.rows();
266 if(n == 1) {
267 if(e0 - b0 == 1) {
268 return std::exchange(x(0, 0), b0->state).real() != static_cast<float>(b0->state);
269 }
270 const bool state = std::any_of(b0, e0, [](const Stimulus &p) { return p.state; });
271 return std::exchange(x(0, 0), state).real() != static_cast<float>(state);
272 }
273 const unsigned int ndiv2 = n >> 1U;
274 const unsigned int nndiv2 = n * ndiv2;
275 const unsigned int idx = log2i(ndiv2);
276
277 Stimuli::iterator e1, e2, e3;
278 e2 = std::partition(b0, e0, [](const Stimulus &p) { return p.row & 1U; });
279 e1 = std::partition(b0, e2, [](const Stimulus &p) { return p.col & 1U; });
280 e3 = std::partition(e2, e0, [](const Stimulus &p) { return p.col & 1U; });
281 std::transform(b0, e1, b0, [](const Stimulus &p) { return Stimulus{p.row >> 1U, p.col >> 1U, p.state}; });
282 std::transform(e1, e2, e1, [](const Stimulus &p) { return Stimulus{p.row >> 1U, p.col >> 1U, p.state}; });
283 std::transform(e2, e3, e2, [](const Stimulus &p) { return Stimulus{p.row >> 1U, p.col >> 1U, p.state}; });
284 std::transform(e3, e0, e3, [](const Stimulus &p) { return Stimulus{p.row >> 1U, p.col >> 1U, p.state}; });
285
286 bool changed = false;
287 if(b0 != e1) {
288 changed = update(tree_[idx][offset + 3], b0, e1, 4 * offset + 12) || changed; // odd-odd
289 }
290 if(e1 != e2) {
291 changed = update(tree_[idx][offset + 2], e1, e2, 4 * offset + 8) || changed; // odd-even
292 }
293 if(e2 != e3) {
294 changed = update(tree_[idx][offset + 1], e2, e3, 4 * offset + 4) || changed; // even-odd
295 }
296 if(e3 != e0) {
297 changed = update(tree_[idx][offset], e3, e0, 4 * offset) || changed; // even-even
298 }
299
300 if(changed) {
301 const cfloat *x00 = tree_[idx][offset].data();
302 const cfloat *x01 = tree_[idx][offset + 1].data();
303 const cfloat *x10 = tree_[idx][offset + 2].data();
304 const cfloat *x11 = tree_[idx][offset + 3].data();
305 cfloat *xp = x.data();
306 const unsigned int Nn = N * n;
307
308 for(unsigned int j = 0; j < ndiv2; j++) {
309 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
310 for(unsigned int i = 0; i < ndiv2; i++) {
311 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
312
313 const cfloat tu = twiddle_[j + Nn] * x01[k];
314 const cfloat td = twiddle_[i + j + Nn] * x11[k];
315 const cfloat ts = twiddle_[i + Nn] * x10[k];
316
317 const cfloat x00_k = x00[k];
318 const cfloat a = x00_k + tu;
319 const cfloat b = x00_k - tu;
320 const cfloat c = ts + td;
321 const cfloat d = ts - td;
322
323 xp[k1] = a + c;
324 xp[k1 + nndiv2] = b + d;
325 xp[k2] = a - c;
326 xp[k2 + nndiv2] = b - d;
327 }
328 }
329 }
330 return changed;
331 }
332
333#ifdef EFFT_USE_FFTW3
339 void initializeGroundTruth(const cfloatmat &image = cfloatmat::Zero(N, N)) {
340 plan_ = fftw_plan_dft_2d(N, N, fftwInput_.data(), fftwOutput_.data(), FFTW_FORWARD, FFTW_ESTIMATE | FFTW_UNALIGNED);
341 for(Eigen::Index i = 0; i < image.rows(); i++) {
342 for(Eigen::Index j = 0; j < image.cols(); j++) {
343 fftwInput_[N * i + j][0] = image(i, j).real();
344 fftwInput_[N * i + j][1] = image(i, j).imag();
345 }
346 }
347 fftw_execute(plan_);
348 }
349
355 void updateGroundTruth(const Stimulus &p) {
356 fftwInput_[N * p.row + p.col][0] = static_cast<double>(p.state);
357 fftwInput_[N * p.row + p.col][1] = 0;
358 fftw_execute(plan_);
359 }
360
366 void updateGroundTruth(const Stimuli &pv) {
367 std::set<std::pair<unsigned int, unsigned int>> activated;
368 for(const Stimulus &p : pv) {
369 if(p.state) {
370 activated.insert({p.row, p.col});
371 } else if(activated.find({p.row, p.col}) != activated.end()) {
372 continue;
373 }
374 fftwInput_[N * p.row + p.col][0] = static_cast<double>(p.state);
375 fftwInput_[N * p.row + p.col][1] = 0;
376 }
377 fftw_execute(plan_);
378 }
379#endif
380
386 [[nodiscard]] inline Eigen::Matrix<cfloat, N, N> getFFT() const {
387 return tree_[LOG2_N][0];
388 }
389
390#ifdef EFFT_USE_FFTW3
396 [[nodiscard]] inline Eigen::Matrix<cfloat, N, N> getGroundTruthFFT() const {
397 return Eigen::Map<const Eigen::Matrix<std::complex<double>, N, N, Eigen::RowMajor>>(reinterpret_cast<const std::complex<double> *>(fftwOutput_.data())).template cast<cfloat>();
398 }
399
405 [[nodiscard]] inline double check() const {
406 return (getFFT() - getGroundTruthFFT()).norm();
407 }
408#endif
409};
410
411#endif // EFFT_HPP
Definition efft.hpp:38
void setState(const bool state)
Sets the state of all stimuli in the container.
Definition efft.hpp:63
void filter()
Filters out stimuli.
Definition efft.hpp:46
Definition efft.hpp:26
Definition efft.hpp:85
void initialize()
Initializes the FFT computation with zero matrix.
Definition efft.hpp:122
bool update(const Stimulus &p)
Updates the FFT with a single stimulus.
Definition efft.hpp:182
bool update(Stimuli &pv)
Updates the FFT with multiple stimuli.
Definition efft.hpp:254
Eigen::Matrix< cfloat, N, N > getFFT() const
Get the FFT result as an Eigen matrix of complex floats.
Definition efft.hpp:386
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:264
void initialize(cfloatmat &x, const int offset=0)
Initializes the FFT computation with the provided matrix.
Definition efft.hpp:133
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:191