Coverage for /Users/Newville/Codes/xraylarch/larch/xrd/xrd_pyFAI.py: 12%

205 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 that use pyFAI 

4 

5mkak 2017.03.14 

6''' 

7 

8########################################################################## 

9# IMPORT PYTHON PACKAGES 

10import os 

11import numpy as np 

12import json 

13 

14HAS_pyFAI = False 

15try: 

16 import pyFAI 

17 import pyFAI.units 

18 pyFAI.use_opencl = False 

19 HAS_pyFAI = True 

20except ImportError: 

21 pass 

22 

23from larch.io import tifffile 

24 

25########################################################################## 

26# FUNCTIONS 

27 

28def read_poni(fname): 

29 """read pyFAI PONI file to dict""" 

30 conf = dict(dist=None, wavelength=None, pixel1=None, pixel2=None, 

31 poni1=None, poni2=None, rot1=None, rot2=None, rot3=None) 

32 with open(fname, 'r') as fh: 

33 for line in fh.readlines(): 

34 line = line[:-1].strip() 

35 if line.startswith('#'): 

36 continue 

37 try: 

38 key, val = [a.strip() for a in line.split(':', 1)] 

39 key = key.lower() 

40 except: 

41 continue 

42 if key == 'detector_config': 

43 confdict = json.loads(val) 

44 for k, v in confdict.items(): 

45 k = k.lower() 

46 if k in conf: 

47 conf[k] = float(v) 

48 

49 if key == 'distance': 

50 key='dist' 

51 elif key == 'pixelsize1': 

52 key='pixel1' 

53 elif key == 'pixelsize2': 

54 key='pixel2' 

55 if key in conf: 

56 conf[key] = float(val) 

57 return conf 

58 

59def write_poni(filename, calname='', pixel1=0, pixel2=0, 

60 poni1=0, poni2=0, dist=0, rot1=0, rot2=0, rot3=0, 

61 wavelength=0, **kws): 

62 """write pyFAI PONI file""" 

63 buff = '''# XRD Calibration {calname:s} 

64# Saved {ctime:s} 

65PixelSize1: {pixel1:16.11g} 

66PixelSize2: {pixel2:16.11g} 

67Distance: {dist:16.11g} 

68Poni1: {poni1:16.11g} 

69Poni2: {poni2:16.11g} 

70Rot1: {rot1:16.11g} 

71Rot2: {rot2:16.11g} 

72Rot3: {rot3:16.11g} 

73Wavelength: {wavelength:16.11g} 

74''' 

75 with open(filename, 'w') as fh: 

76 fh.write(buff.format(calname=calname, ctime=time.ctime(), 

77 pixel1=pixel1, pixel2=pixel2, 

78 poni1=poni1, poni2=poni2, 

79 rot1=rot1, rot2=rot2, rot3=rot3, 

80 dist=dist, wavelength=wavelength)) 

81 

82 

83def return_ai(calfile): 

84 

85 if calfile is not None and os.path.exists(calfile): 

86 return pyFAI.load(calfile) 

87 

88def q_from_xy(x, y, ai=None, calfile=None): 

89 

90 if ai is None: ai = pyFAI.load(calfile) 

91 

92 try: 

93 return ai.qFunction(np.array([y,]),np.array([x,]))[0] 

94 except: 

95 return 0 

96 

97def twth_from_xy(x, y, ai=None, calfile=None, ang_units='degrees'): 

98 

99 if ai is None: ai = pyFAI.load(calfile) 

100 

101 try: 

102 twth = ai.tth(np.array([y,]),np.array([x,])) 

103 except: 

104 return 0 

105 

106 if ang_units.startswith('rad'): 

107 return twth[0] 

108 else: 

109 return np.degrees(twth[0]) 

110 

111def eta_from_xy(x, y, ai=None, calfile=None, ang_units='degrees'): 

112 

113 if ai is None: ai = pyFAI.load(calfile) 

114 

115 try: 

116 eta = ai.chi(np.array([y,]),np.array([x,])) 

117 except: 

118 return 0 

119 

120 if ang_units.startswith('rad'): 

121 return eta[0] 

122 else: 

123 return np.degrees(eta[0]) 

124 

125def read_lambda(calfile): 

126 

127 ai = pyFAI.load(calfile) 

128 return ai._wavelength*1e10 ## units A 

129 

130def integrate_xrd_row(rowxrd2d, calfile, unit='q', steps=2048, 

131 wedge_limits=None, mask=None, dark=None, 

132 flip=True): 

133 ''' 

134 Uses pyFAI (poni) calibration file to produce 1D XRD data from a row of 2D XRD images 

135 

136 Must provide pyFAI calibration file 

137 

