Coverage for /Users/Newville/Codes/xraylarch/larch/xrd/xrd_fitting.py: 16%
200 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2'''
3Diffraction functions require for fitting and analyzing data.
5mkak 2017.02.06 (originally written spring 2016)
6'''
9##########################################################################
10# IMPORT PYTHON PACKAGES
12import math
14import numpy as np
15from scipy import optimize,signal,interpolate
17from .xrd_tools import (d_from_q, d_from_twth, twth_from_d, twth_from_q,
18 q_from_d, q_from_twth)
21##########################################################################
22# FUNCTIONS
24def peakfilter(intthrsh,ipeaks,y,verbose=False):
25 '''
26 Returns x and y for data set corresponding to peak indices solution
27 from peakfilter() with the option of setting a minimum intensity
28 threshold for filtering peaks
29 '''
31 ipks = []
32 ipks += [i for i in ipeaks if y[i] > intthrsh]
33 if verbose: print('Peaks under intensity %i filtered out.' % intthrsh)
35 return ipks
37def peaklocater(ipeaks,x):
38 '''
39 Returns x and y for data set corresponding to peak indices solution
40 from peakfinder()
41 '''
42 xypeaks = [x[i] for i in ipeaks]
44 return np.array(xypeaks)
46def peakfinder_methods():
48 methods = []
49 try:
50 import peakutils
51 methods += ['peakutils.indexes']
52 except:
53 pass
54 try:
55 from scipy import signal
56 methods += ['scipy.signal.find_peaks_cwt']
57 except:
58 pass
60 return methods
63def peakfinder(y, method='scipy.signal.find_peaks_cwt',
64 widths=20, gapthrsh=5, thres=0.0, min_dist=10):
65 '''
66 Returns indices for peaks in y from dataset
67 '''
69 if method == 'peakutils.indexes':
70 try:
71 import peakutils
72 except:
73 print('python package peakutils not installed')
74 widths = np.arange(1,int(len(y)/widths))
75 peak_indices = signal.find_peaks_cwt(y, widths, gap_thresh=gapthrsh)
76 peak_indices = peakutils.indexes(y, thres=thres, min_dist=min_dist)
77 elif method == 'scipy.signal.find_peaks_cwt':
78 ## scipy.signal.find_peaks_cwt(vector, widths, wavelet=None, max_distances=None,
79 ## gap_thresh=None, min_length=None, min_snr=1, noise_perc=10)
80 widths = np.arange(1,int(len(y)/widths))
81 peak_indices = signal.find_peaks_cwt(y, widths, gap_thresh=gapthrsh)
83 return peak_indices
85def peakfitter(ipeaks, twth, I, verbose=True, halfwidth=40, fittype='single'):
87 peaktwth,peakFWHM,peakinty = [],[],[]
88 for j in ipeaks:
89 if j > halfwidth and (len(twth)-j) > halfwidth:
90 minval = int(j - halfwidth)
91 maxval = int(j + halfwidth)
93 if I[j] > I[minval] and I[j] > I[maxval]:
94 xdata = twth[minval:maxval]
95 ydata = I[minval:maxval]
97 try:
98 pktwth,pkfwhm,pkint = data_gaussian_fit(xdata,ydata,fittype=fittype)
99 peaktwth += [pktwth]
100 peakFWHM += [pkfwhm]
101 peakinty += [pkint]
102 except:
103 pass
105 return np.array(peaktwth),np.array(peakFWHM),np.array(peakinty)
108def data_gaussian_fit(x,y,fittype='single'):
109 '''
110 Fits a single or double Gaussian functions.
111 '''
112 meanx = sum(x)/float(len(x))
113 meany = sum(y)/float(len(y))
114 sigma = np.sqrt(sum(y*(x-meanx)**2)/float(len(x)))
116 try:
117 popt,pcov = optimize.curve_fit(gaussian,x,y,p0=[np.max(y),meanx,sigma])
118 except:
119 popt = [1,1,1]
121 if fittype == 'double':
122 a,b,c = popt
123 popt2,pcov2 = optimize.curve_fit(doublegaussian,x,y,p0=[a,b,c,np.min(y),meanx,sigma])
125 rsqu_n1 = 0
126 rsqu_n2 = 0
127 rsqu_d = 0
128 for i in range(x.shape[0]):
129 rsqu_n1 = (y[i] - gaussian(x[i],*popt))**2 + rsqu_n1
130 if fittype == 'double':
131 rsqu_n2 = (y[i] - doublegaussian(x[i],*popt2))**2 + rsqu_n2
132 rsqu_d = (y[i] - meany)**2 + rsqu_d
135 if fittype == 'double':
136 pkpos = popt2[1]
137 pkfwhm = abs(2*np.sqrt(2*math.log1p(2))*popt2[2])
138 pkint = np.max(doublegaussian(x,*popt2))
139 else:
140 pkpos = popt[1]
141 pkfwhm = abs(2*np.sqrt(2*math.log1p(2))*popt[2])
142 pkint = np.max(gaussian(x,*popt))
144 return pkpos,pkfwhm,pkint
148def gaussian(x,a,b,c):
149 return a*np.exp(-(x-b)**2/(2*c**2))
153def doublegaussian(x,a1,b1,c1,a2,b2,c2):
154 return a1*np.exp(-(x-b1)**2/(2*c1**2))+a2*np.exp(-(x-b2)**2/(2*c2**2))
157def instrumental_fit_uvw(ipeaks, twthaxis, I, halfwidth=40, verbose=True):
159 twth,FWHM,inten = peakfitter(ipeaks,twthaxis,I,halfwidth=halfwidth,
160 fittype='double',verbose=verbose)
162 tanth = np.tan(np.radians(twth/2))
163 sqFWHM = FWHM**2
165 (u,v,w) = data_poly_fit(tanth,sqFWHM,verbose=verbose)
167 if verbose:
168 print('\nFit results:')
169 for i,(twthi,fwhmi,inteni) in enumerate(zip(twth,FWHM,inten)):
170 print('Peak %i @ %0.2f deg. (fwhm %0.3f deg, %i counts)' % (i,twthi,fwhmi,inteni))
171 print('\nInstrumental broadening parameters:')
172 print('--- U : %0.8f' % u)
173 print('--- V : %0.8f' % v)
174 print('--- W : %0.8f\n' % w)
176 return(u,v,w)
179def poly_func(x,a,b,c):
180 return a*x**2 + b*x + c
183def data_poly_fit(x, y, plot=False, verbose=False):
184 '''
185 Fits a set of data with to a second order polynomial function.
186 '''
188 try:
189 popt,pcov = optimize.curve_fit(poly_func,x,y,p0=[1,1,1])
190 except:
191 print( 'WARNING: scipy.optimize.curve_fit was unsuccessful.' )
192 return [1,1,1]
194 n = len(x)
195 meany = sum(y)/n
197 rsqu_n = 0
198 rsqu_d = 0
199 for i in range(x.shape[0]):
200 rsqu_n = (y[i] - poly_func(x[i],*popt))**2 + rsqu_n
201 rsqu_d = (y[i] - meany)**2 + rsqu_d
203 if verbose:
204 print('---Polynomial Fit:')
205 print('--- U : %0.8f' % popt[0])
206 print('--- V : %0.8f' % popt[1])
207 print('--- W : %0.8f\n' % popt[2])
209 return popt
211def trim_range(data,min,max,axis=0):
213 maxi = -1
214 mini = 0
215 for i,val in enumerate(data[axis]):
216 mini = i if val < min else mini
217 maxi = i if val < max else maxi
219 return data[:,mini:maxi]
221def interpolate_for_y(x,x0,y0):
222 t = interpolate.splrep(x0,y0)
223 return interpolate.splev(x,t,der=0)
225def calc_peak():
227 x = np.arange(-2,2,0.001)
229 ## Lorentzian
230 b = 1/(1+(x**2))
232 ## Gaussian
233 c = np.exp(-math.log1p(2)*(x**2))
235 ## VOIGT: Convolve, shift, and scale
236 d = np.convolve(b,c,'same')
237 d = norm_pattern(d,c)
238 shiftx = find_max(x,d)
239 newx = x-shiftx
241 ## Diffraction pattern data.
242 ## x,b - 'Lorentzian'
243 ## x,c - 'Gaussian'
244 ## newx,d - 'Voigt'
246 return 'Lorentzian',b,x,'Gaussian',x,c,'Voigt',newx,d
249def norm_pattern(intensity,scale_int):
251 max_int = np.max(intensity)
252 scale = np.max(scale_int)
253 intensity = intensity/max_int*scale
255 return(intensity)
258def scale_100(intensity):
260 intensity = norm_pattern(intensity,100)
262 return(intensity)
266def find_max(x,y):
267 return [xi for xi,yi in zip(x,y) if yi == np.max(y)][0]
271def calcRsqu(y,ycalc):
273 ss_res = 0
274 ss_tot = 0
276 ymean = np.mean(y) #sum(y)/float(len(y))
278 for i,yi in enumerate(y):
279 ss_res = ss_res + (yi - ycalc[i])**2
280 ss_tot = ss_tot + (yi - ymean)**2
282 return (1 - (ss_res/ss_tot))
285def outliers(y,scale=0.2):
286 for i,yi in enumerate(y):
287 if i > 0 and i < (len(y)-1):
288# if yi > y[i-1]*scale and yi > y[i+1]*scale:
289# y[i] = (y[i-1]+y[i+1])/2
290 if abs(yi-y[i-1]) > yi/scale and abs(yi - y[i+1]) > yi/scale:
291 y[i] = (y[i-1]+y[i+1])/2
293 return y
296def calc_broadening(pklist, twth, wavelength, u=1.0, v=1.0, w=1.0, C=1.0, D=None, verbose=False,smooth=False):
297 '''
298 == Broadening calculation performed in 2theta - not q ==
300 pklist - location of peaks in range [q,2th,d,I]
301 twth - 2-theta axis
302 wavelength - in units A
304 u,v,w - instrumental broadening parameters
305 C - shape factor (1 for sphere)
306 D - particle size in units A (if D is None, no size broadening)
307 '''
309 ## TERMS FOR INSTRUMENTAL BROADENING
310 thrad = np.radians(pklist[1]/2)
311 Bi = np.sqrt( u*(np.tan(thrad))**2 + v*np.tan(thrad) + w )
313 ## TERMS FOR SIZE BROADENING
314 ## FWHM(2th) = (C*wavelength)/(D*math.cos(math.radians(twth/2)))
315 ## FWHM(q) = (C*wavelength)/D*(1/np.sqrt(1-termB))
316 if D is not None:
317 termB = ((wavelength*pklist[0])/(2*math.pi))**2
318 Bs = (C*wavelength)/D*(1/np.sqrt(1-termB))
320 ## Define intensity array for broadened peaks
321 Itot = np.zeros(len(twth))
323 step = twth[1]-twth[0]
325 ## Loop through all peaks
326 for i,peak in enumerate(zip(*pklist)):
327 if peak[1] > min(twth) and peak[1] < max(twth):
328 A = peak[3]
329 B = peak[1]
331 c_i = Bi[i]/abs(2*np.sqrt(2*math.log1p(2)))
333 if D is not None: c_s = Bs[i]/abs(2*np.sqrt(2*math.log1p(2)))
335 # Bm = np.sqrt(Bi[i]**2+Bs[i]**2)
336 #else:
337 # Bm = np.sqrt(Bi[i]**2)
339 #c_m = Bm/abs(2*np.sqrt(2*math.log1p(2)))
341 width1 = max(Bs[i],Bi[i]) if D is not None else Bi[i]
342 width2 = min(Bs[i],Bi[i]) if D is not None else Bi[i]
344 min2th = B-2*width1
345 max2th = B+2*width1
347 num = abs((max2th-min2th)/step)
348 twthG = np.linspace(max2th,min2th,num)
350 intBi = A*np.exp(-(twthG-B)**2/(2*c_i**2))
351 if D is not None: intBs = A*np.exp(-(twthG-B)**2/(2*c_s**2))
352 #intBm = A*np.exp(-(twthG-B)**2/(2*c_m**2))
354 if D is not None:
355 new_intensity = np.convolve(intBs,intBi,'same')
356 else:
357 new_intensity = intBi
359 new_intensity = norm_pattern(new_intensity,A)
361 X = twthG
362 Y = new_intensity
364 bins = len(twth)
365 idx = np.digitize(X,twth)
366 Ii = [np.sum(Y[idx==k]) for k in range(bins)]
367 Itot = Itot + Ii
369 if smooth:
370 Itot = outliers(Itot)
371 return Itot
373##########################################################################
374# def fit_with_minimization(q,I,parameters=None,fit_method='leastsq'):
375# '''
376# fit_method options: 'leastsq','cobyla','slsqp','nelder'
377#
378# parameters of type Parameters(): needs a,b,c,nsize,pkshift?
379#
380# my_pars = Parameters()
381# my_pars.add('nsize', value= NPsize, min= 3.0, max=100.0,vary=False)
382# my_pars.add('a', value=bkgdA, min=minA, max=maxA)
383# my_pars.add('b', value=bkgdB, min=minB, max=maxB)
384# my_pars.add('c', value=bkgdC, min=minC, max=maxC)
385# '''
386#
387# from lmfit import minimize,Parameters,report_fit
388#
389# # ## First, fit background alone:
390# # result = minimize(BACKGROUND_FUNCTION, my_pars, args=(q,I),method=fit_method)
391# # ## Second, add fitted parameters into parameter set
392# # my_pars.add('?', value=result.params['?'].value)
393# # ## Third, fit peaks on top of background
394# # result = minimize(BACKGROUND_FUNCTION+PEAKS_FUNCTION, my_pars, args=(q,I),method=fit_method)
395# # ## Fourth, view error report
396# # report_fit(result)
397# # ## Fifth, return results
398# # NPsize = result.params['nsize'].value
399# # bkgdA = result.params['a'].value
400# # bkgdB = result.params['b'].value
401# # bkgdC = result.params['c'].value
402#
403#
404# return