5#include <unsupported/Eigen/FFT>
16template <
typename OperationFunc>
18 std::span<const double> data_span,
19 std::span<const double> kernel,
20 OperationFunc&& operation,
21 bool return_full_size =
true)
23 if (data_span.empty() || kernel.empty()) {
24 if (return_full_size && !data_span.empty() && !kernel.empty()) {
25 return std::vector<double>(data_span.size() + kernel.size() - 1, 0.0);
27 return std::vector<double>(data_span.size(), 0.0);
31 size_t conv_size = data_span.size() + kernel.size() - 1;
33 size_t fft_size = std::max(256UZ, conv_size);
34 fft_size = 1 << (32 - std::countl_zero(fft_size - 1));
36 Eigen::VectorXd padded_input = Eigen::VectorXd::Zero(fft_size);
37 Eigen::VectorXd padded_kernel = Eigen::VectorXd::Zero(fft_size);
39 std::ranges::copy(data_span, padded_input.begin());
40 std::ranges::copy(kernel, padded_kernel.begin());
42 Eigen::FFT<double> fft;
43 Eigen::VectorXcd input_fft, kernel_fft;
44 fft.fwd(input_fft, padded_input);
45 fft.fwd(kernel_fft, padded_kernel);
47 Eigen::VectorXcd result_fft(fft_size);
48 operation(input_fft, kernel_fft, result_fft);
50 double scale_factor = 1.0 / fft_size;
51 std::ranges::transform(result_fft, result_fft.begin(),
52 [scale_factor](std::complex<double>& c) {
53 return c * scale_factor;
56 Eigen::VectorXd time_result;
57 fft.inv(time_result, result_fft);
59 if (return_full_size) {
60 std::vector<double> result(conv_size);
61 std::ranges::copy(time_result | std::views::take(conv_size), result.begin());
64 std::vector<double> result(data_span.size());
65 std::ranges::copy(time_result | std::views::take(data_span.size()), result.begin());
78template <OperationReadyData DataType>
83 for (
auto& span : target_data) {
84 auto fir_filter = std::make_shared<Nodes::Filters::FIR>(
nullptr, impulse_response);
85 std::vector<double> output;
86 output.reserve(span.size());
87 for (
double sample : span) {
88 output.push_back(fir_filter->process_sample(sample));
90 std::copy(output.begin(), output.end(), span.begin());
93 auto reconstructed_data = target_data
94 | std::views::transform([](
const auto& span) {
95 return std::vector<double>(span.begin(), span.end());
97 | std::ranges::to<std::vector>();
99 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
111template <OperationReadyData DataType>
112DataType
transform_convolve_with_fir(DataType& input,
const std::vector<double>& impulse_response, std::vector<std::vector<double>>& working_buffer)
116 working_buffer.clear();
117 working_buffer.resize(target_data.size());
119 for (
size_t i = 0; i < target_data.size(); ++i) {
120 auto fir_filter = std::make_shared<Nodes::Filters::FIR>(
nullptr, impulse_response);
121 working_buffer[i].reserve(target_data[i].size());
122 for (
double sample : target_data[i]) {
123 working_buffer[i].push_back(fir_filter->process_sample(sample));
127 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
137template <OperationReadyData DataType>
140 if (impulse_response.size() == 1) {
141 if (std::abs(impulse_response[0] - 1.0) < 1e-15) {
146 std::vector<std::vector<double>> reconstructed_data(target_data.size());
148 for (
size_t i = 0; i < target_data.size(); ++i) {
149 std::ranges::transform(target_data[i], reconstructed_data[i].begin(),
150 [gain = impulse_response[0]](
double x) {
return x * gain; });
152 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
158 auto convolution_op = [](
const Eigen::VectorXcd& input_fft,
159 const Eigen::VectorXcd& kernel_fft,
160 Eigen::VectorXcd& result_fft) {
161 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
162 [](
const std::complex<double>& a,
const std::complex<double>& b) {
167 for (
auto& span : target_data) {
168 auto result =
fft_convolve_helper(span, std::span<const double>(impulse_response), convolution_op,
false);
169 std::copy(result.begin(), result.end(), span.begin());
172 auto reconstructed_data = target_data
173 | std::views::transform([](
const auto& span) {
174 return std::vector<double>(span.begin(), span.end());
176 | std::ranges::to<std::vector>();
178 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
189template <OperationReadyData DataType>
190DataType
transform_convolve(DataType& input,
const std::vector<double>& impulse_response, std::vector<std::vector<double>>& working_buffer)
192 if (impulse_response.size() == 1) {
193 if (std::abs(impulse_response[0] - 1.0) < 1e-15) {
197 for (
size_t i = 0; i < target_data.size(); ++i) {
198 std::ranges::transform(target_data[i], working_buffer[i].begin(),
199 [gain = impulse_response[0]](
double x) {
return x * gain; });
201 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
207 working_buffer.clear();
208 working_buffer.resize(target_data.size());
210 auto convolution_op = [](
const Eigen::VectorXcd& input_fft,
211 const Eigen::VectorXcd& kernel_fft,
212 Eigen::VectorXcd& result_fft) {
213 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
214 [](
const std::complex<double>& a,
const std::complex<double>& b) {
219 for (
size_t i = 0; i < target_data.size(); ++i) {
220 working_buffer[i] =
fft_convolve_helper(target_data[i], std::span<const double>(impulse_response), convolution_op,
false);
223 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
234template <OperationReadyData DataType>
239 std::vector<double> reversed_template(template_signal.rbegin(), template_signal.rend());
241 auto correlation_op = [](
const Eigen::VectorXcd& input_fft,
242 const Eigen::VectorXcd& kernel_fft,
243 Eigen::VectorXcd& result_fft) {
244 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
245 [](
const std::complex<double>& a,
const std::complex<double>& b) {
246 return a * std::conj(b);
250 for (
auto& span : target_data) {
254 auto [min_it, max_it] = std::ranges::minmax_element(result);
255 double max_abs = std::max(std::abs(*min_it), std::abs(*max_it));
257 std::ranges::transform(result, result.begin(),
258 [max_abs](
double val) { return val / max_abs; });
262 std::copy(result.begin(), result.begin() + std::min(result.size(), span.size()), span.begin());
265 auto reconstructed_data = target_data
266 | std::views::transform([](
const auto& span) {
267 return std::vector<double>(span.begin(), span.end());
269 | std::ranges::to<std::vector>();
271 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
283template <OperationReadyData DataType>
288 std::vector<double> reversed_template(template_signal.rbegin(), template_signal.rend());
290 auto correlation_op = [](
const Eigen::VectorXcd& input_fft,
291 const Eigen::VectorXcd& kernel_fft,
292 Eigen::VectorXcd& result_fft) {
293 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
294 [](
const std::complex<double>& a,
const std::complex<double>& b) {
295 return a * std::conj(b);
299 for (
size_t i = 0; i < target_data.size(); ++i) {
303 auto [min_it, max_it] = std::ranges::minmax_element(result);
304 double max_abs = std::max(std::abs(*min_it), std::abs(*max_it));
306 std::ranges::transform(result, result.begin(),
307 [max_abs](
double val) { return val / max_abs; });
311 working_buffer[i].resize(result.size());
312 working_buffer[i] = std::move(result);
315 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
325template <OperationReadyData DataType>
333 auto correlation_op = [](
const Eigen::VectorXcd& input_fft,
334 const Eigen::VectorXcd& ,
335 Eigen::VectorXcd& result_fft) {
336 std::ranges::transform(input_fft, input_fft, result_fft.begin(),
337 [](
const std::complex<double>& a,
const std::complex<double>& b) {
338 return a * std::conj(b);
342 for (
auto& span : target_data) {
343 std::vector<double> signal_copy(span.begin(), span.end());
347 double max_val = *std::max_element(result.begin(), result.end());
349 std::ranges::transform(result, result.begin(),
350 [max_val](
double val) { return val / max_val; });
354 std::copy(result.begin(), result.begin() + std::min(result.size(), span.size()), span.begin());
357 auto reconstructed_data = target_data
358 | std::views::transform([](
const auto& span) {
359 return std::vector<double>(span.begin(), span.end());
361 | std::ranges::to<std::vector>();
363 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
374template <OperationReadyData DataType>
379 auto correlation_op = [](
const Eigen::VectorXcd& input_fft,
380 const Eigen::VectorXcd&,
381 Eigen::VectorXcd& result_fft) {
382 std::ranges::transform(input_fft, input_fft, result_fft.begin(),
383 [](
const std::complex<double>& a,
const std::complex<double>& b) {
384 return a * std::conj(b);
388 for (
size_t i = 0; i < target_data.size(); ++i) {
389 std::vector<double> signal_copy(target_data[i].begin(), target_data[i].end());
393 double max_val = *std::max_element(result.begin(), result.end());
395 std::ranges::transform(result, result.begin(),
396 [max_val](
double val) { return val / max_val; });
400 working_buffer[i].resize(result.size());
401 working_buffer[i] = std::move(result);
404 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
414template <OperationReadyData DataType>
428template <OperationReadyData DataType>
429DataType
transform_matched_filter(DataType& input,
const std::vector<double>& reference_signal, std::vector<std::vector<double>>& working_buffer)
443template <OperationReadyData DataType>
444DataType
transform_deconvolve(DataType& input,
const std::vector<double>& impulse_to_remove,
double regularization = 1e-6)
448 auto deconvolution_op = [regularization](
const Eigen::VectorXcd& input_fft,
449 const Eigen::VectorXcd& kernel_fft,
450 Eigen::VectorXcd& result_fft) {
451 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
452 [regularization](
const std::complex<double>& signal,
const std::complex<double>& kernel) {
453 double magnitude_sq = std::norm(kernel);
454 if (magnitude_sq < regularization) {
455 return std::complex<double>(0.0, 0.0);
457 return signal * std::conj(kernel) / (magnitude_sq + regularization);
461 for (
auto& span : target_data) {
463 std::copy(result.begin(), result.begin() + std::min(result.size(), span.size()), span.begin());
466 auto reconstructed_data = target_data
467 | std::views::transform([](
const auto& span) {
468 return std::vector<double>(span.begin(), span.end());
470 | std::ranges::to<std::vector>();
472 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
485template <OperationReadyData DataType>
486DataType
transform_deconvolve(DataType& input,
const std::vector<double>& impulse_to_remove,
double regularization, std::vector<std::vector<double>>& working_buffer)
488 auto [target_data, structure_info] = OperationHelper::setup_operation_buffer(input, working_buffer);
490 auto deconvolution_op = [regularization](
const Eigen::VectorXcd& input_fft,
491 const Eigen::VectorXcd& kernel_fft,
492 Eigen::VectorXcd& result_fft) {
493 std::ranges::transform(input_fft, kernel_fft, result_fft.begin(),
494 [regularization](
const std::complex<double>& signal,
const std::complex<double>& kernel) {
495 double magnitude_sq = std::norm(kernel);
496 if (magnitude_sq < regularization) {
497 return std::complex<double>(0.0, 0.0);
499 return signal * std::conj(kernel) / (magnitude_sq + regularization);
503 working_buffer.clear();
504 working_buffer.resize(target_data.size());
506 for (
size_t i = 0; i < target_data.size(); ++i) {
507 working_buffer[i] = fft_convolve_helper(target_data[i], impulse_to_remove, deconvolution_op);
510 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
static std::tuple< std::vector< std::span< double > >, DataStructureInfo > extract_structured_double(T &compute_data)
Extract structured double data from IO container or direct ComputeData with automatic container handl...
static auto setup_operation_buffer(T &input, std::vector< std::vector< double > > &working_buffer)
Setup operation buffer from IO or ComputeData type.
DataType transform_auto_correlate_fft(DataType &input, bool normalize=true)
Auto-correlation using FFT (IN-PLACE)
DataType transform_matched_filter(DataType &input, const std::vector< double > &reference_signal)
Matched filter using cross-correlation for signal detection (IN-PLACE)
DataType transform_deconvolve(DataType &input, const std::vector< double > &impulse_to_remove, double regularization=1e-6)
Deconvolution using frequency domain division (IN-PLACE) Useful for removing known impulse responses.
std::vector< double > fft_convolve_helper(std::span< const double > data_span, std::span< const double > kernel, OperationFunc &&operation, bool return_full_size=true)
Common FFT convolution helper to eliminate code duplication.
DataType transform_convolve_with_fir(DataType &input, const std::vector< double > &impulse_response)
Direct convolution using existing FIR filter infrastructure (IN-PLACE) This leverages the existing,...
DataType transform_cross_correlate(DataType &input, const std::vector< double > &template_signal, bool normalize=true)
Cross-correlation using FFT (convolution with time-reversed impulse) (IN-PLACE)
DataType transform_convolve(DataType &input, const std::vector< double > &impulse_response)
Convolution transformation using existing infrastructure with C++20 ranges (IN-PLACE)
void normalize(std::vector< double > &data, double target_peak)
Normalize single-channel data to specified peak level (in-place)