138 rowxrd2d : 2D diffraction images for integration 

139 calfile : poni calibration file 

140 unit : unit for integration data ('2th'/'q'); default is 'q' 

141 steps : number of steps in integration data; default is 10000 

142 wedge_limits : azimuthal slice limits 

143 mask : mask array for image 

144 dark : dark image array 

145 flip : vertically flips image to correspond with Dioptas poni file calibration 

146 ''' 

147 

148 if not HAS_pyFAI: 

149 print('pyFAI not imported. Cannot calculate 1D integration.') 

150 return 

151 

152 try: 

153 ai = pyFAI.load(calfile) 

154 except: 

155 print('calibration file "%s" could not be loaded.' % calfile) 

156 return 

157 

158 if type(dark) is str: 

159 try: 

160 dark = np.array(tifffile.imread(xrd2dbkgd)) 

161 except: 

162 dark = None 

163 

164 dir = -1 if flip else 1 

165 attrs = dict(mask=mask, dark=dark, method='csr', 

166 polarization_factor=0.999, correctSolidAngle=True) 

167 

168 if unit.startswith('2th'): 

169 attrs.update({'unit':'2th_deg'}) 

170 else: 

171 attrs.update({'unit':'q_A^-1'}) 

172 

173 if wedge_limits is not None: 

174 attrs.update({'azimuth_range':wedge_limits}) 

175 

176 # print("Calc XRD 1D for row", ai, steps, attrs) 

177 q, xrd1d = [], [] 

178 for i, xrd2d in enumerate(rowxrd2d): 

179 row_q,row_xrd1d = calcXRD1d(xrd2d[::dir,:], ai, steps, attrs) 

180 q += [row_q] 

181 xrd1d += [row_xrd1d] 

182 

183 return np.array(q), np.array(xrd1d) 

184 

185def integrate_xrd(xrd2d, calfile, unit='q', steps=2048, file='', wedge_limits=None, 

186 k=None, dark=None, is_eiger=True, save=False, verbose=False): 

187 ''' 

188 Uses pyFAI (poni) calibration file and 2D XRD image to produce 1D XRD data 

189 

190 Must provide pyFAI calibration file 

191 

192 xrd2d : 2D diffraction images for integration 

193 calfile : poni calibration file 

194 unit : unit for integration data ('2th'/'q'); default is 'q' 

195 steps : number of steps in integration data; default is 10000 

196 wedge_limits : azimuthal slice limits 

197 file : filename for saving data; if '' (default) will not save 

198 mask : mask array for image 

199 dark : dark image array 

200 ''' 

201 

202 if HAS_pyFAI: 

203 try: 

204 ai = pyFAI.load(calfile) 

205 except: 

206 print('Provided calibration file could not be loaded.') 

207 return 

208 else: 

209 print('pyFAI not imported. Cannot calculate 1D integration.') 

210 return 

211 

212 attrs = {} 

213 if unit.startswith('2th'): 

214 attrs.update({'unit':'2th_deg'}) 

215 else: 

216 attrs.update({'unit':'q_A^-1'}) 

217 

218 if wedge_limits is not None: 

219 attrs.update({'azimuth_range':wedge_limits}) 

220 

221 if mask: 

222 if np.shape(mask) == np.shape(xrd2d): attrs.update({'mask':mask}) 

223 if dark: 

224 if np.shape(dark) == np.shape(xrd2d): attrs.update({'dark':dark}) 

225 

226 if file != '': 

227 if verbose: 

228 print('\nSaving %s data to file: %s' % (unit,file)) 

229 attrs.update({'filename':file}) 

230 return calcXRD1d(xrd2d, ai, steps, attrs) 

231 

232 

233def calc_cake(xrd2d, calfile, unit='q', mask=None, dark=None, 

234 xsteps=2048, ysteps=2048, verbose=False): 

235 

236 if HAS_pyFAI: 

237 try: 

238 ai = pyFAI.load(calfile) 

239 except: 

240 print('Provided calibration file could not be loaded.') 

241 return 

242 else: 

243 print('pyFAI not imported. Cannot calculate 1D integration.') 

244 attrs = {} 

245 if unit.startswith('2th'): 

246 attrs.update({'unit':'2th_deg'}) 

247 else: 

248 attrs.update({'unit':'q_A^-1'}) 

249 if mask: 

250 if np.shape(mask) == np.shape(xrd2d): attrs.update({'mask':mask}) 

251 if dark: 

252 if np.shape(dark) == np.shape(xrd2d): attrs.update({'dark':dark}) 

253 

254 return calcXRDcake(xrd2d, ai, xsteps, ysteps, attrs) 

255 

256 

257def calcXRD1d(xrd2d ,ai, steps, attrs): 

258 return ai.integrate1d(xrd2d, steps, **attrs) 

259 

260def calcXRDcake(xrd2d,ai,xstp,ystp,attrs): 

261 return ai.integrate2d(xrd2d,xstp,ystp,**attrs) ## returns I,q,eta 

262 

263def save1D(filename, xaxis, I, error=None, xaxis_unit=None, calfile=None, 

264 has_dark=False, has_flat=False, polarization_factor=None, 

265 normalization_factor=None): 

266 ''' 

