python - law - Ajustar una función Gaussian 2D usando scipy.optimize.curve_fit-ValueError y minpack.error
optimize curve fit numpy (3)
Tengo la intención de adaptar una función Gaussiana 2D a las imágenes que muestran un rayo láser para obtener sus parámetros como FWHM
y posición. Hasta ahora traté de entender cómo definir una función Gaussiana 2D en Python y cómo pasarle las variables x e y.
He escrito un pequeño script que define esa función, la grafica, le agrega algo de ruido y luego intenta ajustarlo usando curve_fit
. Todo parece funcionar, excepto el último paso en el que intento ajustar la función de mi modelo a los datos ruidosos. Aquí está mi código:
import scipy.optimize as opt
import numpy as np
import pylab as plt
#define model function and pass independant variables x and y as a list
def twoD_Gaussian((x,y), amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
xo = float(xo)
yo = float(yo)
a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
return offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo) + c*((y-yo)**2)))
# Create x and y indices
x = np.linspace(0, 200, 201)
y = np.linspace(0, 200, 201)
x,y = np.meshgrid(x, y)
#create data
data = twoD_Gaussian((x, y), 3, 100, 100, 20, 40, 0, 10)
# plot twoD_Gaussian data generated above
plt.figure()
plt.imshow(data)
plt.colorbar()
# add some noise to the data and try to fit the data generated beforehand
initial_guess = (3,100,100,20,40,0,10)
data_noisy = data + 0.2*np.random.normal(size=len(x))
popt, pcov = opt.curve_fit(twoD_Gaussian, (x,y), data_noisy, p0 = initial_guess)
Aquí está el mensaje de error que recibo cuando winpython 64-bit
el script usando winpython 64-bit
Python 2.7
winpython 64-bit
:
ValueError: object too deep for desired array
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:/Python/WinPython-64bit-2.7.6.2/python-2.7.6.amd64/lib/site-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 540, in runfile
execfile(filename, namespace)
File "E:/Work Computer/Software/Python/Fitting scripts/2D Gaussian function fit/2D_Gaussian_LevMarq_v2.py", line 39, in <module>
popt, pcov = opt.curve_fit(twoD_Gaussian, (x,y), data_noisy, p0 = initial_guess)
File "C:/Python/WinPython-64bit-2.7.6.2/python-2.7.6.amd64/lib/site-packages/scipy/optimize/minpack.py", line 533, in curve_fit
res = leastsq(func, p0, args=args, full_output=1, **kw)
File "C:/Python/WinPython-64bit-2.7.6.2/python-2.7.6.amd64/lib/site-packages/scipy/optimize/minpack.py", line 378, in leastsq
gtol, maxfev, epsfcn, factor, diag)
minpack.error: Result from function call is not a proper array of floats.
¿Qué es lo que estoy haciendo mal? ¿Es así como paso las variables independientes a la function/curve_fit
modelo function/curve_fit
?
La salida de twoD_Gaussian
debe ser 1D. Lo que puedes hacer es agregar un .ravel()
al final de la última línea, como esto:
def twoD_Gaussian((x, y), amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
xo = float(xo)
yo = float(yo)
a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
g = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo)
+ c*((y-yo)**2)))
return g.ravel()
Obviamente, necesitarás remodelar la salida para trazar, por ejemplo:
# Create x and y indices
x = np.linspace(0, 200, 201)
y = np.linspace(0, 200, 201)
x, y = np.meshgrid(x, y)
#create data
data = twoD_Gaussian((x, y), 3, 100, 100, 20, 40, 0, 10)
# plot twoD_Gaussian data generated above
plt.figure()
plt.imshow(data.reshape(201, 201))
plt.colorbar()
Haga el ajuste como antes:
# add some noise to the data and try to fit the data generated beforehand
initial_guess = (3,100,100,20,40,0,10)
data_noisy = data + 0.2*np.random.normal(size=data.shape)
popt, pcov = opt.curve_fit(twoD_Gaussian, (x, y), data_noisy, p0=initial_guess)
Y trazar los resultados:
data_fitted = twoD_Gaussian((x, y), *popt)
fig, ax = plt.subplots(1, 1)
ax.hold(True)
ax.imshow(data_noisy.reshape(201, 201), cmap=plt.cm.jet, origin=''bottom'',
extent=(x.min(), x.max(), y.min(), y.max()))
ax.contour(x, y, data_fitted.reshape(201, 201), 8, colors=''w'')
plt.show()
Para ampliar un poco la respuesta de Dietrich, obtuve el siguiente error al ejecutar la solución sugerida con Python 3.4 (en Ubuntu 14.04):
def twoD_Gaussian((x, y), amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
^
SyntaxError: invalid syntax
Ejecutar 2to3
sugirió la siguiente solución simple:
def twoD_Gaussian(xdata_tuple, amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
(x, y) = xdata_tuple
xo = float(xo)
yo = float(yo)
a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
g = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo)
+ c*((y-yo)**2)))
return g.ravel()
La razón de esto es que el desempaquetado automático de tuplas cuando se pasa a una función como parámetro se ha eliminado desde Python 3. Para obtener más información, consulte aquí: PEP 3113
curve_fit()
quiere que la dimensión de xdata
sea (2,n*m)
y no (2,n,m)
. ydata
debe tener forma (n*m)
no (n,m)
respectivamente. Así que usas ravel()
para aplanar tus arreglos 2D:
xdata = np.vstack((xx.ravel(),yy.ravel()))
ydata = data_noisy.ravel()
popt, pcov = opt.curve_fit(twoD_Gaussian, xdata, ydata, p0=initial_guess)
Por cierto: no estoy seguro de que la parametrización con los términos trigonométricos sea la mejor. Por ejemplo, tomar el descrito here podría ser un poco más sólido en cuanto a aspectos numéricos y grandes desviaciones.