File: vtkFFT.h

package info (click to toggle)
vtk9 9.3.0%2Bdfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 267,116 kB
  • sloc: cpp: 2,195,914; ansic: 285,452; python: 104,858; sh: 4,061; yacc: 4,035; java: 3,977; xml: 2,771; perl: 2,189; lex: 1,762; objc: 153; makefile: 150; javascript: 90; tcl: 59
file content (574 lines) | stat: -rw-r--r-- 21,528 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class vtkFFT
 * @brief perform Discrete Fourier Transforms
 *
 * vtkFFT provides methods to perform Discrete Fourier Transforms (DFT).
 * These include providing forward and reverse Fourier transforms.
 * The current implementation uses the third-party library kissfft.
 *
 * The terminology tries to follow the Numpy terminology, that is :
 *  - Fft means the Fast Fourier Transform algorithm
 *  - Prefix `R` stands for Real (meaning optimized function for real inputs)
 *  - Prefix `I` stands for Inverse
 *
 * Some functions provides pointer-based version of themself in order to
 * prevent copying memory when possible.
 */

#ifndef vtkFFT_h
#define vtkFFT_h

#include "vtkAOSDataArrayTemplate.h" // vtkAOSDataArrayTemplate
#include "vtkCommonMathModule.h"     // export macro
#include "vtkDataArray.h"            // vtkDataArray
#include "vtkDataArrayRange.h"       // vtkDataArrayRange
#include "vtkMath.h"                 // vtkMath::Pi
#include "vtkObject.h"
#include "vtkSMPTools.h" // vtkSMPTools::Transform, vtkSMPTools::For

#include "vtk_kissfft.h" // kiss_fft_scalar, kiss_fft_cpx
// clang-format off
#include VTK_KISSFFT_HEADER(kiss_fft.h)
#include VTK_KISSFFT_HEADER(tools/kiss_fftr.h)
// clang-format on

#include <array>       // std::array
#include <cmath>       // std::sin, std::cos, std::sqrt
#include <numeric>     // std::accumulate, std::inner_product
#include <type_traits> // std::enable_if, std::iterator_traits
#include <vector>      // std::vector

VTK_ABI_NAMESPACE_BEGIN
class VTKCOMMONMATH_EXPORT vtkFFT : public vtkObject
{
public:
  ///@{
  /**
   * Useful type definitions and utilities.
   *
   * ScalarNumber is defined as a floating point number.
   *
   * ComplexNumber is defined as a struct that contains two ScalarNumber.
   * These 2 numbers should be contiguous in memory.
   * First one should be named r and represent the real part,
   * while second one should be named i and represent the imaginary part.
   * This specification is important for the implementation of functions
   * accepting and returning values from vtkDataArrays as it allows to do
   * some zero-copy operations.
   *
   * A vtkScalarNumberArray is a data array with a layout memory compatible
   * with the underlying library for zero copy operations.
   *
   * isFftType is a trait to tell templates if Type is either ScalarNumber
   * or ComplexNumber.
   *
   * Common operators such as +,-,*,/ between ScalarNumber and
   * ComplexNumber are included in this header.
   */
  using ScalarNumber = kiss_fft_scalar;
  using ComplexNumber = kiss_fft_cpx;
  static_assert(sizeof(ComplexNumber) == 2 * sizeof(ScalarNumber),
    "vtkFFT::ComplexNumber definition is not valid");

  using vtkScalarNumberArray = vtkAOSDataArrayTemplate<vtkFFT::ScalarNumber>;
  template <typename T>
  struct isFftType : public std::false_type
  {
  };
  ///@}

  /**
   * Enum containing octave band numbers, named upon their nominal midband frequency.
   * Value multiplied by 3 should be a one-third-octave band number matching an octave band.
   */
  enum Octave
  {
    Hz_31_5 = 5,
    Hz_63 = 6,
    Hz_125 = 7,
    Hz_250 = 8,
    Hz_500 = 9,
    kHz_1 = 10,
    kHz_2 = 11,
    kHz_4 = 12,
    kHz_8 = 13,
    kHz_16 = 14
  };

