从一个向量估计峰宽,该向量是未知数量的具有不同高度的相同高斯峰的叠加?

信息处理 峰值检测 盲反卷积 r 稀疏模型 脉冲
2022-02-20 04:55:23

如果您有一个向量,它是未知数量的相同高斯形状峰值/未知宽度(但宽度相同)和不同幅度(具有泊松或高斯噪声)的脉冲的叠加,有人知道推断这个的方法吗宽度?

例如,让我们模拟 R 中宽度为 5 的高斯峰的叠加:

gauspeak=function(x, u, w, h=1) h*exp(((x-u)^2)/(-2*(w^2)))

sumgauspeaks = function(x, u, w, h) {
  npeaks = length(u)
  n = length(x)
  Y_nonoise = do.call(cbind, lapply(1:npeaks, function (peak) gauspeak(x, u=u[peak], w=w[peak], h=h[peak]) ))
  y_nonoise = rowSums(Y_nonoise)
  set.seed(101)
  Y = apply(Y_nonoise, 2, function(col) rpois(n,col)) # peaks with Poisson noise
  y = rowSums(Y) # measured noisy signal (superposition of identical Gaussians of unknown width & width different heights)
  return(y)
}

x = 1:1000
npeaks = 40 # unknown number of peaks
u = runif(npeaks, min=min(x), max=max(x)) # unknown peak locations
w = rep(5,npeaks) # unknown peak widths (all the same though)
h = runif(npeaks, min=0, max=1000) # unknown peak heights
y = sumgauspeaks(x, u, w, h)
plot(x,y,type="l")

在此处输入图像描述

如果我在此图中测量此(非负)信号,我希望能够估计(恒定)峰宽w在这种情况下,叠加的高斯峰的数量是 5(不知道先验它们的幅度/高度或峰的真实数量或它们的位置,但假设所有这些峰的形状相同但比例不同的高斯)......任何人都知道如何做到这一点以最有效的方式?这可能来自 DFT 或其他什么吗?或者通过基于具有不同宽度的时间偏移高斯峰的协变量矩阵/字典估计稀疏尖峰序列,并根据正交匹配追踪或 LASSO 回归检查最常选择哪一类峰宽?有什么想法吗?我只需要一个粗略的估计,它不需要准确,但我希望它快...

编辑:我知道的一种算法,但它比我想要的更多,因为它估计了解释信号的单个最佳峰形是在这个 R 代码中实现的de Rooi & Eilers (2011)的算法:

# 1. GAUSSIAN PEAK FUNCTION ####
gauspeak=function(x, u, w, h=1) h*exp(((x-u)^2)/(-2*(w^2)))

# 2. FUNCTION TO SIMULATE SUM OF GAUSSIAN PEAKS WITH POISSON NOISE ####
sumgauspeaks = function(x, u, w, h) {
  npeaks = length(u)
  n = length(x)
  Y_nonoise = do.call(cbind, lapply(1:npeaks, function (peak) gauspeak(x, u=u[peak], w=w[peak], h=h[peak]) ))
  y_nonoise = rowSums(Y_nonoise)
  set.seed(101)
  Y = apply(Y_nonoise, 2, function(col) rpois(n,col)) # peaks with Poisson noise
  y = rowSums(Y) # measured noisy signal (superposition of identical Gaussians of unknown width & width different heights)
  return(y)
}

# 3. FUNCTION TO CALCULATE FULL WIDTH AT HALF MAXIMUM OF A FITTED SIGNAL ####
# plus corresponding width of a Gaussian peak function if signal were Gaussian
fwhm = function(x, y, interpol=FALSE) { 
  halfheight = max(y)/2
  id.maxy = which.max(y)
  y1 = y[1:id.maxy]
  y2 = y[id.maxy:length(y)]
  x1 = x[1:id.maxy]
  x2 = x[id.maxy:length(y)]
  if (interpol) {
    x.start = approx(x=y1,y=x1, xout=halfheight, method="linear")$y # use spline() if you would like spline interpolation
x.stop = approx(x=y2,y=x2, xout=halfheight, method="linear")$y # use spline() if you would like spline interpolation
  } else { 
    x.start = x[which.min(abs(y1-halfheight))]
    x.stop = x[which.min(abs(y2-halfheight))+id.maxy]
  }
  fwhm = x.stop-x.start
  width = fwhm/(2*sqrt(2*log(2)))
  return(list(fwhm=fwhm, width=width))
}

