/
AE.py
executable file
·363 lines (287 loc) · 13.6 KB
/
AE.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/python
from numpy import *
from scipy import *
import numpy as np
from scipy.special import *
from scipy import optimize
import loglib
import helplib as hl
import argparse
import os, sys
import re
#to compile on windows
if os.name == 'nt':
from scipy.sparse.csgraph import _validation
#using the argparse module to make use of command line options
parser = argparse.ArgumentParser(description="Fit Appearance Energy measurements and extract the AE")
#we need at least one thing to fit
filegroup = parser.add_mutually_exclusive_group(required=True)
filegroup.add_argument("--filename", help="Specify a filename to be fitted")
filegroup.add_argument("--folder", help="Specify a folder. All files contained in the folder will be fitted")
filegroup.add_argument("--filelist", help="Specify a file that includes a list of filenames to be fitted")
#those are optional
parser.add_argument("--alpha", "-a", help="Specify the exponent for the fit function. If not specified, it will be fitted as well", type = float)
parser.add_argument("--fwhm", "-s", help="Specify the FWHM-resolution in eV. If not specified, 1 eV will be assumed", type = float)
parser.add_argument("--linearbackground", help="Set this, if you want to fit a linear (non-constant) background.", action = 'store_true')
parser.add_argument("--noshow", help="Do not show the plot windows.", action = 'store_true')
parser.add_argument("--nosave", help="Do not save the plots.", action = 'store_true')
parser.add_argument("--writetoplot", "-w", help="Write AE to plot and mark it.", action = 'store_true')
parser.add_argument("--writefit", help="Write a file with an array that contains the x- and y-values of the fit.", action = 'store_true')
parser.add_argument("--outputfolder", help="This option can be used to output files to a specific directory.")
parser.add_argument('--version', action='version', version='r1')
#parse it
args = parser.parse_args()
#now we can assign variables from the given arguments
fwhm = args.fwhm
alpha = args.alpha
linearbackground = args.linearbackground
#we need an output folder. default is 'output'
outputfolder = 'output'
if args.outputfolder is not None:
outputfolder = args.outputfolder.rstrip('/')
if not os.path.exists(outputfolder):
os.makedirs(outputfolder)
# derive the log class from the loglib and add a function to write AE parameters
class AELog(loglib.Log):
def AE_fit_p(self, params, alpha, min, max, linearbackground, fwhm, offsetfixed):
if alpha is not None:
self.write('AE: %f (Alpha fixed to %f)' % (params[1], alpha))
else:
self.write('Alpha: %s, AE: %s' % (params[3], params[1]))
if offsetfixed is not None:
self.write('Offset fixed at: %s, Slope: %s' % (offsetfixed, params[4]))
else:
self.write('Offset: %s, Slope: %s' % (params[0], params[4]))
self.write('Energy Resolution was set to %s eV FWHM' % fwhm)
if min is not None:
self.write('Fit was started at %s eV.' % min)
if max is not None:
self.write('Fit was ended at %s eV.' % max)
if linearbackground is True:
self.write('A linear background (non-constant) was used.')
def printargs(self):
if self.cmdargs.filename is not None:
self.write('AE.py is in filename-mode.')
elif self.cmdargs.folder is not None:
self.write('AE.py is in folder-mode.')
elif self.cmdargs.filelist is not None:
self.write('AE.py is in filelist-mode.')
if self.cmdargs.alpha is not None:
self.write('Alpha was set in the command line to %s.' % self.cmdargs.alpha)
if self.cmdargs.fwhm is not None:
self.write('Energy resolution was set in the command line to %s eV FWHM.' % self.cmdargs.fwhm)
if self.cmdargs.linearbackground is True:
self.write('AE.py was set to fit a linear background (non-constant) from command line.')
if self.cmdargs.noshow is True:
self.write('AE.py was set to not show any plots from command line.')
if self.cmdargs.nosave is True:
self.write('AE.py was set to not save any plots from command line.')
log = AELog(outputfolder = outputfolder)
#set arguments in the log object
log.setargs(args)
#this is tricky. if we don't plot, we need to use a different backend for matplotlib, as the normal one crashes if plot is not called
import matplotlib
if (args.noshow is True) and (args.nosave is False):
matplotlib.use('PDF')
#now we can import fitlib and matplotlib completely, because the backend is now set
import matplotlib.pyplot as plt
import fitlib as fl
#we need this to make a filelist list, that contains filenames
filelist = []
#here we go through the 3 cases: filename, folder, filelist
#this is the easiest case - add the filename to the filelist
if args.filename is not None:
filelist.append([args.filename, os.path.basename(args.filename)])
elif args.folder is not None:
#adjust file path in case we're running on fucking windows
args.folder = os.path.normcase(args.folder)
#lets go through that folder and add every filename to the filelist
caldirlist = os.listdir(args.folder)
for file in caldirlist:
filelist.append([os.path.join(args.folder, file), file])
elif args.filelist is not None:
#in this we have to read the list of filenames in a file
f = hl.openfile(args.filelist)
#we can also compile a regex for the optional arguments
argre = re.compile('^[a-z]+=(([0-9]+(\.)?[0-9]?)|(True|False))+$')
for line in f:
#we split the array by whitespaces or tabs (split tries both)
line = line.strip('\r\n').split()
if len(line) > 0:
#first argument should be the filename + path, therefore appending it to a temp array together with the filename
linearray = [line[0], os.path.basename(line[0])]
#first argument of the list out
del line[0]
#append rest (could also be zero length, doesn't matter)
linearray = linearray + line
#append the whole list to the filelist
filelist.append(linearray)
#if there are too many plots, we shouldn't show them all
if len(filelist) > 5:
args.noshow = True
log.write('Not showing any plots, because there are more than 5 files to deal with.')
#one empty line in the logfile
log.emptyline()
#let's walk our filelist
for file in filelist:
#this variable is set to false if we encounter a non-readable file
usefulfile = True
try:
data = hl.readfile(file[0])
except IOError:
usefulfile = False
log.ioerror(file[0])
if usefulfile is True:
#default values for initial guesses
offset = float64(10)
ea = float64(8)
fwhm = float64(1.0)
#by default we don't cut away data
minfit = None
maxfit = None
#by default we don't have the offset fixed
offsetfixed = None
#we want to plot the complete data (and not a subset), so we copy it here
#in case it gets cut later
complete_data = data
#if there were arguments specified in the filelist, we set them here
if len(file) > 2:
#reset other values that are read from the command line
alpha = None
linearbackground = False
#loop through all given arguments
for arg in file:
# remove brackets
arg = arg.replace('[', '').replace(']', '')
#are they matching "arg=value" where value is a float or int
if argre.match(arg):
#split them up
arg = arg.split('=', 2)
if arg[0] == 'min':
minfit = arg[1]
if arg[0] == 'max':
maxfit = arg[1]
if arg[0] == 'offset':
offset = float64(arg[1])
if arg[0] == 'offsetfixed':
offsetfixed = float64(arg[1])
if arg[0] == 'ea':
ea = float64(arg[1])
if arg[0] == 'alpha':
alpha = float64(arg[1])
if arg[0] == 'fwhm':
fwhm = float64(arg[1])
if arg[0] == 'linearbackground':
if arg[1] == 'True':
linearbackground = True
elif arg[1] == 'False':
linearbackground = False
#doesn't do anything if minfit and maxfit are None (and they are by default)
data = fl.cutarray(data, lowerlim = minfit, upperlim = maxfit)
#if the standard guess of ea is outside the data range, we set it to the middle of the range
datamin = float64(data[:,0].min())
datamax = float64(data[:,0].max())
if (ea < datamin) or (ea > datamax):
ea = (datamax - datamin) / 2 + datamin
#if alpha or linearbackground were set from commandline, we overwrite the settings from the file
if args.alpha is not None:
alpha = args.alpha
log.write('Overwriting alpha from command line!')
if args.linearbackground is True:
linearbackground = args.linearbackground
log.write('Overwriting linear background from command line!')
if args.fwhm is not None:
fwhm = args.fwhm
log.write('Overwriting FWHM from command line!')
#depending on the situation of alpha and the lin background we need different amounts of params
"""
if (alpha is None) and (linearbackground is False):
p0 = [0]*4
p0[0] = offset
p0[1] = ea
p0[2] = 1
p0[3] = 1
elif (alpha is not None) and (linearbackground is False):
p0 = [0]*3
p0[0] = offset
p0[1] = ea
p0[2] = 1
elif (alpha is None) and (linearbackground is True):
p0 = [0]*5
p0[0] = offset
p0[1] = ea
p0[2] = 1
p0[3] = 1
p0[4] = 1
elif (alpha is not None) and (linearbackground is True):
p0 = [0]*4
p0[0] = offset
p0[1] = ea
p0[2] = 1
p0[3] = 1
"""
p = [0]*5
p[0] = offset
p[1] = ea
p[2] = 1
p[3] = 1
p[4] = 0
#retrieve function for Appearance Energy - the alpha is None if not specified, hence returning a function with alpha fit-able
#ae_func = fl.get_AE_func(sigma, alpha, linearbackground)
sigma = fwhm / (2*sqrt(2*np.log(2)))
ae_func = fl.AE_func(alpha, offsetfixed, linearbackground)
ae_func = eval(ae_func)
#actually fit
p1 = fl.fit_function_to_data(data, ae_func, p)
#log success
if p1 is not None:
log.write('============================')
log.write('Fitted file %s with success.' % file[0])
log.AE_fit_p(p1, alpha, minfit, maxfit, linearbackground, fwhm, offsetfixed)
else:
log.write('Failed with fitting of file %s.' % file[0])
#we need to create a more speaking filename
additions = ''
additions += '_fwhm=%s' % str(fwhm)
if alpha is not None:
additions += '_alpha=%s' % alpha
if minfit is not None:
additions += '_min=%s' % minfit
if maxfit is not None:
additions += '_max=%s' % maxfit
if linearbackground is True:
additions += '_linearbackground'
if offsetfixed is not None:
additions += '_offsetfixed=%s' % offsetfixed
# we offer an option to write an array of x- and y-values to a file
if args.writefit is True:
fitdata = fl.data_from_fit_and_parameters(data, ae_func, p1)
arrayfilename = os.path.normcase(os.path.join(os.path.dirname(sys.argv[0]), outputfolder + '/' + file[1] + additions + '_fitarray.txt'))
hl.writearray(fitdata, arrayfilename)
log.write('Wrote array to %s' % arrayfilename)
#we don't even need to plot if we neither save nor show
if (args.noshow is False) or (args.nosave is False):
fig1 = plt.figure()
ae_x = p1[1]
fl.plotES(complete_data, file[1] + ' / AE = %.2f' % ae_x)
fl.plot_fit(data, ae_func, p1)
#we annotate the AE in the plot
if args.writetoplot is True:
# which alpha?
if alpha is not None:
alpha_value = alpha
else:
alpha_value = p1[3]
annotate_string = 'AE = %.2f\n$\\alpha$ = %.3f' %(ae_x, alpha_value)
fig1.text(0.15, 0.85, annotate_string,
verticalalignment='top', horizontalalignment='left',
color='green', fontsize=15)
if args.nosave is False:
plotfile = os.path.normcase(os.path.join(os.path.dirname(sys.argv[0]), outputfolder + '/' + file[1] + additions + '.pdf'))
plt.savefig(plotfile, format='pdf')
log.write('Plotted to file: %s' % plotfile)
#done
log.stop()
#showing the plot happens in the end, so to show all windows at the same time and not block the script execution
if args.noshow is False:
plt.show()