  /**
   * Enum specifying which octave band we want to compute.
   * Could be a full octave range, or a one-third-octave one for instance.
   */
  enum OctaveSubdivision
  {
    Full,
    FirstHalf,
    SecondHalf,
    FirstThird,
    SecondThird,
    ThirdThird
  };

  ///@{
  /**
   * Compute the one-dimensional DFT for complex input.
   * If input is scalars then the imaginary part is set to 0
   *
   * input has n complex points
   * output has n complex points in case of success and empty in case of failure
   */
  static std::vector<ComplexNumber> Fft(const std::vector<ScalarNumber>& in);
  static void Fft(ScalarNumber* input, std::size_t size, ComplexNumber* result);
  static std::vector<ComplexNumber> Fft(const std::vector<ComplexNumber>& in);
  static void Fft(ComplexNumber* input, std::size_t size, ComplexNumber* result);
#ifndef __VTK_WRAP__
  static vtkSmartPointer<vtkScalarNumberArray> Fft(vtkScalarNumberArray* input);
#endif
  ///@}

  ///@{
  /**
   * Compute the one-dimensional DFT for real input
   *
   *  input has n scalar points
   *  output has (n/2) + 1 complex points in case of success and empty in case of failure
   */
  static std::vector<ComplexNumber> RFft(const std::vector<ScalarNumber>& in);
  static void RFft(ScalarNumber* input, std::size_t size, ComplexNumber* result);
#ifndef __VTK_WRAP__
  static vtkSmartPointer<vtkScalarNumberArray> RFft(vtkScalarNumberArray* input);
#endif
  ///@}

  /**
   * Compute the inverse of @c Fft. The input should be ordered in the same way as is returned by @c
   * Fft, i.e.,
   *  - in[0] should contain the zero frequency term,
   *  - in[1:n//2] should contain the positive-frequency terms,
   *  - in[n//2 + 1:] should contain the negative-frequency terms.
   *
   *  input has n complex points
   *  output has n scalar points in case of success and empty in case of failure
   */
  static std::vector<ComplexNumber> IFft(const std::vector<ComplexNumber>& in);

  /**
   * Compute the inverse of @c RFft. The input is expected to be in the form returned by @c Rfft,
   * i.e. the real zero-frequency term followed by the complex positive frequency terms in
   * order of increasing frequency.
   *
   *  input has  (n/2) + 1 complex points
   *  output has n scalar points in case of success and empty in case of failure
   */
  static std::vector<ScalarNumber> IRFft(const std::vector<ComplexNumber>& in);

  /**
   * Return the absolute value (also known as norm, modulus, or magnitude) of complex number
   */
  static inline ScalarNumber Abs(const ComplexNumber& in);

  /**
   * Return the squared absolute value of the complex number
   */
  static inline ScalarNumber SquaredAbs(const ComplexNumber& in);

  /**
   * Return the conjugate of the given complex number
   */
  static inline ComplexNumber Conjugate(const ComplexNumber& in);

  /**
   * Return the DFT sample frequencies. Output has @c windowLength size.
   */
  static std::vector<ScalarNumber> FftFreq(int windowLength, double sampleSpacing);

  /**
   * Return the DFT sample frequencies for the real version of the dft (see @c Rfft).
   * Output has @c (windowLength / 2) + 1 size.
   */
  static std::vector<ScalarNumber> RFftFreq(int windowLength, double sampleSpacing);