# 4. FUNCTION TO CARRY OUT FAST GAUSSIAN PEAK FIT (USING TER BRAAK / OKSANEN PARAMETERISATION) #### 
# i.e. fit  a quadratic model on a log scale using a least square model fit on a log transformed Y scale (if control$fast==TRUE) or using a generalized linear model with Poisson noise fit on a log link scale (if control$fast==FALSE)
# see https://esajournals.onlinelibrary.wiley.com/doi/abs/10.1890/0012-9658(2001)082%5B1191:CIFTOI%5D2.0.CO%3B2
gausfit = function(x, y, control=list(start=NULL, fast=TRUE)) {
  maxy <- max(y)
  if (maxy==0) { return(list(x=x, y=y, fitted=rep(0,length(y)))) }
  if (maxy<=1) { ymax <- 1E5 } else { ymax <- maxy }
  yscaling <- ymax/maxy
  y <- pmax(round(y*yscaling),0)
  ymax_idx <- which.max(y)
  subs_start <- suppressWarnings(max(which(y[1:ymax_idx]==0)))
  if (is.infinite(subs_start)) subs_start <- 1
  subs_stop <- (ymax_idx+suppressWarnings(min(which(y[ymax_idx:length(y)]==0))))
  if (is.infinite(subs_stop)) subs_stop <- length(y)
  subset <- subs_start:subs_stop # y!=0 #  # remove zeros on the side
  x_subs <- x[subset]
  y_subs <- y[subset]
  # we initialize coefficients with weighted least squares model fit on log transformed Y scale
  if (is.null(control$start)) { offs <- 1E-10
                            weights <- y_subs^2/(y_subs+offs)^2 
  c <- control$start <- .lm.fit(x=cbind(1,x_subs,x_subs^2)*sqrt(weights), 
                            y=log(y_subs+offs)*sqrt(weights))$coefficients 
  } 

  # now do actual quadratic GLM fit (if fast==FALSE)
  if (control$fast==FALSE) { 
c <- suppressWarnings(glm.fit(cbind(1,x_subs,x_subs^2), y_subs, 
                              family=poisson(link=log), 
                              start=control$start, 
                              control=glm.control(epsilon = 1e-8, maxit = 50, trace = FALSE)
))$coefficients 
  }

  if (c[[3]]<0) {
    u_fitted <- -c[[2]]/2/c[[3]] # inferred mode
    w_fitted <- sqrt(-(1/2)/c[[3]]) # inferred width
    h_fitted <- exp(c[1] - c[2]^2/4/c[3]) # inferred peak height (* yscaling)
    fitted <- gauspeak(x=x, u=u_fitted, w=w_fitted, h=h_fitted/yscaling)
  } else { 
    u_fitted <- mean(x) # inferred mode
    w_fitted <- diff(range(x))/2 # inferred width
    h_fitted <- 0 # inferred peak height (* yscaling)
    fitted <- rep(0,length(x))
  }
  out <- list(x=x, y=y, 
              fitted=fitted,
              fitted.pars=c(u=u_fitted, w=w_fitted, h=h_fitted))
  return(out)
}


# 5. DECONVOLUTION FUNCTIONS OF DE ROOI & EILERS 2011 ARTICLE ####
# see https://www.sciencedirect.com/science/article/pii/S0003267011006696

