BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
RLDeconvolutor.h
Go to the documentation of this file.
1/** @file RLDeconvolutor.h
2 @class RLDeconvolutor
3 @author Nikolay Karpushkin (nkarpushkin@mail.ru)
4 @brief Class to apply Richardson-Lucy deconvolution
5*/
6
7#ifndef RLDeconvolutor_H
8#define RLDeconvolutor_H
9
10#include "FairLogger.h"
11
12#include <algorithm>
13#include <cassert>
14#include <cmath>
15#include <iostream>
16#include <numeric>
17#include <vector>
18
20{
21 public:
22 RLDeconvolutor(const std::vector<float>& y,
23 const std::vector<float>& kernel_,
24 const float R2threshold_,
25 const int max_iterations_)
27 , kernel(kernel_)
28 , reversed_kernel(kernel_)
29 , R2()
30 , R2threshold(R2threshold_)
31 , max_iterations(max_iterations_)
32 , converged(false)
33 {
34 deconvolved.resize(y.size(), 1.0);
35 std::reverse(reversed_kernel.begin(), reversed_kernel.end());
36 }
37
38 void setR2threshold(float thr) { R2threshold = thr; }
40
42 {
43 R2.clear();
44 auto next_step = convolution(deconvolved, kernel);
45 for (int iter = 0; iter < max_iterations; ++iter) {
46 // constrain operator is needed to make the deconvolution procedure more stable. Excludes overfit to noise
47 LOG(debug4) << "RLDeconvolutor : calculating error " << iter;
48 std::vector<float> error = constrainOperator(elementwiseDivision(blurred_signal, next_step));
49 LOG(debug4) << "RLDeconvolutor : recalculating deconvolved " << iter;
51 next_step = convolution(deconvolved, kernel);
52
53 R2.push_back(R2score(blurred_signal, next_step));
54 if (R2.back() > R2default)
55 R2.back() = R2default;
56 LOG(debug4) << "RLDeconvolutor : estimated error R2 " << R2.back();
57
58 // converged
59 if (R2.back() < R2threshold) {
60 LOG(debug4) << "RLDeconvolutor : coverged at iter " << iter;
61 converged = true;
62 break;
63 }
64 }
65
66 if (!converged)
67 LOG(debug4) << "RLDeconvolutor : did not converge.";
68
69 // Find minima between maxima
70 LOG(debug4) << "RLDeconvolutor : findCamelPoints";
72
73 // Extract signal positions and amplitudes
74 LOG(debug4) << "RLDeconvolutor : extracting";
76
77 return converged;
78 }
79
80 // Individual signals -- pairs of position and amplitude
81 const std::vector<std::pair<size_t, float>>& getSignalInfo() const { return signal_info; }
82
83 // Individual signals -- deconvolved ~ delta-functions
84 const std::vector<std::vector<float>>& getSignalDeconvolution() const { return signal_deconvolved; }
85
86 // Individual signals -- reconvolved
87 const std::vector<std::vector<float>>& getSignalDecomposition() const { return signal_decomposed; }
88
89 std::vector<float> getWfmApproximation() const { return convolution(deconvolved, kernel); }
90
91 const std::vector<float>& getR2() const { return R2; }
92
93 private:
94 std::vector<float> blurred_signal; // original data with possible pileup etc. Tested to work well with positive
95 // data with baseline at zero
96 std::vector<float> deconvolved; // deconvolved (delta-functions)
97 std::vector<float> kernel; // kernel of convolution (PSF or Ethalon signal)
98 std::vector<float> reversed_kernel;
99 std::vector<float> R2;
100 float R2threshold;
101 inline static constexpr float R2default = 2.0;
102 int max_iterations;
103 bool converged;
104
105 std::vector<size_t> camel_points; // in case of pileup it is the minimum between two succeding maxima
106 std::vector<std::pair<size_t, float>> signal_info; // signal positions and amplitudes
107 std::vector<std::vector<float>> signal_deconvolved; // initial waveform is deconvolved
108 std::vector<std::vector<float>> signal_decomposed; // initial waveform is decomposed into vector of signals
109
110 std::vector<float> convolution(const std::vector<float>& signal, const std::vector<float>& kern) const
111 {
112 // convolution like numpy.convolve with 'same' option.
113 // TODO think about FFT for large-time signals.
114 size_t signal_size = signal.size();
115 size_t kernel_size = kern.size();
116 size_t padding = kernel_size / 2; // Padding for both sides
117
118 std::vector<float> padded_signal(signal_size + 2 * padding, 0.0);
119 std::copy(signal.begin(), signal.end(), padded_signal.begin() + padding);
120
121 std::vector<float> result(signal_size, 0.0);
122
123 for (size_t i = 0; i < signal_size; ++i)
124 for (size_t j = 0; j < kernel_size; ++j)
125 result.at(i) += padded_signal.at(i + kernel_size - 1 - j) * kern.at(j);
126
127 return result;
128 }
129
130 std::vector<float> constrainOperator(const std::vector<float>& data_array)
131 {
132 std::vector<float> out_array(data_array.size(), 0.0);
133
134 // Lambda function to define the condition (positivity)
135 auto condition = [](float number) { return number > 0.0; };
136
137 // Apply the condition
138 for (size_t i = 0; i < data_array.size(); ++i) {
139 if (condition(data_array.at(i)))
140 out_array.at(i) = data_array.at(i);
141 else
142 out_array.at(i) = 0.0;
143 }
144 return out_array;
145 }
146
147 std::vector<float> elementwiseMultiplication(const std::vector<float>& vec1, const std::vector<float>& vec2) const
148 {
149 std::vector<float> result(vec1.size());
150 assert(vec1.size() == vec2.size());
151
152 for (size_t i = 0; i < vec1.size(); ++i)
153 result.at(i) = vec1.at(i) * vec2.at(i);
154
155 return result;
156 }
157
158 std::vector<float> elementwiseDivision(const std::vector<float>& vec1, const std::vector<float>& vec2) const
159 {
160 std::vector<float> result(vec1.size());
161 assert(vec1.size() == vec2.size());
162
163 for (size_t i = 0; i < vec1.size(); ++i)
164 result.at(i) = vec1.at(i) / (vec2.at(i) + std::numeric_limits<float>::min());
165
166 return result;
167 }
168
169 float R2score(const std::vector<float>& true_values, const std::vector<float>& predicted_values) const
170 {
171 assert(true_values.size() == predicted_values.size());
172 if (true_values.empty())
173 return R2default;
174
175 float mean_true = std::accumulate(true_values.begin(), true_values.end(), 0.0) / true_values.size();
176 float ss_total = 0.0;
177 float ss_residual = 0.0;
178
179 for (size_t i = 0; i < true_values.size(); ++i) {
180 ss_total += std::pow(true_values.at(i) - mean_true, 2);
181 ss_residual += std::pow(true_values.at(i) - predicted_values.at(i), 2);
182 }
183
184 return ss_residual / (ss_total + std::numeric_limits<float>::min());
185 }
186
187 std::vector<size_t> findMaximaIndices(const std::vector<float>& vec) const
188 {
189 std::vector<size_t> maxima_indices;
190 if (vec.empty())
191 return maxima_indices;
192
193 for (size_t i = 1; i < vec.size() - 1; ++i) {
194 if (vec.at(i) > vec.at(i - 1) && vec.at(i) > vec.at(i + 1)) {
195 maxima_indices.push_back(i);
196 }
197 }
198
199 return maxima_indices;
200 }
201
202 std::vector<size_t> findCamelPoints() const
203 {
204 std::vector<size_t> maxima_indices = findMaximaIndices(deconvolved);
205 std::vector<size_t> c_points;
206
207 LOG(debug4) << "RLDeconvolutor : findCamelPoints between total maxima " << maxima_indices.size();
208 if (!maxima_indices.empty()) {
209 for (size_t i = 0; i < maxima_indices.size() - 1; ++i) {
210 // Find the minimum value between each pair of maxima
211 auto camel_area = getSubvector(deconvolved, maxima_indices.at(i), maxima_indices.at(i + 1));
212 if (!camel_area.empty()) {
213 size_t camel_point =
214 maxima_indices.at(i)
215 + std::distance(camel_area.begin(), std::min_element(camel_area.begin(), camel_area.end()));
216 c_points.push_back(camel_point);
217 }
218 }
219 }
220 c_points.insert(c_points.begin(), 0);
221 c_points.push_back(deconvolved.size() - 1);
222 for (auto it : c_points)
223 LOG(debug4) << "RLDeconvolutor : found CamelPoints " << it;
224
225 return c_points;
226 }
227
228 std::vector<float> getSubvector(const std::vector<float>& vec, size_t start, size_t end) const
229 {
230 return std::vector<float>(vec.begin() + start, vec.begin() + end);
231 }
232
234 {
235 signal_info.clear();
236 if (camel_points.empty())
237 return;
238
239 // Process regions between camel points
240 for (size_t i = 0; i < camel_points.size() - 1; ++i) {
241 int start_index = camel_points.at(i);
242 int end_index = camel_points.at(i + 1);
243
244 // Get the subvector corresponding to the region
245 std::vector<float> subvector = getSubvector(deconvolved, start_index, end_index);
246 std::vector<float> thisVector(deconvolved.size(), 0.0);
247 std::copy(subvector.begin(), subvector.end(), thisVector.begin() + start_index);
248 signal_deconvolved.push_back(thisVector);
249
250 // Convolve the subvector with the kernel to obtain the reconstructed signal
251 std::vector<float> reconstructed_signal = convolution(thisVector, kernel);
252 signal_decomposed.push_back(reconstructed_signal);
253
254 // Find the maximum amplitude and its position in the reconstructed signal
255 auto max_amplitude_iter = std::max_element(reconstructed_signal.begin(), reconstructed_signal.end());
256 float max_amplitude = *max_amplitude_iter;
257 int max_amplitude_position = std::distance(reconstructed_signal.begin(), max_amplitude_iter);
258
259 // Store the result in the signal_info vector
260 signal_info.push_back(std::make_pair(max_amplitude_position, max_amplitude));
261 }
262 }
263};
264
265#endif
int i
Definition P4_F32vec4.h:22
friend F32vec4 max(const F32vec4 &a, const F32vec4 &b)
Definition P4_F32vec4.h:31
Class to apply Richardson-Lucy deconvolution.
const std::vector< float > & getR2() const
RLDeconvolutor(const std::vector< float > &y, const std::vector< float > &kernel_, const float R2threshold_, const int max_iterations_)
std::vector< float > getWfmApproximation() const
std::vector< std::vector< float > > signal_deconvolved
const std::vector< std::vector< float > > & getSignalDeconvolution() const
float R2score(const std::vector< float > &true_values, const std::vector< float > &predicted_values) const
bool runDeconvolution()
std::vector< float > constrainOperator(const std::vector< float > &data_array)
std::vector< std::vector< float > > signal_decomposed
std::vector< float > getSubvector(const std::vector< float > &vec, size_t start, size_t end) const
std::vector< float > blurred_signal
std::vector< float > elementwiseMultiplication(const std::vector< float > &vec1, const std::vector< float > &vec2) const
std::vector< float > deconvolved
std::vector< float > reversed_kernel
std::vector< float > elementwiseDivision(const std::vector< float > &vec1, const std::vector< float > &vec2) const
std::vector< float > R2
std::vector< float > convolution(const std::vector< float > &signal, const std::vector< float > &kern) const
std::vector< float > kernel
std::vector< std::pair< size_t, float > > signal_info
const std::vector< std::pair< size_t, float > > & getSignalInfo() const
std::vector< size_t > findCamelPoints() const
static constexpr float R2default
const std::vector< std::vector< float > > & getSignalDecomposition() const
void setMaxIterations(int max)
std::vector< size_t > findMaximaIndices(const std::vector< float > &vec) const
std::vector< size_t > camel_points
void setR2threshold(float thr)