sablib
Loading...
Searching...
No Matches
beads.cpp
Go to the documentation of this file.
1
8
9/*
10Original License:
11Copyright (c) 2018, Laurent Duval
12All rights reserved.
13
14Redistribution and use in source and binary forms, with or without
15modification, are permitted provided that the following conditions are met:
16
17* Redistributions of source code must retain the above copyright notice, this
18 list of conditions and the following disclaimer.
19
20* Redistributions in binary form must reproduce the above copyright notice,
21 this list of conditions and the following disclaimer in the documentation
22 and/or other materials provided with the distribution
23* Neither the name of IFP Energies nouvelles nor the names of its
24 contributors may be used to endorse or promote products derived from this
25 software without specific prior written permission.
26THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
30FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36*/
37
38#define _USE_MATH_DEFINES
39#include <algorithm>
40#include <cmath>
41#include <functional>
42
43#include "../misc/convolve.h"
44#include "../misc/diff.h"
45#include "../misc/spdiags.h"
46
47#include "beads.h"
48
49namespace sablib {
50
51namespace {
52
53const std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >
54BAfilt(const unsigned int d, const double frequency, const unsigned int length)
55{
56 Eigen::VectorXd a(1), b, b1(2), v(3), v2(2);
57 double omega_c = 2 * M_PI * frequency;
58 double t;
59
60 b1 << 1, -1;
61 v << -1, 2, -1;
62
63 for(unsigned int i = 1; i < d; i++){
64 b1 = Convolve(b1, v).eval();
65 }
66
67 v2 << -1, 1;
68 b = Convolve(b1, v2);
69
70 v << 1, 2, 1;
71 a << 1;
72
73 for(unsigned int i = 0; i < d; i++){
74 a = Convolve(a, v).eval();
75 }
76
77 t = std::pow((1 - std::cos(omega_c)) / (1 + std::cos(omega_c)), d);
78 a = (b + t * a).eval();
79
80 Eigen::MatrixXd xa(a.size(), length);
81 Eigen::MatrixXd xb(b.size(), length);
82
83 for(unsigned int i = 0; i < length; i++) {
84 xa.block(0, i, a.size(), 1) = a;
85 xb.block(0, i, b.size(), 1) = b;
86 }
87
88 Eigen::VectorXi dr = Eigen::VectorXi::LinSpaced(2 * d + 1, -d, d);
89 Eigen::SparseMatrix<double> A = Spdiags(xa, dr, length, length);
90 Eigen::SparseMatrix<double> B = Spdiags(xb, dr, length, length);
91
92 return std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >(A, B);
93}
94
95}; // unnamed namespace
96
97//
98// Implementation of BaselineBeads() function
99//
100const std::tuple< std::vector<double>, std::vector<double> >
102 const std::vector<double> & y, const unsigned int s, const double frequency, const double r,
103 const double lambda0, const double lambda1, const double lambda2,
104 const unsigned int loop, const double eps, const BeadsPenalty penalty
105)
106{
107 if(y.size() == 0) {
108 throw std::invalid_argument("BaselineBeads(): the length of y is zero.");
109 }
110
111 if(s == 0 || s > 3) {
112 throw std::invalid_argument("BaselineBeads(): s must be 1, 2 or 3.");
113 }
114
115 if(frequency <= 0) {
116 throw std::invalid_argument("BaselineBeads(): non-positive frequency value is given.");
117 }
118
119 if(r <= 0) {
120 throw std::invalid_argument("BaselineBeads(): non-positive r value is given.");
121 }
122
123 if(lambda0 <= 0 || lambda1 <= 0 || lambda2 <= 0) {
124 throw std::invalid_argument("BaselineBeads(): non-positive lambda value is given.");
125 }
126
127 if(loop == 0) {
128 throw std::invalid_argument("BaselineBeads(): loop is zero.");
129 }
130
131 if(eps <= 0) {
132 throw std::invalid_argument("BaselineBeads(): non-positive eps value is given.");
133 }
134
135 const double eps0 = 1e-6;
136 const double eps1 = 1e-6;
137
138 Eigen::VectorXd yy = Eigen::VectorXd::Map(y.data(), y.size());
139
140 std::function<double(const double)> phi, wfun;
141
142 switch(penalty) {
144 phi = [&](const double xx) {
145 double abs_x = std::fabs(xx);
146 return std::sqrt(abs_x * abs_x + eps1);
147 };
148 wfun = [&](const double xx) {
149 return 1 / phi(xx);
150 };
151 break;
153 phi = [&](const double xx) {
154 double abs_x = std::fabs(xx);
155 return abs_x - eps1 * std::log(abs_x + eps1);
156 };
157 wfun = [&](const double xx) {
158 return 1 / (std::fabs(xx) + eps1);
159 };
160 break;
161 default:
162 throw std::invalid_argument("invalid penalty function type.");
163 }
164
165 auto theta = [&](const double xx) {
166 if(xx > eps0) {
167 return xx;
168 }
169 else if(xx < -eps0) {
170 return -r * xx;
171 }
172 else {
173 return (1 + r) * xx * xx / (4 * eps0) + (1 - r) * xx / 2 + eps0 * (1 + r) / 4;
174 }
175 };
176
177 int length = yy.size();
178 auto [ A, B ] = BAfilt(s, frequency, length);
179
180 Eigen::SparseMatrix<double> I, D1, D2;
181
182 I.resize(length, length);
183 I.setIdentity();
184 D1 = Diff(I);
185 D2 = Diff(I, 2);
186
187 Eigen::SparseLU< Eigen::SparseMatrix<double> > solverA, solverQ;
188
189 solverA.compute(A);
190
191 if(solverA.info() != Eigen::Success) {
192 throw std::runtime_error("BaselineBeads(): solverA calculation fails.");
193 }
194
195 Eigen::SparseMatrix<double> BTB = B.transpose() * B;
196 Eigen::VectorXd b = Eigen::VectorXd::Constant(length, (1 - r) / 2);
197 Eigen::VectorXd d = BTB * (solverA.solve(yy)) - lambda0 * A.transpose() * b;
198 Eigen::VectorXd w1 = Eigen::VectorXd::Constant(length - 1, lambda1);
199 Eigen::VectorXd w2 = Eigen::VectorXd::Constant(length - 2, lambda2);
200
201 Eigen::VectorXd x = yy;
202
203 double prev_c = 0.5
204 * (B * solverA.solve(x)).array().square().sum()
205 + lambda0 * x.unaryExpr(theta).sum()
206 + lambda1 * (D1 * x).unaryExpr(phi).sum()
207 + lambda2 * (D2 * x).unaryExpr(phi).sum();
208
209 for(unsigned int i = 0; i < loop; i++) {
210 Eigen::SparseMatrix<double> L1, L2, G, M;
211
212 L1 = (w1.array() * (D1 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
213 L2 = (w2.array() * (D2 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
214
215 G = x.unaryExpr([&](const double xx) {
216 double z = (-eps0 <= xx && xx <= eps0) ? eps0 : std::fabs(xx);
217 return (1 + r) / 4 / z;
218 }).asDiagonal();
219
220 M = 2 * lambda0 * G + D1.transpose() * L1 * D1 + D2.transpose() * L2 * D2;
221 solverQ.compute(BTB + A.transpose() * M * A);
222
223 if(solverQ.info() != Eigen::Success) {
224 throw std::runtime_error("BaselineBeads(): solverQ calculation fails.");
225 }
226
227 x = A * solverQ.solve(d);
228
229 Eigen::VectorXd a = yy - x;
230 double c = 0.5
231 * (B * solverA.solve(a)).array().square().sum()
232 + lambda0 * x.unaryExpr(theta).sum()
233 + lambda1 * (D1 * x).unaryExpr(phi).sum()
234 + lambda2 * (D2 * x).unaryExpr(phi).sum();
235
236 if(std::fabs((prev_c - c) / c) < eps) {
237 break;
238 }
239
240 prev_c = c;
241 }
242
243 Eigen::VectorXd f = yy - x;
244 f = (f - B * solverA.solve(f)).eval();
245
246 std::vector<double> f_result(f.size()), x_result(x.size());
247
248 Eigen::VectorXd::Map(f_result.data(), f.size()) = f;
249 Eigen::VectorXd::Map(x_result.data(), x.size()) = x;
250
251 return std::tuple< std::vector<double>, std::vector<double> >(f_result, x_result);
252}
253
254//
255// Implementation of BeadsExpandBoundaries() function
256//
257const std::vector<double> BeadsExpandBoundaries(const std::vector<double> & y, const unsigned int n)
258{
259 if(y.size() == 0) {
260 throw std::invalid_argument("BeadsExpandBoundaries(): the length of y is zero.");
261 }
262
263 std::vector<double> result(y.size() + 2 * n);
264
265 auto it1 = result.begin();
266 auto it2 = result.rbegin();
267 for(unsigned int i = 0; i < n; i++, it1++, it2++) {
268 double x = (double)i / (n - 1);
269 double t = std::sqrt(x);
270 *it1 = y.front() * t;
271 *it2 = y.back() * t;
272 }
273
274 std::copy(y.begin(), y.end(), result.begin() + n);
275
276 return result;
277}
278
279//
280// Implementation of BeadsTrimBoundaries() function
281//
282const std::vector<double> BeadsTrimBoundaries(const std::vector<double> & y, const unsigned int n)
283{
284 if(y.size() == 0) {
285 throw std::invalid_argument("BeadsTrimBoundaries(): the length of y is zero.");
286 }
287
288 if(y.size() < 2 * n) {
289 throw std::invalid_argument("BeadsTrimBoundaries(): the length of y is too short.");
290 }
291
292 std::vector<double> result(y.size() - 2 * n);
293 auto it = y.begin() + n;
294
295 std::copy(it, it + result.size(), result.begin());
296
297 return result;
298}
299
300}; // namespace sablib
const std::tuple< std::vector< double >, std::vector< double > > BaselineBeads(const std::vector< double > &y, const unsigned int s, const double frequency, const double r, const double lambda0, const double lambda1, const double lambda2, const unsigned int loop, const double eps, const BeadsPenalty penalty)
Performs baseline estimation and denoising using Sparsity (BEADS).
Definition beads.cpp:101
const std::vector< double > BeadsTrimBoundaries(const std::vector< double > &y, const unsigned int n)
Trims the expanded signal boundaries.
Definition beads.cpp:282
const std::vector< double > BeadsExpandBoundaries(const std::vector< double > &y, const unsigned int n)
Expands the signal boundaries by padding with a tapered sequence.
Definition beads.cpp:257
Baseline estimation and subtraction using Baseline Estimation And Denoising using Sparsity(BEADS).
BeadsPenalty
Penalty types for the BEADS algorithm.(see Table 1 in Duval's paper).
Definition beads.h:25
Python's NumPy-like convolve() function.
const Derived1::PlainObject Convolve(const Eigen::MatrixBase< Derived1 > &v1, const Eigen::MatrixBase< Derived2 > &v2, const ConvolveMode mode=ConvolveMode::Full)
Returns the discrete, linear convolution of two one-dimensional sequences.
Definition convolve.h:35
MATLAB-like diff() function.
const Derived::PlainObject Diff(const Eigen::MatrixBase< Derived > &m0, const int n=1, const Dir dir=Dir::RowWise)
Calculates the n-th discrete difference along the given axis.
Definition diff.h:32
Python's SciPy-like spdiags() function.
Eigen::SparseMatrix< typename Derived::PlainObject::Scalar > Spdiags(const Eigen::MatrixBase< Derived > &data, const Eigen::VectorXi &diags, const int m=-1, const int n=-1)
Returns a sparse matrix with the specified elements on its diagonals.
Definition spdiags.h:30