ALS_baseline_fun <- function(y, d = 2, lambda = 10, w = 1, p=0.01){
  m <- length(y)
  E <- diag(m)
  D <- diff(E, diff = d)
  w <- rep(w, m)
  W <- diag(w)
  for (i in 1:20)
  {
    W <- diag(w)
    z <- solve(W + lambda * t(D) %*% D, w * y)
    w <- p * (y > z) + (1 - p) * (y < z)
  }
  return(z)
}


#############################################################
# y = input
# g = given or initial guess of impulse respons
# baseline: 0 = no baseline correction
#               1 = ALS fitting
#               2 = ALS fitting and solving the big system
# gamma = penalty parameter for ALS baseline
# kappa = penalty parameter for deconvolution of the signal
# lambda = penalty for G matrix
# blind: FALSE = impuls respons is taken as given
#          TRUE = impuls response is iteratively improved
#############################################################

deconvol_fun <- function(y, g, baseline=0, gamma, lambda, kappa, k=1, blind=FALSE)
{
  if(baseline!=0)
  {
    # fit baseline
    z <- ALS_baseline_fun(y, d=2, gamma, w=1, p=0.01)
    y <- y - z
  }

  # estimate peaks
  peaks <- L0_peak_fun(y, g, kappa=kappa, blind=blind, lambda=lambda)

  # 'big system' part
  if(baseline==2)
  {
    stop("this function is not yet implemented")
  }
  list(a=peaks$a, g=peaks$g)
}


# actual deconvolution function

L0_peak_fun <- function(y, g, kappa=0.02, blind=FALSE, lambda=0)
{
  nloop = 20
  p = 1
  nc = length(g)
  n = length(y)
  m = n
  w = rep(1, m)
  Gg = matrix(0, nc, nloop)
  for (loop in 1:nloop) 
  {
    # Pulsvormmatrix C for fitting
    G = matrix(0, n + nc - 1, n)
    for (k in 1:n) G[(1:nc) + k - 1, k] = g
    G = G[floor(nc / 2) + (1:m), ]

    # Deconvolve
    kappa = kappa
    s0 = 0
    beta = 0.001
    w = rep(1, m)
    for (it  in 1:10) 
    {
      W = diag(c(w))
      a = solve(W + t(G) %*% G,  t(G) %*% y)
      w1 = w
      w = kappa / (beta + a^2)
      z = G %*% a
      r = y - z
      s = t(r) %*% r + kappa %*% t(a) %*% a
      ds = s - s0
      if (it == 10) cat(ds, '\n')
      s0 = s
    }

    # Improve C
    if(blind==TRUE)
    {
      G = matrix(0, m + nc - 1, nc)
      for (k in 1:nc) G[(1:n) + k - 1, k] = a
      G = G[(1:m) + floor(nc / 2), ]
      Dg = diff(diag(nc), 1)
      lambda = lambda
      gnew = solve(t(G) %*% G + lambda * t(Dg) %*% Dg, t(G) %*% y)
      g = gnew / max(gnew)
      Gg[, loop] = g
    }

    if (abs(ds) < 1e-6) break
  }
  list(a=a, g=g)
}



# 6. DO TEST ####
x = 1:1000
npeaks = 40
u = runif(npeaks, min=min(x), max=max(x)) # unknown peak locations
w = rep(5,npeaks) # unknown peak widths
h = runif(npeaks, min=0, max=1000) # unknown peak heights
y = sumgauspeaks(x=x, u, w, h)
dev.off()
plot(x,y,type="l")

# estimate peak shape using De Rooi & Eiler's 2011 method
nc=50
g = gauspeak(1:nc, u=25, w=2, h=1) # initial guess of peak shape (peak width guess here too narrow)
system.time(dec <- deconvol_fun(y/max(y), g, baseline=0, 
                                lambda=1E-5, kappa=0.01, blind=TRUE)) # takes 149s