  /**
   * Return lower and upper frequency from a octave band number / nominal midband frequency.
   * @param[in] octave octave band number associated to nominal midband frequency
   * @param[in] octaveSubdivision (optional) which subdivision of octave wanted (default: Full)
   * @param[in] baseTwo (optional) whether to compute it using base-2 or base-10 (default: base-2)
   * cf. "ANSI S1.11: Specification for Octave, Half-Octave, and Third Octave Band Filter Sets".
   */
  static std::array<double, 2> GetOctaveFrequencyRange(Octave octave,
    OctaveSubdivision octaveSubdivision = OctaveSubdivision::Full, bool baseTwo = true);

  ///@{
  /**
   * Compute consecutive Fourier transforms Welch method without averaging nor
   * scaling the result.
   *
   * @param[in] signal the input signal
   * @param[in] window the window to use per segment. Its size defines the size of FFT and thus the
   * size of the output.
   * @param[in] noverlap number of samples that will overlap between two segment
   * @param[in] detrend if true then each segment will be detrend using the mean value of the
   * segment before applying the FFT.
   * @param[in] onesided if true return a one-sided spectrum for real data. If input is copmlex then
   * this option will be ignored.
   * @param[out] shape if not @c nullptr, return the shape (n,m) of the result. `n` is the number of
   * segment and `m` the number of samples per segment.
   *
   * @return a 1D array that stores all resulting segment. For a shape (N,M), layout is
   * (segment0_sample0, segment0_sample1, ..., segment0_sampleM, segment1_sample0, ...
   * segmentN_sampleM)
   */
#ifndef __VTK_WRAP__
  template <typename T, typename TW, typename std::enable_if<isFftType<T>::value>::type* = nullptr>
  static std::vector<ComplexNumber> OverlappingFft(const std::vector<T>& signal,
    const std::vector<TW>& window, std::size_t noverlap, bool detrend, bool onesided,
    unsigned int* shape = nullptr);

  template <typename TW>
  static vtkFFT::ComplexNumber* OverlappingFft(vtkFFT::vtkScalarNumberArray* signal,
    const std::vector<TW>& window, std::size_t noverlap, bool detrend, bool onesided,
    unsigned int* shape = nullptr);
#endif
  ///@}

  /**
   * Scaling modes for Spectrogram and Csd functions.
   */
  enum Scaling : int
  {
    Density = 0, ///< Cross Spectral \b Density scaling (<b>V^2/Hz</b>)
    Spectrum     ///< Cross \b Spectrum scaling (<b>V^2</b>)
  };

  /**
   * Spectral modes for Spectrogram and Csd functions.
   */
  enum SpectralMode : int
  {
    STFT = 0, ///< Short-Time Fourier Transform, for local sections
    PSD       ///< Power Spectral Density
  };

  ///@{
  /**
   * Compute a spectrogram with consecutive Fourier transforms using Welch method.
   *
   * @param[in] signal the input signal
   * @param[in] window the window to use per segment. Its size defines the size of FFT and thus the
   * size of the output.
   * @param[in] sampleRate sample rate of the input signal
   * @param[in] noverlap number of samples that will overlap between two segment
   * @param[in] detrend if true then each segment will be detrend using the mean value of the
   * segment before applying the FFT.
   * @param[in] onesided if true return a one-sided spectrum for real data. If input is copmlex then
   * this option will be ignored.
   * @param[in] scaling can be either Cross Spectral \b Density (<b>V^2/Hz</b>) or Cross \b Spectrum
   * (<b>V^2</b>)
   * @param[in] mode determine which type of value ares returned. It is very dependent to how you
   * want to use the result afterwards.
   * @param[out] shape if not @c nullptr, return the shape (n,m) of the result. `n` is the number of
   * segment and `m` the number of samples per segment. Shape is inverted if `transpose` is true.
   * @param[in] transpose allows to transpose the resulting the resulting matrix into something of
   * shape (m, n)
   *
   * @return a 1D array that stores all resulting segment. For a shape (N,M), layout is
   * (segment0_sample0, segment0_sample1, ..., segment0_sampleM, segment1_sample0, ...
   * segmentN_sampleM)
   */
#ifndef __VTK_WRAP__
  template <typename T, typename TW, typename std::enable_if<isFftType<T>::value>::type* = nullptr>
  static std::vector<ComplexNumber> Spectrogram(const std::vector<T>& signal,
    const std::vector<TW>& window, double sampleRate, int noverlap, bool detrend, bool onesided,
    vtkFFT::Scaling scaling, vtkFFT::SpectralMode mode, unsigned int* shape = nullptr,
    bool transpose = false);

