38#define _USE_MATH_DEFINES
54const std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >
55BAfilt(
const unsigned int d,
const double frequency,
const unsigned int length)
57 Eigen::VectorXd a(1), b, b1(2), v(3), v2(2);
58 double omega_c = 2 * M_PI * frequency;
64 for(
unsigned int i = 1; i < d; i++){
74 for(
unsigned int i = 0; i < d; i++){
78 t = std::pow((1 - std::cos(omega_c)) / (1 + std::cos(omega_c)), d);
79 a = (b + t * a).eval();
81 Eigen::MatrixXd xa(a.size(), length);
82 Eigen::MatrixXd xb(b.size(), length);
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;
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);
93 return std::tuple< Eigen::SparseMatrix<double>, Eigen::SparseMatrix<double> >(A, B);
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
109 throw std::invalid_argument(
"BaselineBeads(): the length of y is zero.");
112 if(s == 0 || s > 3) {
113 throw std::invalid_argument(
"BaselineBeads(): s must be 1, 2 or 3.");
117 throw std::invalid_argument(
"BaselineBeads(): non-positive frequency value is given.");
121 throw std::invalid_argument(
"BaselineBeads(): non-positive r value is given.");
124 if(lambda0 <= 0 || lambda1 <= 0 || lambda2 <= 0) {
125 throw std::invalid_argument(
"BaselineBeads(): non-positive lambda value is given.");
129 throw std::invalid_argument(
"BaselineBeads(): loop is zero.");
133 throw std::invalid_argument(
"BaselineBeads(): non-positive eps value is given.");
136 const double eps0 = 1e-6;
137 const double eps1 = 1e-6;
139 Eigen::VectorXd yy = Eigen::VectorXd::Map(y.data(), y.size());
141 std::function<double(
const double)> phi, wfun;
145 phi = [&](
const double xx) {
146 double abs_x = std::fabs(xx);
147 return std::sqrt(abs_x * abs_x + eps1);
149 wfun = [&](
const double xx) {
154 phi = [&](
const double xx) {
155 double abs_x = std::fabs(xx);
156 return abs_x - eps1 * std::log(abs_x + eps1);
158 wfun = [&](
const double xx) {
159 return 1 / (std::fabs(xx) + eps1);
163 throw std::invalid_argument(
"invalid penalty function type.");
166 auto theta = [&](
const double xx) {
170 else if(xx < -eps0) {
174 return (1 + r) * xx * xx / (4 * eps0) + (1 - r) * xx / 2 + eps0 * (1 + r) / 4;
178 int length = yy.size();
179 auto [ A, B ] = BAfilt(s, frequency, length);
181 Eigen::SparseMatrix<double> I, D1, D2;
183 I.resize(length, length);
188 Eigen::SparseLU< Eigen::SparseMatrix<double> > solverA, solverQ;
192 if(solverA.info() != Eigen::Success) {
193 throw std::runtime_error(
"BaselineBeads(): solverA calculation fails.");
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);
202 Eigen::VectorXd x = yy;
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();
210 for(
unsigned int i = 0; i < loop; i++) {
211 Eigen::SparseMatrix<double> L1, L2, G, M;
213 L1 = (w1.array() * (D1 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
214 L2 = (w2.array() * (D2 * x).unaryExpr(wfun).array()).matrix().asDiagonal();
216 G = x.unaryExpr([&](
const double xx) {
217 double z = (-eps0 <= xx && xx <= eps0) ? eps0 : std::fabs(xx);
218 return (1 + r) / 4 / z;
221 M = 2 * lambda0 * G + D1.transpose() * L1 * D1 + D2.transpose() * L2 * D2;
222 solverQ.compute(BTB + A.transpose() * M * A);
224 if(solverQ.info() != Eigen::Success) {
225 throw std::runtime_error(
"BaselineBeads(): solverQ calculation fails.");
228 x = A * solverQ.solve(d);
230 Eigen::VectorXd a = yy - x;
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();
237 if(std::fabs((prev_c - c) / c) < eps) {
244 Eigen::VectorXd f = yy - x;
245 f = (f - B * solverA.solve(f)).eval();
251 Eigen::VectorXd::Map(result.
baseline.data(), x.size()) = x;
252 Eigen::VectorXd::Map(result.
corrected.data(), f.size()) = f;
263 throw std::invalid_argument(
"BeadsExpandBoundaries(): the length of y is zero.");
266 std::vector<double> result(y.size() + 2 * n);
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;
277 std::copy(y.begin(), y.end(), result.begin() + n);
288 throw std::invalid_argument(
"BeadsTrimBoundaries(): the length of y is zero.");
291 if(y.size() < 2 * n) {
292 throw std::invalid_argument(
"BeadsTrimBoundaries(): the length of y is too short.");
295 std::vector<double> result(y.size() - 2 * n);
296 auto it = y.begin() + n;
298 std::copy(it, it + result.size(), result.begin());
const std::vector< double > BeadsTrimBoundaries(const std::vector< double > &y, const unsigned int n)
Trims the expanded signal boundaries.
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).
const std::vector< double > BeadsExpandBoundaries(const std::vector< double > &y, const unsigned int n)
Expands the signal boundaries by padding with a tapered sequence.
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).
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.
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.
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.
Return value structure for baseline estimation functions.
std::vector< double > baseline
std::vector< double > corrected