dev.off()
par(mfrow=c(3,1))
plot(1:length(y), y/max(y),type='l', col="grey", xlab="x", ylab="y", main="Normalised response")
plot(1:length(y), dec$a, type='h', col="red", xlab="x", ylab="Amplitude", main="Fitted spike train based on L0 norm penalized regression")
plot(1:nc, gauspeak(1:50, u=25, w=w[[1]], h=1), type='l', col="grey", lwd=2, xlab="x", ylab="Peak shape", main="Real peak shape (grey) & fitted peak shape (red)")
lines(1:nc, dec$g,type='l', col="red")

在此处输入图像描述

fwhm(1:nc, dec$g, interpol=TRUE)$width # estimated width based on full width at half max: 5.10
gausfit(1:nc, y=dec$g, control=list(start=NULL, fast=FALSE))$fitted.pars # estimated width based on Gaussian fit on inferred peak shape: 4.87

对于 L0 norm 惩罚正则化/最佳子集选择,我还发现了这篇文章,我认为这是Eilers 方法的进一步发展:

该算法的问题是(1)拟合在收敛性方面不是很稳定,(2)有两个正则化参数需要调整,(3)峰形不被限制为高斯(可以通过拟合来解决)每次迭代后推断峰值形状的高斯,但也许有更好的方法??)和(4)算法很慢(我笔记本电脑上的这个小例子是 150 秒)。所以理想情况下,我正在寻找更强大和更快的东西......

4个回答

我的第一条评论是,如果你关心处理速度,你为什么要使用 R,或者你只是原型算法?

无论如何,无需深入了解我是如何得出它的,这里有一个更快的公式:

记录您的信号(如果为 0,则为 -1):

g[x]=ln(y[x])

计算以下值:

B=g[x6]+g[x5]+g[x4]g[x3]g[x2]g[x1]g[x+1]g[x+2]g[x+3]+g[x+4]+g[x+5]+g[x+6]152

B<0

w[x]=12B

否则,-1。(可能是因为噪音,远离高峰)

以下是测试运行的一些结果:

  y ln(y) w
------ ------ -----  
   114 4.7362 -1.00
   167 5.1180 -1.00
   233 5.4510 -1.00
   326 5.7869 6.69
   439 6.0845 6.19
   668 6.5043 6.40
   769 6.6451 5.32
  1003 6.9108 4.83
  1213 7.1009 4.97
  1435 7.2689 5.01
  1613 7.3859 4.92
  1645 7.4055 4.81
  1645 7.4055 5.13
  1722 7.4512 5.58
  1550 7.3460 5.00
  1464 7.2889 4.91
  1301 7.1709 5.42
  1072 6.9773 5.10
   852 6.7476 4.98
   705 6.5582 4.94
   526 6.2653 5.25
   378 5.9349 6.50
   269 5.5947 6.37
   156 5.0499 4.42
   136 4.9127 2.03

如您所见,它在峰值附近非常准确。生成这样的公式有一个规则。它可以根据需要扩展以覆盖尽可能宽的姿态。(是的,我认为这将是未来的博客文章。感谢您的拼图。)

我在代码末尾插入了这些行以获取我的值:

fileConn<-file("y.txt")
写(y,文件连接)
关闭(文件连接)

几点注意事项:

1)峰值计算的值将略低于附近计算的值,因为峰值本身不包括在计算中。

2) 当从 log 列输入 -1s 时,公式可能仍会给出一个值。所有 g[x+d] 值必须有效。

3) 该公式是为独立峰设计的

4) 该公式压缩了任何常数值和任何线性趋势,因此它应该减轻附近尾部的影响

5)如果它适合您的目的,您仍然必须弄清楚如何最好地使用它。

我会根据要求详细说明。


显然,您宁愿在没有约束的情况下解决问题。我做了一些改进。

你有九个团块(由零分隔的部分)

cnw
-- --- ---
0 0 79
1 112 95
2 238 36
3 328 44
4 407 34
5 492 38
6 536 189
7 729 132
8 888 76

峰的初步调查:

