Подгонка 2D-суммы гауссианов, scipy.optimise.leastsq (Ответ: используйте curve_fit!)

Я хочу подогнать к этим данным двумерную сумму гауссов:

Данные изображения

После того, как я сначала не смог подобрать сумму к этому, я вместо этого сэмплировал каждый пик отдельно (image) и вернул соответствие, найдя его моменты (по существу, используя этот код).

К сожалению, это приводит к неправильному измерению положения пика из-за перекрытия сигналов соседних пиков. Ниже приведен график суммы отдельных подгонок. Очевидно, их пики все склоняются к центру. Мне нужно учитывать это, чтобы вернуть правильное положение пика.

Сумма 3 отдельных гауссовых подгонок с использованием наименьшего квадрата

У меня есть рабочий код, который отображает 2D-функцию гауссовского конверта (twoD_Gaussian()), и я анализирую его через optimise.leastsq как 1D-массив, используя numpy.ravel и соответствующую функцию ошибок, однако это приводит к бессмысленному выводу.

Я попытался поместить один пик в сумму и получил следующий ошибочный результат:

частичная подходящая сумма. Параметры нижнего правого пика подобраны неудачно

Я был бы признателен за любые советы о том, что я мог бы попытаться сделать для этой работы, или альтернативные подходы, если это не подходит. Все входы приветствуются, конечно!

Код ниже:

from scipy.optimize import leastsq
import numpy as np
import matplotlib.pyplot as plt

def twoD_Gaussian(amp0, x0, y0, amp1=13721, x1=356, y1=247, amp2=14753, x2=291,  y2=339, sigma=40):

    x0 = float(x0)
    y0 = float(y0)
    x1 = float(x1)
    y1 = float(y1)
    x2 = float(x2)
    y2 = float(y2)

    return lambda x, y:  (amp0*np.exp(-(((x0-x)/sigma)**2+((y0-y)/sigma)**2)/2))+(
                             amp1*np.exp(-(((x1-x)/sigma)**2+((y1-y)/sigma)**2)/2))+(
                             amp2*np.exp(-(((x2-x)/sigma)**2+((y2-y)/sigma)**2)/2))

def fitgaussian2D(x, y, data, params):

    """Returns (height, x, y, width_x, width_y)
    the gaussian parameters of a 2D distribution found by a fit"""
    errorfunction = lambda p: np.ravel(twoD_Gaussian(*p)(*np.indices(np.shape(data))) - data)

    p, success = optimize.leastsq(errorfunction, params)
    return p     

# Create data indices
I = image   # Red channel of a scanned image, equivalent to the  1st image displayed in this post.
p = np.asarray(I).astype('float')
w,h = np.shape(I)
x, y = np.mgrid[0:h, 0:w]
xy = (x,y)

# scanned at 150 dpi = 5.91 dots per mm
dpmm = 5.905511811
plot_width = 40*dpmm

# create function indices
fdims = np.round(plot_width/2)  
xdims = (RC[0] - fdims, RC[0] + fdims)
ydims = (RC[1] - fdims, RC[1] + fdims)
fx = np.linspace(xdims[0], xdims[1], np.round(plot_width))
fy = np.linspace(ydims[0], ydims[1], np.round(plot_width))
fx,fy = np.meshgrid(fx,fy)

#Crop image for display
crp_data = image[xdims[0]:xdims[1], ydims[0]:ydims[1]]
z = crp_data    

# Parameters obtained from separate fits
Amplitudes = (13245, 13721, 15374)
px = (410, 356, 290)
py = (350, 247, 339)

initial_guess_sum = (Amp[0], px[0], py[0], Amp[1], px[1], py[1], Amp[2], px[2], py[2])
initial_guess_peak3 = (Amp[0], px[0], py[0]) # Try fitting single peak within sum


fitted_pars = fitgaussian2D(x, y, z, initial_guess_sum)
#fitted_pars = fitgaussian2D(x, y, z, initial_guess_peak3)

data_fitted= twoD_Gaussian(*fitted_pars)(fx,fy)
#data_fitted= twoD_Gaussian(*initial_guess_sum)(fx,fy)



fig = plt.figure(figsize=(10, 30))
ax = fig.add_subplot(111, aspect="equal")
#fig, ax = plt.subplots(1)
cb = ax.imshow(p, cmap=plt.cm.jet, origin='bottom',
    extent=(x.min(), x.max(), y.min(), y.max()))
ax.contour(fx, fy, data_fitted.reshape(fx.shape[0], fy.shape[1]), 4, colors='w')