267 copied and modified from pyFAI/io.py 

268 ''' 

269 if xaxis_unit is None or xaxis_unit == 'q': 

270 xaxis_unit = pyFAI.units.Q_A 

271 elif xaxis_unit.startswith('2th'): 

272 xaxis_unit = pyFAI.units.TTH_DEG 

273 

274 if calfile is None: 

275 ai = None 

276 else: 

277 ai = pyFAI.load(calfile) 

278 

279 xaxis_unit = pyFAI.units.to_unit(xaxis_unit) 

280 with open(filename, 'w') as f: 

281 f.write(make_headers(has_dark=has_dark, has_flat=has_flat, ai=ai, 

282 polarization_factor=polarization_factor, 

283 normalization_factor=normalization_factor)) 

284 try: 

285 f.write('\n# --> %s\n' % (filename)) 

286 except UnicodeError: 

287 f.write('\n# --> %s\n' % (filename.encode('utf8'))) 

288 if error is None: 

289 try: 

290 f.write('#%14s %14s\n' % (xaxis_unit.REPR, 'I ')) 

291 except: 

292 f.write('#%14s %14s\n' % (xaxis_unit.name, 'I ')) 

293 f.write('\n'.join(['%14.6e %14.6e' % (t, i) for t, i in zip(xaxis, I)])) 

294 else: 

295 f.write('#%14s %14s %14s\n' % 

296 (xaxis_unit.REPR, 'I ', 'sigma ')) 

297 f.write('\n'.join(['%14.6e %14.6e %14.6e' % (t, i, s) for t, i, s in zip(xaxis, I, error)])) 

298 f.write('\n') 

299 

300def make_headers(hdr='#', has_dark=False, has_flat=False, ai=None, 

301 polarization_factor=None, normalization_factor=None): 

302 ''' 

303 copied and modified from pyFAI/io.py 

304 ''' 

305 if ai is not None: 

306 headerLst = ['== pyFAI calibration =='] 

307 headerLst.append('SplineFile: %s' % ai.splineFile) 

308 headerLst.append('PixelSize: %.3e, %.3e m' % 

309 (ai.pixel1, ai.pixel2)) 

310 headerLst.append('PONI: %.3e, %.3e m' % (ai.poni1, ai.poni2)) 

311 headerLst.append('Distance Sample to Detector: %s m' % 

312 ai.dist) 

313 headerLst.append('Rotations: %.6f %.6f %.6f rad' % 

314 (ai.rot1, ai.rot2, ai.rot3)) 

315 headerLst += ['', '== Fit2d calibration =='] 

316 

317 f2d = ai.getFit2D() 

318 headerLst.append('Distance Sample-beamCenter: %.3f mm' % 

319 f2d['directDist']) 

320 headerLst.append('Center: x=%.3f, y=%.3f pix' % 

321 (f2d['centerX'], f2d['centerY'])) 

322 headerLst.append('Tilt: %.3f deg TiltPlanRot: %.3f deg' % 

323 (f2d['tilt'], f2d['tiltPlanRotation'])) 

324 headerLst.append('') 

325 

326 if ai._wavelength is not None: 

327 headerLst.append('Wavelength: %s' % ai.wavelength) 

328 if ai.maskfile is not None: 

329 headerLst.append('Mask File: %s' % ai.maskfile) 

330 if has_dark or (ai.darkcurrent is not None): 

331 if ai.darkfiles: 

332 headerLst.append('Dark current: %s' % ai.darkfiles) 

333 else: 

334 headerLst.append('Dark current: Done with unknown file') 

335 if has_flat or (ai.flatfield is not None): 

336 if ai.flatfiles: 

337 headerLst.append('Flat field: %s' % ai.flatfiles) 

338 else: 

339 headerLst.append('Flat field: Done with unknown file') 

340 if polarization_factor is None: 

341 try: 

342 polarization_factor = ai._polarization_factor 

343 except: 

344 pass 

345 headerLst.append('Polarization factor: %s' % polarization_factor) 

346 headerLst.append('Normalization factor: %s' % normalization_factor) 

347 else: 

348 headerLst = ' ' 

349 

350 return '\n'.join([hdr + ' ' + i for i in headerLst])