nc Center -B(alt) Miss Width of Peak
--- - ------ ------ ---- ----    
 10 0 9.96 0.0210 0.02 4.87
 33 0 32.38 0.0204 0.03 4.95
 60 0 59.82 0.0152 0.16 5.74

131 1 130.27 0.0196 0.05 5.05
161 1 160.31 0.0163 0.14 5.54
189 1 188.16 0.0203 0.02 4.97

256 2 255.81 0.0202 0.02 4.97

355 3 354.13 0.0210 0.05 4.88

423 4 422.05 0.0203 0.07 4.97

510 5 509.91 0.0209 0.03 4.90

555 6 554.90 0.0203 0.03 4.96
576 6 575.34 0.0200 0.11 5.00
609 6 608.31 0.0127 0.25 6.27
644 6 644.67 0.0099 0.63 7.09
657 6 656.34 0.0134 0.34 6.11
680 6 679.08 0.0194 0.12 5.08
703 6 702.81 0.0101 0.07 7.05

748 7 747.02 0.0171 0.05 5.40
773 7 772.18 0.0202 0.07 4.97
800 7 799.87 0.0140 0.43 5.98
821 7 820.74 0.0119 0.03 6.47
842 7 841.34 0.0196 0.12 5.05

906 8 905.78 0.0226 0.06 4.70
933 8 932.95 0.0113 0.59 6.66
947 8 945.79 0.0156 0.28 5.67

“未命中”列是数据在该点与高斯拟合的接近程度的度量。您可以看到孤立的峰为您提供了非常好的结果。我对我在 DFT 中梳理音调的工作充满信心,我可以梳理丛中的高斯,因此读数与独立的读数一样好。

我怀疑你可以在速度上与我竞争。在一台相当旧的计算机上运行 Python 需要 0.02 秒,包括打印到控制台的时间。

Ha 刚刚找到了一种更快更好的方法,仅使用 BIC 优化的最佳峰宽选择,使用具有给定宽度的移位高斯峰形状的带状协变量矩阵和使用非负最小二乘拟合(使用活动集方法解决并正则化有点问题,虽然当然比上面的 LASSO 或 L0 范数惩罚要少,但结果是你不必调整任何正则化参数)。

R 代码如下,它比我的代码快了 10 倍,而且更可靠(1000 的窗口为 13 秒,500 的窗口为 2.5 秒,200 的窗口为 0.04 秒):

# FUNCTION TO CALCULATE INFORMATION CRITERIA AIC & BIC GIVING GOODNESS OF FIT ####
# SSs are calculated on given transformed scale (e.g. sqrt() variance stabilizing function for Poisson data)
IC = function (y, yhat, npars, transf = function (y) sqrt(y)) { 
  nobs = length(y)
  RSS = sum((transf(y)-transf(yhat))^2) # residual SS on sqrt() scale
  min2LL = nobs + nobs*log(2*pi) + nobs*log(RSS/nobs) # -2*logL
  AIC = min2LL + 2*npars 
  BIC = min2LL + log(nobs)*npars
  return(list(AIC=AIC, BIC=BIC))
}

# FUNCTION GIVING FIT QUALITY (BIC) IN FUNCTION OF GAUSSIAN PEAK WIDTH USED IN BANDED COVARIATE MATRIX OF NNLS FIT
fitqual = function(width) {
  bandedM = do.call(cbind, lapply(1:length(y), function (u) gauspeak(1:length(y), u=u, w=width, h=1)))
  require(nnls)
  fit = nnls(A=bandedM, b=y)
  fitqual = IC(y, fit$fitted, npars=sum(fit$x>0), transf =  function(y) sqrt(y))$BIC # fit$deviance=RSS
  return(fitqual) 
} 
system.time(what <- optimize(fitqual, interval=c(3, 6), maximum=FALSE, tol=1E-3)$minimum) # 13 s
what # estimated Gaussian peak width: 5.02 
BICvals <- sapply(seq(1,10,length.out=100), function(width) fitqual(width) ) 
dev.off()
plot(seq(1,10,length.out=100), BICvals, type="l", ylab="Bayesian Information Criterion (BIC)", xlab="Gaussian peak width")

