MayaFlux 0.3.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Convolution.cpp
Go to the documentation of this file.
1#include "Convolution.hpp"
2
4
5#include <Eigen/Dense>
6#include <unsupported/Eigen/FFT>
7
8namespace P = MayaFlux::Parallel;
9
11
12std::vector<double> apply_convolution(
13 std::span<const double> src,
14 std::span<const double> kernel,
15 const ConvolutionProcessor& processor,
16 bool full_size)
17{
18 if (src.empty() || kernel.empty())
19 return std::vector<double>(src.size(), 0.0);
20
21 const size_t conv_len = src.size() + kernel.size() - 1;
22 const size_t fft_size = std::bit_ceil(std::max(size_t { 256 }, conv_len));
23 const size_t bins = fft_size / 2 + 1;
24
25 Eigen::VectorXd padded_src = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(fft_size));
26 Eigen::VectorXd padded_kernel = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(fft_size));
27 std::ranges::copy(src, padded_src.begin());
28 std::ranges::copy(kernel, padded_kernel.begin());
29
30 Eigen::FFT<double> fft;
31 Eigen::VectorXcd src_fft, ker_fft;
32 fft.fwd(src_fft, padded_src);
33 fft.fwd(ker_fft, padded_kernel);
34
35 std::vector<std::complex<double>> sig(bins), ker(bins), res(bins);
36 P::for_each(P::par_unseq,
37 std::views::iota(size_t { 0 }, bins).begin(),
38 std::views::iota(size_t { 0 }, bins).end(),
39 [&](size_t b) {
40 sig[b] = src_fft(static_cast<Eigen::Index>(b));
41 ker[b] = ker_fft(static_cast<Eigen::Index>(b));
42 });
43
44 processor(sig, ker, res);
45
46 Eigen::VectorXcd full_fft(static_cast<Eigen::Index>(fft_size));
47 P::for_each(P::par_unseq,
48 std::views::iota(size_t { 0 }, fft_size).begin(),
49 std::views::iota(size_t { 0 }, fft_size).end(),
50 [&](size_t b) {
51 full_fft(static_cast<Eigen::Index>(b)) = (b < bins)
52 ? res[b]
53 : std::conj(res[fft_size - b]);
54 });
55
56 const double inv = 1.0 / static_cast<double>(fft_size);
57 full_fft *= inv;
58
59 Eigen::VectorXd time_result;
60 fft.inv(time_result, full_fft);
61
62 const size_t out_len = full_size ? conv_len : src.size();
63 std::vector<double> out(out_len);
64 P::for_each(P::par_unseq,
65 std::views::iota(size_t { 0 }, out_len).begin(),
66 std::views::iota(size_t { 0 }, out_len).end(),
67 [&](size_t i) { out[i] = time_result(static_cast<Eigen::Index>(i)); });
68 return out;
69}
70
71std::vector<double> convolve(std::span<const double> src, std::span<const double> ir)
72{
73 if (ir.size() == 1) {
74 std::vector<double> out(src.size());
75 const double g = ir[0];
76 P::transform(P::par_unseq, src.begin(), src.end(), out.begin(),
77 [g](double x) { return x * g; });
78 return out;
79 }
80
81 return apply_convolution(src, ir,
82 [](const auto& s, const auto& k, auto& r) {
83 P::transform(P::par_unseq, s.begin(), s.end(), k.begin(), r.begin(),
84 [](const std::complex<double>& a, const std::complex<double>& b) {
85 return a * b;
86 });
87 });
88}
89
90std::vector<double> cross_correlate(
91 std::span<const double> src,
92 std::span<const double> tmpl,
93 bool normalize)
94{
95 std::vector<double> reversed(tmpl.rbegin(), tmpl.rend());
96
97 auto out = apply_convolution(src, reversed,
98 [](const auto& s, const auto& k, auto& r) {
99 P::transform(P::par_unseq, s.begin(), s.end(), k.begin(), r.begin(),
100 [](const std::complex<double>& a, const std::complex<double>& b) {
101 return a * std::conj(b);
102 });
103 });
104
105 if (normalize && !out.empty()) {
106 const auto [mn, mx] = std::ranges::minmax_element(out);
107 const double peak = std::max(std::abs(*mn), std::abs(*mx));
108 if (peak > 0.0) {
109 P::transform(P::par_unseq, out.begin(), out.end(), out.begin(),
110 [peak](double v) { return v / peak; });
111 }
112 }
113
114 return out;
115}
116
117std::vector<double> matched_filter(
118 std::span<const double> src,
119 std::span<const double> reference)
120{
121 return cross_correlate(src, reference, true);
122}
123
124std::vector<double> deconvolve(
125 std::span<const double> src,
126 std::span<const double> ir,
127 double regularization)
128{
129 return apply_convolution(src, ir,
130 [regularization](const auto& s, const auto& k, auto& r) {
131 P::transform(P::par_unseq, s.begin(), s.end(), k.begin(), r.begin(),
132 [regularization](const std::complex<double>& sig, const std::complex<double>& ker) {
133 const double mag_sq = std::norm(ker);
134 if (mag_sq < regularization)
135 return std::complex<double> {};
136 return sig * std::conj(ker) / (mag_sq + regularization);
137 });
138 });
139}
140
141std::vector<double> auto_correlate(std::span<const double> src, bool normalize)
142{
143 auto out = apply_convolution(src, src,
144 [](const auto& s, const auto&, auto& r) {
145 P::transform(P::par_unseq, s.begin(), s.end(), s.begin(), r.begin(),
146 [](const std::complex<double>& a, const std::complex<double>& b) {
147 return a * std::conj(b);
148 });
149 });
150
151 if (normalize && !out.empty()) {
152 const double peak = *std::max_element(out.begin(), out.end());
153 if (peak > 0.0) {
154 P::transform(P::par_unseq, out.begin(), out.end(), out.begin(),
155 [peak](double v) { return v / peak; });
156 }
157 }
158
159 return out;
160}
161
162} // namespace MayaFlux::Kinesis::Discrete
size_t a
size_t b
std::vector< double > cross_correlate(std::span< const double > src, std::span< const double > tmpl, bool normalize)
Cross-correlation via FFT.
std::vector< double > peak(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Peak amplitude per window.
Definition Analysis.cpp:51
std::vector< double > deconvolve(std::span< const double > src, std::span< const double > ir, double regularization)
Frequency-domain deconvolution with Tikhonov regularisation.
std::vector< double > apply_convolution(std::span< const double > src, std::span< const double > kernel, const ConvolutionProcessor &processor, bool full_size)
Core FFT convolution engine.
std::function< void(const std::vector< std::complex< double > > &signal_fft, const std::vector< std::complex< double > > &kernel_fft, std::vector< std::complex< double > > &result_fft)> ConvolutionProcessor
Frequency-domain processor callback for apply_convolution.
void normalize(std::span< double > data, double target_min, double target_max) noexcept
Normalize to [target_min, target_max] in-place Single-pass min/max reduction followed by a single tra...
Definition Transform.cpp:61
std::vector< double > auto_correlate(std::span< const double > src, bool normalize)
Auto-correlation via FFT.
std::vector< double > convolve(std::span< const double > src, std::span< const double > ir)
Linear FFT convolution.
std::vector< double > matched_filter(std::span< const double > src, std::span< const double > reference)
Matched filter (normalised cross-correlation)
std::vector< double > reversed(const std::vector< double > &data)
Reverse time order of single-channel data (non-destructive)
Definition Yantra.cpp:651
void normalize(std::vector< double > &data, double target_peak)
Normalize single-channel data to specified peak level (in-place)
Definition Yantra.cpp:560
double peak(const std::vector< double > &data)
Find peak amplitude in single-channel data.
Definition Yantra.cpp:216