  template <typename TW>
  static vtkSmartPointer<vtkFFT::vtkScalarNumberArray> Spectrogram(
    vtkFFT::vtkScalarNumberArray* signal, const std::vector<TW>& window, double sampleRate,
    int noverlap, bool detrend, bool onesided, vtkFFT::Scaling scaling, vtkFFT::SpectralMode mode,
    unsigned int* shape = nullptr, bool transpose = false);
#endif
  ///@}

  ///@{
  /**
   * Compute the Cross Spectral Density of a given signal. This is the optimized version for
   * computing the csd of a single signal with itself. It uses Spectrogram behind the hood, and then
   * average all resulting segments of the spectrogram.
   *
   * @param[in] signal the input signal
   * @param[in] window the window to use per segment. Its size defines the size of FFT and thus the
   * size of the output.
   * @param[in] sampleRate sample rate of the input signal
   * @param[in] noverlap number of samples that will overlap between two segment
   * @param[in] detrend if true then each segment will be detrend using the mean value of the
   * segment before applying the FFT.
   * @param[in] onesided if true return a one-sided spectrum for real data. If input is copmlex then
   * this option will be ignored.
   * @param[in] scaling can be either Cross Spectral \b Density (<b>V^2/Hz</b>) or Cross \b Spectrum
   * (<b>V^2</b>)
   *
   * @return a 1D array containing the resulting cross spectral density or spectrum.
   *
   * See vtkFFT::Spectrogram
   */
#ifndef __VTK_WRAP__
  template <typename T, typename TW, typename std::enable_if<isFftType<T>::value>::type* = nullptr>
  static std::vector<vtkFFT::ScalarNumber> Csd(const std::vector<T>& signal,
    const std::vector<TW>& window, double sampleRate, int noverlap, bool detrend, bool onesided,
    vtkFFT::Scaling scaling);

  template <typename TW>
  static vtkSmartPointer<vtkFFT::vtkScalarNumberArray> Csd(vtkScalarNumberArray* signal,
    const std::vector<TW>& window, double sampleRate, int noverlap, bool detrend, bool onesided,
    vtkFFT::Scaling scaling);
#endif
  ///@}

  /**
   * Transpose in place an inlined 2D matrix. This algorithm is not optimized
   * for square matrices but is generic. This will also effectively swap shape values.
   * Worst case complexity is : O( (shape[0]*shape[1])^3/2 )
   *
   * XXX: some fft librairies such as FFTW already propose functions to do that.
   * This should be taken into account if the backend is changed at some point.
   *
   * XXX: An optimized version could be implemented for square matrices
   */
#ifndef __VTK_WRAP__
  template <typename T>
  static void Transpose(T* data, unsigned int* shape);
#endif

  ///@{
  /**
   * Window generator functions. Implementation only needs to be valid for x E [0; size / 2]
   * because kernels are symmetric by definitions. This point is very important for some
   * kernels like Bartlett for example.
   *
   * @warning Most generators need size > 1 !
   *
   * Can be used with @c GenerateKernel1D and @c GenerateKernel2D for generating full kernels.
   */
  using WindowGenerator = ScalarNumber (*)(std::size_t, std::size_t);

