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

1#!/usr/bin/env python 

2''' 

3Diffraction functions require for fitting and analyzing data. 

4 

5mkak 2017.02.06 (originally written spring 2016) 

6''' 

7 

8 

9########################################################################## 

10# IMPORT PYTHON PACKAGES 

11 

12import math 

13 

14import numpy as np 

15from scipy import optimize,signal,interpolate 

16 

17from .xrd_tools import (d_from_q, d_from_twth, twth_from_d, twth_from_q, 

18 q_from_d, q_from_twth) 

19 

20 

21########################################################################## 

22# FUNCTIONS 

23 

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 ''' 

30 

31 ipks = [] 

32 ipks += [i for i in ipeaks if y[i] > intthrsh] 

33 if verbose: print('Peaks under intensity %i filtered out.' % intthrsh) 

34 

35 return ipks 

36 

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] 

43 

44 return np.array(xypeaks) 

45 

46def peakfinder_methods(): 

47 

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 

59 

60 return methods 

61 

62 

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 ''' 

68 

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) 

82 

83 return peak_indices 

84 

85def peakfitter(ipeaks, twth, I, verbose=True, halfwidth=40, fittype='single'): 

86 

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) 

92 

93 if I[j] > I[minval] and I[j] > I[maxval]: 

94 xdata = twth[minval:maxval] 

95 ydata = I[minval:maxval] 

96 

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 

104 

105 return np.array(peaktwth),np.array(peakFWHM),np.array(peakinty) 

106 

107 

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))) 

115 

116 try: 

117 popt,pcov = optimize.curve_fit(gaussian,x,y,p0=[np.max(y),meanx,sigma]) 

118 except: 

119 popt = [1,1,1] 

120 

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]) 

124 

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 

133 

134 

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)) 

143 

144 return pkpos,pkfwhm,pkint 

145 

146 

147 

148def gaussian(x,a,b,c): 

149 return a*np.exp(-(x-b)**2/(2*c**2)) 

150 

151 

152 

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)) 

155 

156 

157def instrumental_fit_uvw(ipeaks, twthaxis, I, halfwidth=40, verbose=True): 

158 

159 twth,FWHM,inten = peakfitter(ipeaks,twthaxis,I,halfwidth=halfwidth, 

160 fittype='double',verbose=verbose) 

161 

162 tanth = np.tan(np.radians(twth/2)) 

163 sqFWHM = FWHM**2 

164 

165 (u,v,w) = data_poly_fit(tanth,sqFWHM,verbose=verbose) 

166 

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) 

175 

176 return(u,v,w) 

177 

178 

179def poly_func(x,a,b,c): 

180 return a*x**2 + b*x + c 

181 

182 

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 ''' 

187 

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] 

193 

194 n = len(x) 

195 meany = sum(y)/n 

196 

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 

202 

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]) 

208 

209 return popt 

210 

211def trim_range(data,min,max,axis=0): 

212 

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 

218 

219 return data[:,mini:maxi] 

220 

221def interpolate_for_y(x,x0,y0): 

222 t = interpolate.splrep(x0,y0) 

223 return interpolate.splev(x,t,der=0) 

224 

225def calc_peak(): 

226 

227 x = np.arange(-2,2,0.001) 

228 

229 ## Lorentzian 

230 b = 1/(1+(x**2)) 

231 

232 ## Gaussian 

233 c = np.exp(-math.log1p(2)*(x**2)) 

234 

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 

240 

241 ## Diffraction pattern data. 

242 ## x,b - 'Lorentzian' 

243 ## x,c - 'Gaussian' 

244 ## newx,d - 'Voigt' 

245 

246 return 'Lorentzian',b,x,'Gaussian',x,c,'Voigt',newx,d 

247 

248 

249def norm_pattern(intensity,scale_int): 

250 

251 max_int = np.max(intensity) 

252 scale = np.max(scale_int) 

253 intensity = intensity/max_int*scale 

254 

255 return(intensity) 

256 

257 

258def scale_100(intensity): 

259 

260 intensity = norm_pattern(intensity,100) 

261 

262 return(intensity) 

263 

264 

265 

266def find_max(x,y): 

267 return [xi for xi,yi in zip(x,y) if yi == np.max(y)][0] 

268 

269 

270 

271def calcRsqu(y,ycalc): 

272 

273 ss_res = 0 

274 ss_tot = 0 

275 

276 ymean = np.mean(y) #sum(y)/float(len(y)) 

277 

278 for i,yi in enumerate(y): 

279 ss_res = ss_res + (yi - ycalc[i])**2 

280 ss_tot = ss_tot + (yi - ymean)**2 

281 

282 return (1 - (ss_res/ss_tot)) 

283 

284 

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 

292 

293 return y 

294 

295 

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 == 

299 

300 pklist - location of peaks in range [q,2th,d,I] 

301 twth - 2-theta axis 

302 wavelength - in units A 

303 

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 ''' 

308 

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 ) 

312 

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)) 

319 

320 ## Define intensity array for broadened peaks 

321 Itot = np.zeros(len(twth)) 

322 

323 step = twth[1]-twth[0] 

324 

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] 

330 

331 c_i = Bi[i]/abs(2*np.sqrt(2*math.log1p(2))) 

332 

333 if D is not None: c_s = Bs[i]/abs(2*np.sqrt(2*math.log1p(2))) 

334 

335 # Bm = np.sqrt(Bi[i]**2+Bs[i]**2) 

336 #else: 

337 # Bm = np.sqrt(Bi[i]**2) 

338 

339 #c_m = Bm/abs(2*np.sqrt(2*math.log1p(2))) 

340 

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] 

343 

344 min2th = B-2*width1 

345 max2th = B+2*width1 

346 

347 num = abs((max2th-min2th)/step) 

348 twthG = np.linspace(max2th,min2th,num) 

349 

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)) 

353 

354 if D is not None: 

355 new_intensity = np.convolve(intBs,intBi,'same') 

356 else: 

357 new_intensity = intBi 

358 

359 new_intensity = norm_pattern(new_intensity,A) 

360 

361 X = twthG 

362 Y = new_intensity 

363 

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 

368 

369 if smooth: 

370 Itot = outliers(Itot) 

371 return Itot 

372 

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