/
fpc.py
273 lines (225 loc) · 8.61 KB
/
fpc.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
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 02 13:23:48 2013
@author: rjs3
"""
from __future__ import division, print_function
import csv, os, sys
ver = sys.version_info.major
import numpy as np
import matplotlib.pyplot as plt
from xml.etree import ElementTree as et
if ver >= 3:
from tkinter import Tk
from tkinter.filedialog import askopenfilename,asksaveasfilename
from tkinter.simpledialog import askfloat
else:
from Tkinter import Tk
from tkFileDialog import askopenfilename,asksaveasfilename
from tkSimpleDialog import askfloat
def get_files():
'''
Open a dialog and return a set of files to parse.
'''
# we don't want a full GUI, so keep the root window from appearing
Tk().withdraw()
# show an "Open" dialog box and return the paths to the selected files
fullpaths = askopenfilename(multiple=1,#defaultextension='.xrdml',
filetypes=[('XRDML','.xrdml'),('CSV','.csv'),('All files','.*')])
if len(fullpaths):
print('User opened:', *fullpaths, sep='\n')
else:
print('No files selected')
raise ExitException('Exiting, not an error')
return fullpaths
def crop_noise(counts):
keepers = np.zeros_like(counts,dtype=bool)
for i,count in enumerate(counts):
if count:
keepers[i] = True
else:
break
if not any(keepers):
raise RuntimeError('No nonzero data in this file')
return keepers
def load_csv(fullpath):
'''
Parse Philips CSV files and return the arrays "angle", "cps", and "dcps".
'''
headers={}
with open(fullpath,'r') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
csvheader=row[0]
if csvheader=='Sample identification':
headers['title']=row[1].strip()
elif csvheader=='K-Alpha1 wavelength':
headers['wavelength']=row[1]
elif csvheader=='File date and time':
headers['date']=row[1]
elif csvheader=='Time per step':
time=float(row[1])
elif csvheader=='Angle':
break
#Generate a list of [angle, count] "datapoint" lists
data=[[float(row[0]),float(row[1])] for row in reader]
# split them into individual lists, then convert to arrays
angle, counts = tuple(zip(*data))
angle, counts = np.array(angle), np.array(counts)
cps=counts/time
dcps=np.sqrt(counts)/time
keepers = crop_noise(counts)
return headers, angle[keepers], cps[keepers], dcps[keepers]
def load_xrdml(fullpath):
'''
Parse Philips XRDML and return the arrays "angle", "cps", and "dcps".
'''
headers={}
xrdtree=et.parse(fullpath)
ns='/{http://www.xrdml.com/XRDMeasurement/1.0}'
headers['title']=xrdtree.findtext(ns.join(('.','sample','id'))).strip()
headers['wavelength']=xrdtree.findtext(ns.join(('.','xrdMeasurement',
'usedWavelength','kAlpha1')))
headers['instrument']='X-ray'
headers['date']=xrdtree.findtext(ns.join(('.','xrdMeasurement','scan',
'header','startTimeStamp')))
dataPoints=xrdtree.find(ns.join(('.','xrdMeasurement','scan','dataPoints')))
for child in dataPoints:
tag=child.tag
if tag == ns[1:]+'positions' and child.get('axis')=='Omega':
startangle=float(child.findtext('.'+ns+'startPosition'))
stopangle=float(child.findtext('.'+ns+'endPosition'))
elif tag == ns[1:]+'commonCountingTime':
time=float(child.text)
elif tag == ns[1:]+'intensities':
counts=child.text
counts=np.fromstring(counts,sep=' ',dtype=int)
cps=counts/time
dcps=np.sqrt(counts)/time
angle=np.linspace(startangle,stopangle,len(cps))
keepers = crop_noise(counts)
return headers, angle[keepers], cps[keepers], dcps[keepers]
def stitch_data(fullpaths):
'''
Load data into list, zip it up, test it, and stitch/sort if necessary
'''
data=[load_xrdml(fullpath)
for fullpath in fullpaths if fullpath.endswith('.xrdml')]
data+=[load_csv(fullpath)
for fullpath in fullpaths if fullpath.endswith('.csv')]
# Cut out early if we only load one file
if len(data)==0:
print('no supported types selected')
raise ExitException('Exiting, not an error')
if len(data)==1:
return data[0]
# Zipped is way more convenient here
data=list(zip(*data))
# Test if our data come from the same sample.
sentinel=None
for datum in data[0]:
if sentinel is None:
sentinel = datum['title']
elif sentinel != datum['title']:
raise ValueError("Data do not appear to be from the same sample."
" Try rewriting the sample IDs to match.",
sentinel,datum['title'])
# Stitch away if we get this far
headers = data[0][0]
angle = np.concatenate(data[1])
cps = np.concatenate(data[2])
dcps = np.concatenate(data[3])
# test if our data are already sorted ("if all monotonically increasing")
if np.all(np.diff(angle) >= 0.0):
return headers, angle, cps, dcps
# then sort based on angle
sortind = np.argsort(angle)
return headers, angle[sortind], cps[sortind], dcps[sortind]
def mymessage(text):
print(text)
plt.title(text,fontsize=16)
plt.draw()
def write_refl(headers, q, refl, drefl, path):
'''
Open a file where the previous was located, and drop a refl with the same
name as default.
'''
# we don't want a full GUI, so keep the root window from appearing
Tk().withdraw()
fullpath=asksaveasfilename(initialdir=path,
initialfile=headers['title']+'.refl',
defaultextension='.refl',
filetypes=[('reflectivity','.refl')])
# fullpath = re.findall('\{(.*?)\}', s)
if len(fullpath)==0:
print('Results not saved')
return
textlist = ['#pythonfootprintcorrect 1 1 2014-09-11']
for header in headers:
textlist.append('#'+header+' '+headers[header])
textlist.append('#columns Qz R dR')
for point in tuple(zip(q,refl,drefl)):
textlist.append('{:.12g} {:.12g} {:.12g}'.format(*point))
# print('\n'.join(textlist))
with open(fullpath, 'w') as reflfile:
reflfile.writelines('\n'.join(textlist))
print('Saved successfully as:', fullpath)
def q_from_angle(angle,wavelength=1.5405980):
return 4*np.pi/wavelength*np.sin(np.pi/180*angle)
def footprintCorrect(fullpaths=None):
'''
As a script, ask for filenames, load/stitch data, plot data,
collect footprint range, calc footprint, and write .refl files.
'''
if fullpaths is None:
fullpaths = get_files()
path=os.path.split(fullpaths[0])[0]
headers,angle,cps,dcps=stitch_data(fullpaths)
wavelength=float(headers['wavelength'])
q=q_from_angle(angle,wavelength)
plt.plot(q,cps,'k-')
plt.axis([min(q),q[np.argmax(cps)]*2,0,1.1*max(cps)])
mymessage('Click the beginning and end of the footprint region')
points=plt.ginput(2,timeout=100)
if len(points)<2:
raise ExitException('Point selection timeout, not an error')
# cutoff=askfloat('Cutoff angle','A footprint cutoff angle in degrees',
# initialvalue=1.0)
Tk().withdraw()
width=askfloat('Sample width','Average sample width in mm',initialvalue=24.5)
if width is None:
raise ExitException('Exiting, not an error')
cutoff=np.arcsin(.4/width)/np.pi*180
q_cut=q_from_angle(cutoff,wavelength)
start,stop = points[0][0], points[1][0]
keepers = q > start
fit_range = np.logical_and(keepers, q < stop)
p = np.polyfit(q[fit_range],cps[fit_range],1)
q_correct = q[keepers]
footprint = np.polyval(p, q_correct)
fp_region = q_correct<q_cut
footprint[np.logical_not(fp_region)]=np.polyval(p,q_cut)
plt.plot(q_correct,footprint,'r--')
plt.draw()
refl_correct = cps[keepers]/footprint
refl_correct = refl_correct/max(refl_correct)
drefl_correct = dcps[keepers]/footprint/max(refl_correct)
plt.figure()
plt.semilogy(q,cps/max(cps),'k--')
plt.semilogy(q_correct,footprint/max(cps),'r:')
plt.semilogy(q_correct,refl_correct,'k-')
mymessage('Resulting reflectivity')
plt.draw()
plt.show(block=False)
write_refl(headers,q_correct,refl_correct,drefl_correct,path)
return q_correct,refl_correct,drefl_correct
class ExitException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
if __name__ == '__main__':
try:
footprintCorrect() # We don't need no stinking commandline arguments
except ExitException:
pass