ax.set_xlim(np.int(RC[0])-135, np.int(RC[0])+135)
ax.set_ylim(np.int(RC[1])+135, np.int(RC[1])-135)

#plt.colorbar(cb)
plt.show()

person Tom W    schedule 09.06.2015    source источник
comment
Если вы хотите подогнать пик за пиком, это также возможно с использованием подхода обратной подгонки (en.wikipedia.org /wiki/Backfitting_algorithm), и вы можете инициализировать ожидаемое количество пиков на основе количества локальных максимумов. Но симуляционная оптимизация всех параметров, как вы, конечно, все же будет наиболее точной.   -  person Tom Wenseleers    schedule 25.08.2018


Ответы (2)


Я пробовал множество других вещей, прежде чем сдаться и снова попробовать curve_fit, хотя и с большим знанием парсинга лямбда-функций. Это сработало. Пример вывода и код ниже (все еще с избыточностью) для потомков.

curve_fit output

def twoD_Gaussian(amp0, x0, y0, amp1=13721, x1=356, y1=247, amp2=14753, x2=291,  y2=339, sigma=40):

    x0 = float(x0)
    y0 = float(y0)
    x1 = float(x1)
    y1 = float(y1)
    x2 = float(x2)
    y2 = float(y2)

    return lambda x, y:  (amp0*np.exp(-(((x0-x)/sigma)**2+((y0-y)/sigma)**2)/2))+(
                             amp1*np.exp(-(((x1-x)/sigma)**2+((y1-y)/sigma)**2)/2))+(
                             amp2*np.exp(-(((x2-x)/sigma)**2+((y2-y)/sigma)**2)/2))

def twoD_GaussianCF(xy, amp0, x0, y0, amp1=13721, amp2=14753, x1=356, y1=247, x2=291,  y2=339, sigma_x=12, sigma_y=12):

    x0 = float(x0)
    y0 = float(y0)
    x1 = float(x1)
    y1 = float(y1)
    x2 = float(x2)
    y2 = float(y2)

    g = (amp0*np.exp(-(((x0-x)/sigma_x)**2+((y0-y)/sigma_y)**2)/2))+(
        amp1*np.exp(-(((x1-x)/sigma_x)**2+((y1-y)/sigma_y)**2)/2))+(
        amp2*np.exp(-(((x2-x)/sigma_x)**2+((y2-y)/sigma_y)**2)/2))

    return g.ravel()

# Create data indices
I = image   # Red channel of a scanned image, equivalent to the  1st image displayed in this post.
p = np.asarray(I).astype('float')
w,h = np.shape(I)
x, y = np.mgrid[0:h, 0:w]
xy = (x,y)

N_points = 3
display_width = 80

initial_guess_sum = (Amp[0], px[0], py[0], Amp[1], px[1], py[1], Amp[2], px[2], py[2])

popt, pcov = opt.curve_fit(twoD_GaussianCF, xy, np.ravel(p), p0=initial_guess_sum)

data_fitted= twoD_Gaussian(*popt)(x,y)

peaks = [(popt[1],popt[2]), (popt[5],popt[6]), (popt[7],popt[8])]

fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, aspect="equal")
cb = ax.imshow(p, cmap=plt.cm.jet, origin='bottom',
    extent=(x.min(), x.max(), y.min(), y.max()))
ax.contour(x, y, data_fitted.reshape(x.shape[0], y.shape[1]), 20, colors='w')

ax.set_xlim(np.int(RC[0])-135, np.int(RC[0])+135)
ax.set_ylim(np.int(RC[1])+135, np.int(RC[1])-135)

for k in range(0,N_points):
    plt.plot(peaks[k][0],peaks[k][1],'bo',markersize=7)
plt.show()
person Tom W    schedule 10.06.2015

Если все, что вам нужно, это центр тяжести каждого гауссиана, я бы просто выбрал scipy.optimize.minimize. Умножьте ваши данные на -1, а затем выполните грубую выборку, чтобы найти минимумы. Высота каждого пика будет смещена соседними гауссианами, но позиции не изменятся, поэтому, если вы найдете локальное экстремальное значение, то это должен быть центр тяжести гауссианы.

Если вам нужны другие параметры, возможно, имеет смысл найти центроиды, как я предлагаю, а затем использовать leastsq, чтобы найти амплитуды и ширины. Это может добавить много накладных расходов, если вы выполняете эти подборы много раз, но это значительно уменьшит количество свободных параметров в подборе методом наименьших квадратов.

person shortorian    schedule 10.06.2015