  static inline ScalarNumber HanningGenerator(std::size_t x, std::size_t size);
  static inline ScalarNumber BartlettGenerator(std::size_t x, std::size_t size);
  static inline ScalarNumber SineGenerator(std::size_t x, std::size_t size);
  static inline ScalarNumber BlackmanGenerator(std::size_t x, std::size_t size);
  static inline ScalarNumber RectangularGenerator(std::size_t x, std::size_t size);
  ///@}

  /**
   * Given a window generator function, create a symmetric 1D kernel.
   * @c kernel is the pointer to the raw data array
   */
  template <typename T>
  static void GenerateKernel1D(T* kernel, std::size_t n, WindowGenerator generator);

  /**
   * Given a window generator function, create a symmetric 2D kernel.
   * @c kernel is the pointer to the raw 2D data array.
   */
  template <typename T>
  static void GenerateKernel2D(T* kernel, std::size_t n, std::size_t m, WindowGenerator generator);

  static vtkFFT* New();
  vtkTypeMacro(vtkFFT, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

protected:
  vtkFFT() = default;
  ~vtkFFT() override = default;

  /**
   * Templated zero value, specialized for vtkFFT::ComplexNumber
   */
  template <typename T>
  constexpr static T Zero();

#ifndef __VTK_WRAP__
  /**
   * For a given window defined by @c begin and @c end, compute the scaling needed to apply
   * to the resulting FFT. Used in the `Spectrogram` function.
   */
  template <typename InputIt>
  static typename std::iterator_traits<InputIt>::value_type ComputeScaling(
    InputIt begin, InputIt end, Scaling scaling, double fs);

  /**
   * Dispatch the signal to the right FFT function according to the given parameters.
   * Also detrend the signal and multiply it by the window. Used in the `OverlappingFft` function.
   */
  template <typename T, typename TW>
  static void PreprocessAndDispatchFft(const T* segment, const std::vector<TW>& window,
    bool detrend, bool onesided, vtkFFT::ComplexNumber* result);

  /**
   * XXX(c++17): This function should NOT exist and is here just for the sake template unfolding
   * purposes. As long we don't have `constexrp if` this is the easier way to deal with it.
   *
   * @warning this function will always throw an error
   *
   * @see PreprocessAndDispatchFft
   */
  static void RFft(ComplexNumber* input, std::size_t size, ComplexNumber* result);

  /**
   * Scale a fft according to its window and some mode. Used in the `Spectrogram` function.
   */
  template <typename TW>
  static void ScaleFft(ComplexNumber* fft, unsigned int shape[2], const std::vector<TW>& window,
    double sampleRate, bool onesided, vtkFFT::Scaling scaling, vtkFFT::SpectralMode mode);
#endif

private:
  vtkFFT(const vtkFFT&) = delete;
  void operator=(const vtkFFT&) = delete;
};

//------------------------------------------------------------------------------
template <>
struct vtkFFT::isFftType<vtkFFT::ScalarNumber> : public std::true_type
{
};
template <>
struct vtkFFT::isFftType<vtkFFT::ComplexNumber> : public std::true_type
{
};

//------------------------------------------------------------------------------
template <typename T>
constexpr T vtkFFT::Zero()
{
  return static_cast<T>(0);
}
template <>
constexpr vtkFFT::ComplexNumber vtkFFT::Zero()
{
  return vtkFFT::ComplexNumber{ 0.0, 0.0 };
}

//------------------------------------------------------------------------------
inline vtkFFT::ComplexNumber operator+(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ComplexNumber& rhs)
{
  return vtkFFT::ComplexNumber{ lhs.r + rhs.r, lhs.i + rhs.i };
}
inline vtkFFT::ComplexNumber operator-(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ComplexNumber& rhs)
{
  return vtkFFT::ComplexNumber{ lhs.r - rhs.r, lhs.i - rhs.i };
}
inline vtkFFT::ComplexNumber operator*(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ComplexNumber& rhs)
{
  return vtkFFT::ComplexNumber{ (lhs.r * rhs.r) - (lhs.i * rhs.i),
    (lhs.r * rhs.i) + (lhs.i * rhs.r) };
}
inline vtkFFT::ComplexNumber operator*(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ScalarNumber& rhs)
{
  return vtkFFT::ComplexNumber{ lhs.r * rhs, lhs.i * rhs };
}
inline vtkFFT::ComplexNumber operator/(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ComplexNumber& rhs)
{
  const double divisor = rhs.r * rhs.r + rhs.i * rhs.i;
  return vtkFFT::ComplexNumber{ ((lhs.r * rhs.r) + (lhs.i * rhs.i)) / divisor,
    ((lhs.i * rhs.r) - (lhs.r * rhs.i)) / divisor };
}
inline vtkFFT::ComplexNumber operator/(
  const vtkFFT::ComplexNumber& lhs, const vtkFFT::ScalarNumber& rhs)
{
  return vtkFFT::ComplexNumber{ lhs.r / rhs, lhs.i / rhs };
}

//------------------------------------------------------------------------------
vtkFFT::ScalarNumber vtkFFT::Abs(const ComplexNumber& in)
{
  return std::sqrt(in.r * in.r + in.i * in.i);
}

//------------------------------------------------------------------------------
vtkFFT::ScalarNumber vtkFFT::SquaredAbs(const ComplexNumber& in)
{
  return in.r * in.r + in.i * in.i;
}

//------------------------------------------------------------------------------
vtkFFT::ComplexNumber vtkFFT::Conjugate(const ComplexNumber& in)
{
  return ComplexNumber{ in.r, -in.i };
}

//------------------------------------------------------------------------------
double vtkFFT::HanningGenerator(std::size_t x, std::size_t size)
{
  return 0.5 * (1.0 - std::cos(2.0 * vtkMath::Pi() * x / (size - 1)));
}

//------------------------------------------------------------------------------
double vtkFFT::BartlettGenerator(std::size_t x, std::size_t size)
{
  return 2.0 * x / (size - 1);
}

//------------------------------------------------------------------------------
double vtkFFT::SineGenerator(std::size_t x, std::size_t size)
{
  return std::sin(vtkMath::Pi() * x / (size - 1));
}

//------------------------------------------------------------------------------
double vtkFFT::BlackmanGenerator(std::size_t x, std::size_t size)
{
  const double cosin = std::cos((2.0 * vtkMath::Pi() * x) / (size - 1));
  return 0.42 - 0.5 * cosin + 0.08 * (2.0 * cosin * cosin - 1.0);
}

//------------------------------------------------------------------------------
double vtkFFT::RectangularGenerator(std::size_t, std::size_t)
{
  return 1.0;
}

//------------------------------------------------------------------------------
template <typename T>
void vtkFFT::GenerateKernel1D(T* kernel, std::size_t n, WindowGenerator generator)
{
  std::size_t half = (n / 2) + (n % 2);
  for (std::size_t i = 0; i < half; ++i)
  {
    kernel[i] = kernel[n - 1 - i] = generator(i, n);
  }
}

//------------------------------------------------------------------------------
template <typename T>
void vtkFFT::GenerateKernel2D(T* kernel, std::size_t n, std::size_t m, WindowGenerator generator)
{
  const std::size_t halfX = (n / 2) + (n % 2);
  const std::size_t halfY = (m / 2) + (m % 2);
  for (std::size_t i = 0; i < halfX; ++i)
  {
    for (std::size_t j = 0; j < halfY; ++j)
    {
      // clang-format off
      kernel[i][j]
      = kernel[n - 1 - i][j]
      = kernel[i][m - 1 - j]
      = kernel[n - 1 - i][m - 1 - j]
      = generator(i, n) * generator(j, m);
      // clang-format on
    }
  }
}

VTK_ABI_NAMESPACE_END

#include "vtkFFT.txx" // complex templated functions not wrapped by python

#endif // vtkFFT_h