Fitting a 2D gaussian profile onto a focal spot - python

I've been trying to write code to fit a 2D Gaussian profile onto some data for a focal spot. However everytime I use my code, it outputs diagonal lines for the plot. Can anyone help?
import numpy as np
from matplotlib import image
import matplotlib.pyplot as plt
import scipy.optimize as opt
def pixel_values(filename):
data_raw = image.imread(filename)
data= data_raw.ravel()
return (data)
def power(filename):
pixel=pixel_values(filename)
intensity=np.ravel(pixel)**2
total_intensity=sum(intensity)
print(np.sqrt(total_intensity))
def get_highest_pixel(filename):
data= pixel_values(filename)
highest_pixel=np.amax(data)
pixel_index = np.where(data == data.min())
print(highest_pixel)
print(pixel_index)
return (pixel_index)
x = np.linspace(-0.5,0.5 , 601)
y = np.linspace(-0.5, 0.5, 601)
X,Y = np.meshgrid(x,y)
xdata = np.vstack((X.ravel(),Y.ravel()))
def twoD_Gaussian(xdata,amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
(x, y) = xdata
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()
def fit_twoD_Gaussian(filename,initial_guess):
data_raw=pixel_values(filename)
popt, pcov = opt.curve_fit(twoD_Gaussian,xdata,data_raw, p0=initial_guess)
data_fitted= twoD_Gaussian(xdata,*popt)
return(data_fitted)
def plot_fit(filename, initial_guess):
data_fitted=fit_twoD_Gaussian(filename, initial_guess)
data_raw=pixel_values(filename)
print('the numbers are amplitude, centre position, then sigmas')
print(data_fitted)
fig, ax = plt.subplots(1,1)
ax.imshow(data_raw.reshape(601,601),cmap=plt.cm.jet, origin='centre', extent=(x.min(),x.max(),y.min(),y.max()))
ax.contour(x,y, data_fitted.reshape(601,601),8,colors='w')
plt.show()
guess=np.array([175,300,300,0.1,0.1,0,0])
plot_fit('0_0.JPG',guess)
The data has in a 2D array consisting of 601x601 pixels. So that's why I create two arrays x and y. This is what the code outputs for a rough gaussian like laser beam. The black and white image is the JPG file

Related

Fitting best possible two separate lines to a set of 2D points with python

I have a black-white image where I want to fit 2 separate lines to the edges in the image. It is easy to fit a single line with opencv, with the code below. How do I fit two best possible lines to this image. Here is the input image and 1 line result. I need something that is like the last image below.
def fit_line_to_edges(edges):
points = np.where(edges>0)
points = np.array([points[1], points[0]]).T
[vx,vy,x,y] = cv2.fitLine(points, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((edges.shape[1]-x)*vy/vx)+y)
cv2.line(edges, (edges.shape[1]-1,righty), (0,lefty), color = (136,155,112), thickness = 3)
return edges
Thanks for the help in the comments. Here is the working code using a piecewise linear function
from scipy import optimize
import matplotlib.pyplot as plt
import numpy as np
import cv2
def fit_piecewise_lines(edges):
def piecewise_linear(x, x0, y0, k1, k2):
return np.piecewise(x, [x < x0], [lambda x:k1*x + y0-k1*x0, lambda x:k2*x + y0-k2*x0])
# get x and y points from edges
points = np.where(edges>0)
points = np.array([points[1], points[0]]).T
x = points[:,0]
y = points[:,1]
# initial point
p_init = [np.mean(x), np.mean(y), 1, 1]
# set initial parameter estimates
p, e = optimize.curve_fit(piecewise_linear, x, y, p0=p_init)
# get the point locations
pt1 = (np.min(x).astype(int), piecewise_linear(np.min(x), *p).astype(int))
pt3 = (np.max(x).astype(int), piecewise_linear(np.max(x), *p).astype(int))
pt2 = (p[0].astype(int), p[1].astype(int))
# plot it
cv2.line(edges, pt1, pt2, (255,0,0), 5)
cv2.line(edges, pt2, pt3, (0,255,0), 5)
image_file = "./edges.png"
edges = cv2.imread(image_file)
fit_piecewise_lines(edges)
plt.figure(figsize=(10,10))
plt.imshow(edges)

Multiple gaussian auto-fitting in Python

The problem is to fitter on all my wavelength peaks a Gaussian in order to make a medium adjustment as accurate as possible
My question is how to make the Gaussian adjustment on all my peaks automatically without having to manually specify the coordinates of the peaks
For that, I realized the Gaussian adjustment of the brightest peaks, but I would like to generalize it to the following peaks. Subsequently, the Gaussian adjustment will allow me to obtain a polynomial adjustment fine enough to stagger pixels in wavelength
import numpy as np
from astropy.io import fits
import matplotlib.pyplot as plt
from scipy import interpolate
from tqdm import tqdm
from scipy import ndimage
import peakutils
from scipy.optimize import curve_fit
def gauss(x, x0, amp, wid):
return amp * np.exp( -((x - x0)/wid)**2)
def multi_gauss(x, *params):
y = np.zeros_like(x)
for i in range(0, len(params), 3):
x0, amp, wid = params[i:i+3]
y = y + gauss(x, x0, amp, wid)
return y
neon = fits.getdata(data_directory + wave_filename + '.fits')
neon_sp = np.mean(neon, axis= 0)
n_pix = len(neon_sp)
peaks_index = peakutils.peak.indexes(neon_sp, thres=0.05, min_dist=2)
### peals around the brightest peak
bright_index = peaks_index[np.argmax(neon_sp[peaks_index])]
delta_pix = 20
ind_min = bright_index - delta_pix
ind_max = bright_index + delta_pix
peak_select = peaks_index[np.where((peaks_index > ind_min) & (peaks_index < ind_max))]
peak_select_sort = peak_select[np.argsort(-neon_sp[peak_select])]
if peak_select_sort[1] > peak_select_sort[0] :
ind_max = bright_index + 40
else :
ind_min = bright_index - 40
peak_select = peaks_index[np.where((peaks_index > ind_min) & (peaks_index < ind_max))]
peak_select_sort = peak_select[np.argsort(-neon_sp[peak_select])]
plt.figure(num=0)
plt.clf()
plt.plot(neon_sp)
plt.plot(peaks_index,neon_sp[peaks_index], 'r+')
plt.plot(peak_select,neon_sp[peak_select], 'ro')
### Gaussian fit
x = np.arange(n_pix)
xx = np.arange(0, n_pix, .1)
n_peak = 4
bright_index_fit = np.zeros(n_peak)
for i in range(n_peak):
p = peak_select_sort[i]
guess = [p, neon_sp[p], .5]
popt, pcov = curve_fit(gauss, x, neon_sp, p0=guess)
fit = gauss(xx, *popt)
bright_index_fit[i] = popt[0]
plt.plot(xx,fit, '--')
bright_wave = [703.2, 724.5, 693.0, 743.9]

How can I make my 2D Gaussian fit to my image

I am trying to fit a 2D Gaussian to an image to find the location of the brightest point in it. My code looks like this:
import numpy as np
import astropy.io.fits as fits
import os
from astropy.stats import mad_std
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from lmfit.models import GaussianModel
from astropy.modeling import models, fitting
def gaussian(xycoor,x0, y0, sigma, amp):
'''This Function is the Gaussian Function'''
x, y = xycoor # x and y taken from fit function. Stars at 0, increases by 1, goes to length of axis
A = 1 / (2*sigma**2)
eq = amp*np.exp(-A*((x-x0)**2 + (y-y0)**2)) #Gaussian
return eq
def fit(image):
med = np.median(image)
image = image-med
image = image[0,0,:,:]
max_index = np.where(image >= np.max(image))
x0 = max_index[1] #Middle of X axis
y0 = max_index[0] #Middle of Y axis
x = np.arange(0, image.shape[1], 1) #Stars at 0, increases by 1, goes to length of axis
y = np.arange(0, image.shape[0], 1) #Stars at 0, increases by 1, goes to length of axis
xx, yy = np.meshgrid(x, y) #creates a grid to plot the function over
sigma = np.std(image) #The standard dev given in the Gaussian
amp = np.max(image) #amplitude
guess = [x0, y0, sigma, amp] #The initial guess for the gaussian fitting
low = [0,0,0,0] #start of data array
#Upper Bounds x0: length of x axis, y0: length of y axis, st dev: max value in image, amplitude: 2x the max value
upper = [image.shape[0], image.shape[1], np.max(image), np.max(image)*2]
bounds = [low, upper]
params, pcov = curve_fit(gaussian, (xx.ravel(), yy.ravel()), image.ravel(),p0 = guess, bounds = bounds) #optimal fit. Not sure what pcov is.
return params
def plotting(image, params):
fig, ax = plt.subplots()
ax.imshow(image)
ax.scatter(params[0], params[1],s = 10, c = 'red', marker = 'x')
circle = Circle((params[0], params[1]), params[2], facecolor = 'none', edgecolor = 'red', linewidth = 1)
ax.add_patch(circle)
plt.show()
data = fits.getdata('AzTECC100.fits') #read in file
med = np.median(data)
data = data - med
data = data[0,0,:,:]
parameters = fit(data)
#generates a gaussian based on the parameters given
plotting(data, parameters)
The image is plotting and the code is giving no errors but the fitting isn't working. It's just putting an x wherever the x0 and y0 are. The pixel values in my image are very small. The max value is 0.0007 and std dev is 0.0001 and the x and y are a few orders of magnitude larger. So I believe my problem is that because of this my eq is going to zero everywhere so the curve_fit is failing. I'm wondering if there's a better way to construct my gaussian so that it plots correctly?
I do not have access to your image. Instead I have generated some test "image" as follows:
y, x = np.indices((51,51))
x -= 25
y -= 25
data = 3 * np.exp(-0.7 * ((x+2)**2 + (y-1)**2))
Also, I have modified your code for plotting to increase the radius of the circle by 10:
circle = Circle((params[0], params[1]), 10 * params[2], ...)
and I commented out two more lines:
# image = image[0,0,:,:]
# data = data[0,0,:,:]
The result that I get is shown in the attached image and it looks reasonable to me:
Could it be that the issue is in how you access data from the FITS file? (e.g., image = image[0,0,:,:]) Are the data 4D array? Why do you have 4 indices?
I also saw that you have asked a similar question here: Astropy.model 2DGaussian issue in which you tried to use just astropy.modeling. I will look into that question.
NOTE: you can replace code such as
max_index = np.where(image >= np.max(image))
x0 = max_index[1] #Middle of X axis
y0 = max_index[0] #Middle of Y axis
with
y0, x0 = np.unravel_index(np.argmax(data), data.shape)

How do I fit my data to an Airy Function in python?

My code takes an image of a pinhole aperture and fits the data to a Gaussian. Using the Gaussian fit it calculates the Full-Width at Half Maximum. This tells me the resolution of my imaging system.
Here is the fit I get with my code right now:
According to the theory for pinhole diffraction images, the data should correspond to an Airy disk function. For completeness I want to fit the data to a Bessel function or Airy disk pattern. I cannot find any packages that will fit these functions.
Here is the picture I am using:
You can just make out the outer fringes around the central bright spot. Those are the fringes I want to account for in my fit.
import numpy as np
import scipy.optimize as opt
import PIL
from PIL import ImageFilter
from pylab import *
#defining the Gaussian
def gauss(x, p): # p[0]==mean, p[1]==stdev
return 1.0/(p[1]*np.sqrt(2*np.pi))*np.exp(-(x-p[0])**2/(2*p[1]**2))
im = PIL.Image.open('C:/Documents/User/3000.bmp').convert("L") #convert to array
imArr = np.array(im, dtype=float)
bg = np.average(imArr) #find the background, subtract it
imArr = imArr - bg
#get the approx coordinates of brightest spot by filtering
im2 = im.filter(ImageFilter.GaussianBlur(radius=2))
imArr2 = np.array(im2, dtype=float)
tuple = unravel_index(imArr2.argmax(), imArr2.shape)
#find and plot FWHM for the brightest spot
x = np.arange(tuple[1] - 100, tuple[1] + 100, dtype=np.float)
y = imArr[tuple[0], tuple[1] - 100:tuple[1] + 100]
y /= ((max(x) - min(x)) / len(x)) * np.sum(y) # renormalize to a proper Gaussian
p0 = [tuple[1], tuple[0]]
errfunc = lambda p, x, y: gauss(x, p) - y # distance to the target function
p1, success = opt.leastsq(errfunc, p0[:], args=(x, y))
fit_mu, fit_stdev = p1
FWHM = 2*np.sqrt(2*np.log(2))*fit_stdev
print "FWHM", FWHM
plt.plot(x,y)
plt.plot(x, gauss(x,p1), lw=3, alpha=.5, color='r')
plt.axvspan(fit_mu-FWHM/2, fit_mu+FWHM/2, facecolor='g', alpha=0.5)
plt.show()

Separating gaussian components of a curve using python

I am trying to deblend the emission lines of low resolution spectrum in order to get the gaussian components. This plot represents the kind of data I am using:
After searching a bit, the only option I found was the application of the gauest function from the kmpfit package (http://www.astro.rug.nl/software/kapteyn/kmpfittutorial.html#gauest). I have copied their example but I cannot make it work.
I wonder if anyone could please offer me any alternative to do this or how to correct my code:
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize
def CurveData():
x = np.array([3963.67285156, 3964.49560547, 3965.31835938, 3966.14111328, 3966.96362305,
3967.78637695, 3968.60913086, 3969.43188477, 3970.25463867, 3971.07714844,
3971.89990234, 3972.72265625, 3973.54541016, 3974.36791992, 3975.19067383])
y = np.array([1.75001533e-16, 2.15520995e-16, 2.85030769e-16, 4.10072843e-16, 7.17558032e-16,
1.27759917e-15, 1.57074192e-15, 1.40802933e-15, 1.45038722e-15, 1.55195653e-15,
1.09280316e-15, 4.96611341e-16, 2.68777266e-16, 1.87075114e-16, 1.64335999e-16])
return x, y
def FindMaxima(xval, yval):
xval = np.asarray(xval)
yval = np.asarray(yval)
sort_idx = np.argsort(xval)
yval = yval[sort_idx]
gradient = np.diff(yval)
maxima = np.diff((gradient > 0).view(np.int8))
ListIndeces = np.concatenate((([0],) if gradient[0] < 0 else ()) + (np.where(maxima == -1)[0] + 1,) + (([len(yval)-1],) if gradient[-1] > 0 else ()))
X_Maxima, Y_Maxima = [], []
for index in ListIndeces:
X_Maxima.append(xval[index])
Y_Maxima.append(yval[index])
return X_Maxima, Y_Maxima
def GaussianMixture_Model(p, x, ZeroLevel):
y = 0.0
N_Comps = int(len(p) / 3)
for i in range(N_Comps):
A, mu, sigma = p[i*3:(i+1)*3]
y += A * np.exp(-(x-mu)*(x-mu)/(2.0*sigma*sigma))
Output = y + ZeroLevel
return Output
def Residuals_GaussianMixture(p, x, y, ZeroLevel):
return GaussianMixture_Model(p, x, ZeroLevel) - y
Wave, Flux = CurveData()
Wave_Maxima, Flux_Maxima = FindMaxima(Wave, Flux)
EmLines_Number = len(Wave_Maxima)
ContinuumLevel = 1.64191e-16
# Define initial values
p_0 = []
for i in range(EmLines_Number):
p_0.append(Flux_Maxima[i])
p_0.append(Wave_Maxima[i])
p_0.append(2.0)
p1, conv = optimize.leastsq(Residuals_GaussianMixture, p_0[:],args=(Wave, Flux, ContinuumLevel))
Fig = plt.figure(figsize = (16, 10))
Axis1 = Fig.add_subplot(111)
Axis1.plot(Wave, Flux, label='Emission line')
Axis1.plot(Wave, GaussianMixture_Model(p1, Wave, ContinuumLevel), 'r', label='Fit with optimize.leastsq')
print p1
Axis1.plot(Wave, GaussianMixture_Model([p1[0],p1[1],p1[2]], Wave, ContinuumLevel), 'g:', label='Gaussian components')
Axis1.plot(Wave, GaussianMixture_Model([p1[3],p1[4],p1[5]], Wave, ContinuumLevel), 'g:')
Axis1.set_xlabel( r'Wavelength $(\AA)$',)
Axis1.set_ylabel('Flux' + r'$(erg\,cm^{-2} s^{-1} \AA^{-1})$')
plt.legend()
plt.show()
A typical simplistic way to fit:
def model(p,x):
A,x1,sig1,B,x2,sig2 = p
return A*np.exp(-(x-x1)**2/sig1**2) + B*np.exp(-(x-x2)**2/sig2**2)
def res(p,x,y):
return model(p,x) - y
from scipy import optimize
p0 = [1e-15,3968,2,1e-15,3972,2]
p1,conv = optimize.leastsq(res,p0[:],args=(x,y))
plot(x,y,'+') # data
#fitted function
plot(arange(3962,3976,0.1),model(p1,arange(3962,3976,0.1)),'-')
Where p0 is your initial guess. By the looks of things, you might want to use Lorentzian functions...
If you use full_output=True, you get all kind of info about the fitting. Also check out curve_fit and the fmin* functions in scipy.optimize. There are plenty of wrappers around these around, but often, like here, it's easier to use them directly.

Categories