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#include <tuple>
43
44#include "../misc/convolve.h"
45#include "../misc/diff.h"
46#include "../misc/spdiags.h"
47
48#include "beads.h"
49
50namespace sablib {
51
52namespace {
53
54const std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >
55BAfilt(const unsigned int d, const double frequency, const unsigned int length)
56{
57 Eigen::VectorXd a(1), b, b1(2), v(3), v2(2);
58 double omega_c = 2 * M_PI * frequency;
59 double t;
60
61 b1 << 1, -1;
62 v << -1, 2, -1;
63
64 for(unsigned int i = 1; i < d; i++){
65 b1 = Convolve(b1, v).eval();
66 }
67
68 v2 << -1, 1;
69 b = Convolve(b1, v2);
70
71 v << 1, 2, 1;
72 a << 1;
73
74 for(unsigned int i = 0; i < d; i++){
75 a = Convolve(a, v).eval();
76 }
77
78 t = std::pow((1 - std::cos(omega_c)) / (1 + std::cos(omega_c)), d);
79 a = (b + t * a).eval();
80
81 Eigen::MatrixXd xa(a.size(), length);
82 Eigen::MatrixXd xb(b.size(), length);
83
84 for(unsigned int i = 0; i < length; i++) {
85 xa.block(0, i, a.size(), 1) = a;
86 xb.block(0, i, b.size(), 1) = b;
87 }
88
89 Eigen::VectorXi dr = Eigen::VectorXi::LinSpaced(2 * d + 1, -d, d);
90 Eigen::SparseMatrix<double> A = Spdiags(xa, dr, length, length);
91 Eigen::SparseMatrix<double> B = Spdiags(xb, dr, length, length);
92
93 return std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >(A, B);
94}
95
96}; // unnamed namespace
97
98//
99// Implementation of BaselineBeads() function
100//
101const BaselineResult
103 const std::vector<double> & y, const unsigned int s, const double frequency, const double r,
104 const double lambda0, const double lambda1, const double lambda2,
105 const unsigned int loop, const double eps, const BeadsPenalty penalty
106)
107{
108 if(y.size() == 0) {
109 throw std::invalid_argument("BaselineBeads(): the length of y is zero.");
110 }
111
112 if(s == 0 || s > 3) {
113 throw std::invalid_argument("BaselineBeads(): s must be 1, 2 or 3.");
114 }
115
116 if(frequency <= 0) {
117 throw std::invalid_argument("BaselineBeads(): non-positive frequency value is given.");
118 }
119
120 if(r <= 0) {
121 throw std::invalid_argument("BaselineBeads(): non-positive r value is given.");
122 }
123
124 if(lambda0 <= 0 || lambda1 <= 0 || lambda2 <= 0) {
125 throw std::invalid_argument("BaselineBeads(): non-positive lambda value is given.");
126 }
127
128 if(loop == 0) {
129 throw std::invalid_argument("BaselineBeads(): loop is zero.");
130 }
131
132 if(eps <= 0) {
133 throw std::invalid_argument("BaselineBeads(): non-positive eps value is given.");
134 }
135
136 const double eps0 = 1e-6;
137 const double eps1 = 1e-6;
138
139 Eigen::VectorXd yy = Eigen::VectorXd::Map(y.data(), y.size());
140
141 std::function<double(const double)> phi, wfun;
142
143 switch(penalty) {
145 phi = [&](const double xx) {
146 double abs_x = std::fabs(xx);
147 return std::sqrt(abs_x * abs_x + eps1);
148 };
149 wfun = [&](const double xx) {
150 return 1 / phi(xx);
151 };
152 break;
154 phi = [&](const double xx) {
155 double abs_x = std::fabs(xx);
156 return abs_x - eps1 * std::log(abs_x + eps1);
157 };
158 wfun = [&](const double xx) {
159 return 1 / (std::fabs(xx) + eps1);
160 };
161 break;
162 default:
163 throw std::invalid_argument("invalid penalty function type.");
164 }
165
166 auto theta = [&](const double xx) {
167 if(xx > eps0) {
168 return xx;
169 }
170 else if(xx < -eps0) {
171 return -r * xx;
172 }
173 else {
174 return (1 + r) * xx * xx / (4 * eps0) + (1 - r) * xx / 2 + eps0 * (1 + r) / 4;
175 }
176 };
177
178 int length = yy.size();
179 auto [ A, B ] = BAfilt(s, frequency, length);
180
181 Eigen::SparseMatrix<double> I, D1, D2;
182
183 I.resize(length, length);
184 I.setIdentity();
185 D1 = Diff(I);
186 D2 = Diff(I, 2);
187
188 Eigen::SparseLU< Eigen::SparseMatrix<double> > solverA, solverQ;
189
190 solverA.compute(A);
191
192 if(solverA.info() != Eigen::Success) {
193 throw std::runtime_error("BaselineBeads(): solverA calculation fails.");
194 }
195
196 Eigen::SparseMatrix<double> BTB = B.transpose() * B;
197 Eigen::VectorXd b = Eigen::VectorXd::Constant(length, (1 - r) / 2);
198 Eigen::VectorXd d = BTB * (solverA.solve(yy)) - lambda0 * A.transpose() * b;
199 Eigen::VectorXd w1 = Eigen::VectorXd::Constant(length - 1, lambda1);
200 Eigen::VectorXd w2 = Eigen::VectorXd::Constant(length - 2, lambda2);
201
202 Eigen::VectorXd x = yy;
203
204 double prev_c = 0.5
205 * (B * solverA.solve(x)).array().square().sum()
206 + lambda0 * x.unaryExpr(theta).sum()
207 + lambda1 * (D1 * x).unaryExpr(phi).sum()
208 + lambda2 * (D2 * x).unaryExpr(phi).sum();
209
210 for(unsigned int i = 0; i < loop; i++) {
211 Eigen::SparseMatrix<double> L1, L2, G, M;
212
213 L1 = (w1.array() * (D1 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
214 L2 = (w2.array() * (D2 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
215
216 G = x.unaryExpr([&](const double xx) {
217 double z = (-eps0 <= xx && xx <= eps0) ? eps0 : std::fabs(xx);
218 return (1 + r) / 4 / z;
219 }).asDiagonal();
220
221 M = 2 * lambda0 * G + D1.transpose() * L1 * D1 + D2.transpose() * L2 * D2;
222 solverQ.compute(BTB + A.transpose() * M * A);
223
224 if(solverQ.info() != Eigen::Success) {
225 throw std::runtime_error("BaselineBeads(): solverQ calculation fails.");
226 }
227
228 x = A * solverQ.solve(d);
229
230 Eigen::VectorXd a = yy - x;
231 double c = 0.5
232 * (B * solverA.solve(a)).array().square().sum()
233 + lambda0 * x.unaryExpr(theta).sum()
234 + lambda1 * (D1 * x).unaryExpr(phi).sum()
235 + lambda2 * (D2 * x).unaryExpr(phi).sum();
236
237 if(std::fabs((prev_c - c) / c) < eps) {
238 break;
239 }
240
241 prev_c = c;
242 }
243
244 Eigen::VectorXd f = yy - x;
245 f = (f - B * solverA.solve(f)).eval();
246
247 BaselineResult result;
248 result.baseline.resize(x.size());
249 result.corrected.resize(f.size());
250
251 Eigen::VectorXd::Map(result.baseline.data(), x.size()) = x;
252 Eigen::VectorXd::Map(result.corrected.data(), f.size()) = f;
253
254 return result;
255}
256
257//
258// Implementation of BeadsExpandBoundaries() function
259//
260const std::vector<double> BeadsExpandBoundaries(const std::vector<double> & y, const unsigned int n)
261{
262 if(y.size() == 0) {
263 throw std::invalid_argument("BeadsExpandBoundaries(): the length of y is zero.");
264 }
265
266 std::vector<double> result(y.size() + 2 * n);
267
268 auto it1 = result.begin();
269 auto it2 = result.rbegin();
270 for(unsigned int i = 0; i < n; i++, it1++, it2++) {
271 double x = (double)i / (n - 1);
272 double t = std::sqrt(x);
273 *it1 = y.front() * t;
274 *it2 = y.back() * t;
275 }
276
277 std::copy(y.begin(), y.end(), result.begin() + n);
278
279 return result;
280}
281
282//
283// Implementation of BeadsTrimBoundaries() function
284//
285const std::vector<double> BeadsTrimBoundaries(const std::vector<double> & y, const unsigned int n)
286{
287 if(y.size() == 0) {
288 throw std::invalid_argument("BeadsTrimBoundaries(): the length of y is zero.");
289 }
290
291 if(y.size() < 2 * n) {
292 throw std::invalid_argument("BeadsTrimBoundaries(): the length of y is too short.");
293 }
294
295 std::vector<double> result(y.size() - 2 * n);
296 auto it = y.begin() + n;
297
298 std::copy(it, it + result.size(), result.begin());
299
300 return result;
301}
302
303}; // namespace sablib
const std::vector< double > BeadsTrimBoundaries(const std::vector< double > &y, const unsigned int n)
Trims the expanded signal boundaries.
Definition beads.cpp:285
const BaselineResult 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:102
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:260
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:26
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
Return value structure for baseline estimation functions.
Definition result_type.h:19
std::vector< double > baseline
Definition result_type.h:20
std::vector< double > corrected
Definition result_type.h:21