在此处输入图像描述

唯一的问题似乎是这个 BIC 目标函数严格来说不是 100% 平滑的(尽管它很接近)。如果有人知道更好的平滑目标函数,请告诉我(也许 RSS 使用 2 倍 CV?)!或者任何其他更快的方法也会很酷!
使用正交匹配追踪可能仍然比 nnls 稍快(但似乎没有使用此实现),并且优化为使用稀疏带状协变量矩阵的 nnls 求解器也可能更快(例如,使用osqp二次规划求解器,https://stats. stackexchange.com/questions/136563/linear-regression-with-individual-constraints-in-r,或者使用nnls中的函数, https://search.r-project.org/CRAN/refmans/RcppML/html/ nnls.htmlRcppML)。

编辑:还尝试了 2 倍 CV - 如果 RSS 是在 sqrt 尺度上计算的,它似乎也可以很好地工作并给出一个 100% 平滑的目标函数,尽管它不太准确,因为拟合只完成了一半数据(使用基于 BIC 的优化,每 2 条扫描线进行一次拟合,即使目标函数不平滑,也一样快,并且可能稍微更准确):

# USING 2-FOLD CROSS VALIDATION INSTEAD USING EVEN SCANLINES FOR FITTING & ODD SCANLINES FOR VALIDATION:
# (FASTER OPTION, BUT LITTLE LESS ACCURATE SINCE ONLY HALF OF THE DATA IS USED FOR FITTING)
logRSS.cv = function(width) {
  y1 = y[((1:length(y)) %% 2)==0] # even scanlines for fitting
  y2 = y[((1:length(y)) %% 2)!=0] # odd scanlines for validation
  bandedM = do.call(cbind, lapply(1:length(y1), function (u) gauspeak(1:length(y1), u=u, w=width/2, h=1)))
  require(nnls)
  fit <- nnls(A=bandedM, b=y1) 
  logRSS <- log(sum((sqrt(fit$fitted)-sqrt(y2))^2)) # log(RSS) calculated on sqrt scale
  return(logRSS) 
} 
system.time(what <- optimize(logRSS.cv, interval=c(2, 10), maximum=FALSE, tol=1E-3)$minimum) # 2s for window of 1000, 0.4s for window of 500
what # estimated Gaussian peak width: 4.48
logRSSs <- sapply(seq(1,10,length.out=100), function(width) logRSS.cv(width) ) 
dev.off()
plot(seq(1,10,length.out=100), logRSSs, type="l", ylab="log(RSS)", xlab="Gaussian peak width")
# this objective function seems 100% smooth though

在此处输入图像描述

使用 BIC、AIC 或调整后的 R2 作为标准并结合 2 折交叉验证似乎也很有效......

这里有两个问题:

  1. 求 / 一个高斯
  2. 找到所有的高斯人

你真的想在这里找到高斯,而不仅仅是拟合任何曲线。一个高斯有两个参数μ,σ并且可能有N他们在一个信号。

即使你知道N,这个问题仍然是病态的,因为一个高斯可以由两个或多个高斯叠加形成。这种“嵌套”或“堆积”需要在要拟合的模型中添加更多约束。这些约束在任何两个之间的距离上都是“僵硬的弹簧”μu,μv,因此如果两个方法彼此过于接近,则解决方案将受到“惩罚”。如果您尝试将其优化为一个“对象”,它确实有效,但会为模型添加更多参数。所以,你分散N对这个一维信号进行高斯分析,运行优化步骤,然后评估典型的最小二乘误差加上均值之间距离的误差项,并让它驱动您的下一个决策。

这将是困难的。另外,这里,你真的不知道N.

所以这给我们留下了迭代方法。

那是:

  1. 将一个高斯拟合到曲线
  2. 从曲线中减去它
  3. 如果曲线的幅度高于足以怀疑我们可以从信号中再取一个高斯的阈值,则转到 1,否则终止。

这种方法有两个问题:

  1. 分辨率:例如,我可以在其中看到至少一个样本,其中有两个彼此如此接近的高斯,其中一个的振幅比另一个高得多。这两个在您的信号中介于 0 和 100 之间。在这种情况下,优化几乎总是会选择更高的高斯。另一个类似问题的情况是 300-400 之间的双峰,可能被“视为”一个高斯。

  2. 峰值偏移:考虑一个双峰分布,只有两个高斯分布。当您减去其中一个(以“取出”)时,附近的高斯似乎会向您取出的高斯移动一点(如果它们充分重叠)。这是因为当两个高斯重叠时,它们“共享”一些质量。当你减去时,你把那个质量拿走了。因此,拟合具有不同的双峰之间的结果μ1,μ2和迭代过程的结果(找到μ1,减去它,找到μ2) 会有所不同。

现在,这些都是陷阱,根据您的应用程序,您将不得不接受一些并管理其他的。

但是在这个过程结束时,你会得到一个很好的集合U元组un=(μn,σn),nN然后你可以做任何你喜欢的事情(例如在σn并找到最常见的宽度,最小的宽度,最大的宽度,把导数放在μns 并在其上运行直方图,您可以在连续“脉冲”等之间获得平均时间。

希望这可以帮助。

根据 A_A 的回答,我已经编写了代码:

  • 找到信号的最高峰,并假设这是真实的位置和高度。
  • 找到用于此位置和高度的最佳宽度。记录这个宽度。
  • 从信号中减去这个高斯。
  • 对减去高斯的新信号重复此操作。

这会产生一个高度估计列表,可以对其进行平均以找到总体估计(如果它们不同)。下面是一个示例运行的图。黑色圆圈是单独的宽度。蓝线是所有高斯的真实宽度。红线是所有单个宽度的平均值。

示例运行


仅在下面的 R 代码

subtract_one_gaussian <- function(x, y, width) {
  max_ix <- which.max(y)
  indices <- max_ix + seq(-5*ceiling(width), 5*ceiling(width))
  indices <- indices[indices > 0]
  indices <- indices[indices < length(y) + 1]
  x_hat <- x[indices] 
  y_hat <- gauspeak(x_hat, x[max_ix], width, y[max_ix])
  y_new <- y 
  y_new[indices] <- abs(y_new[indices] - y_hat)
  result$y_new <- y_new
  result$max_ix <- max_ix
  result$x_hat <- x_hat
  result$y_hat <- y_hat
  return(result)
}

find_best_width <- function(x,y, min_width, max_width)
{
  widths <- seq(min_width, max_width, 0.2)
  errors <- c()
  for (width in widths) 
  {
    result <- subtract_one_gaussian(x, y, width)
    errors <-  c(errors,sum(abs(result$y_new)))
  }

  return(widths[which.min(errors)])
}

find_all_gaussians <- function(x,y) 
{
  best_width <- find_best_width(x,y,1,10)
  result$widths <- c(best_width)
  subtraction_result <- subtract_one_gaussian(x, y, best_width)
  result$locations <- c(result$max_ix)
  for (i in seq(1,40)) {
best_width <- find_best_width(x,subtraction_result$y_new,1,10)
result$widths <- c(result$widths, best_width)
subtraction_result <- subtract_one_gaussian(x, subtraction_result$y_new,best_width)
result$locations <- c(result$locations, subtraction_result$max_ix)
  }

  return(result)
}


gaussians <- find_all_gaussians(x,y)
plot(seq(1,length(gaussians$widths)), gaussians$widths)
lines(c(1,length(gaussians$widths)), mean(gaussians$widths)*c(1,1), col='red')
lines(c(1,length(gaussians$widths)), 5*c(1,1), col='blue')