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
« 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
5mkak 2017.03.14
6'''
8##########################################################################
9# IMPORT PYTHON PACKAGES
10import os
11import numpy as np
12import json
14HAS_pyFAI = False
15try:
16 import pyFAI
17 import pyFAI.units
18 pyFAI.use_opencl = False
19 HAS_pyFAI = True
20except ImportError:
21 pass
23from larch.io import tifffile
25##########################################################################
26# FUNCTIONS
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)
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
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))
83def return_ai(calfile):
85 if calfile is not None and os.path.exists(calfile):
86 return pyFAI.load(calfile)
88def q_from_xy(x, y, ai=None, calfile=None):
90 if ai is None: ai = pyFAI.load(calfile)
92 try:
93 return ai.qFunction(np.array([y,]),np.array([x,]))[0]
94 except:
95 return 0
97def twth_from_xy(x, y, ai=None, calfile=None, ang_units='degrees'):
99 if ai is None: ai = pyFAI.load(calfile)
101 try:
102 twth = ai.tth(np.array([y,]),np.array([x,]))
103 except:
104 return 0
106 if ang_units.startswith('rad'):
107 return twth[0]
108 else:
109 return np.degrees(twth[0])
111def eta_from_xy(x, y, ai=None, calfile=None, ang_units='degrees'):
113 if ai is None: ai = pyFAI.load(calfile)
115 try:
116 eta = ai.chi(np.array([y,]),np.array([x,]))
117 except:
118 return 0
120 if ang_units.startswith('rad'):
121 return eta[0]
122 else:
123 return np.degrees(eta[0])
125def read_lambda(calfile):
127 ai = pyFAI.load(calfile)
128 return ai._wavelength*1e10 ## units A
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
136 Must provide pyFAI calibration file
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 '''
148 if not HAS_pyFAI:
149 print('pyFAI not imported. Cannot calculate 1D integration.')
150 return
152 try:
153 ai = pyFAI.load(calfile)
154 except:
155 print('calibration file "%s" could not be loaded.' % calfile)
156 return
158 if type(dark) is str:
159 try:
160 dark = np.array(tifffile.imread(xrd2dbkgd))
161 except:
162 dark = None
164 dir = -1 if flip else 1
165 attrs = dict(mask=mask, dark=dark, method='csr',
166 polarization_factor=0.999, correctSolidAngle=True)
168 if unit.startswith('2th'):
169 attrs.update({'unit':'2th_deg'})
170 else:
171 attrs.update({'unit':'q_A^-1'})
173 if wedge_limits is not None:
174 attrs.update({'azimuth_range':wedge_limits})
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]
183 return np.array(q), np.array(xrd1d)
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
190 Must provide pyFAI calibration file
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 '''
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
212 attrs = {}
213 if unit.startswith('2th'):
214 attrs.update({'unit':'2th_deg'})
215 else:
216 attrs.update({'unit':'q_A^-1'})
218 if wedge_limits is not None:
219 attrs.update({'azimuth_range':wedge_limits})
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})
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)
233def calc_cake(xrd2d, calfile, unit='q', mask=None, dark=None,
234 xsteps=2048, ysteps=2048, verbose=False):
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})
254 return calcXRDcake(xrd2d, ai, xsteps, ysteps, attrs)
257def calcXRD1d(xrd2d ,ai, steps, attrs):
258 return ai.integrate1d(xrd2d, steps, **attrs)
260def calcXRDcake(xrd2d,ai,xstp,ystp,attrs):
261 return ai.integrate2d(xrd2d,xstp,ystp,**attrs) ## returns I,q,eta
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
274 if calfile is None:
275 ai = None
276 else:
277 ai = pyFAI.load(calfile)
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')
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 ==']
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('')
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 = ' '
350 return '\n'.join([hdr + ' ' + i for i in headerLst])