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