pyTEMlib 0.2020.11.1__py3-none-any.whl → 0.2024.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyTEMlib might be problematic. Click here for more details.
- pyTEMlib/__init__.py +11 -11
- pyTEMlib/animation.py +631 -0
- pyTEMlib/atom_tools.py +240 -245
- pyTEMlib/config_dir.py +57 -33
- pyTEMlib/core_loss_widget.py +658 -0
- pyTEMlib/crystal_tools.py +1255 -0
- pyTEMlib/diffraction_plot.py +756 -0
- pyTEMlib/dynamic_scattering.py +293 -0
- pyTEMlib/eds_tools.py +609 -0
- pyTEMlib/eels_dialog.py +749 -491
- pyTEMlib/{interactive_eels.py → eels_dialog_utilities.py} +1199 -1177
- pyTEMlib/eels_tools.py +2031 -1698
- pyTEMlib/file_tools.py +1276 -560
- pyTEMlib/file_tools_qt.py +193 -0
- pyTEMlib/graph_tools.py +1166 -450
- pyTEMlib/graph_viz.py +449 -0
- pyTEMlib/image_dialog.py +158 -0
- pyTEMlib/image_dlg.py +146 -232
- pyTEMlib/image_tools.py +1399 -1028
- pyTEMlib/info_widget.py +933 -0
- pyTEMlib/interactive_image.py +1 -226
- pyTEMlib/kinematic_scattering.py +1196 -0
- pyTEMlib/low_loss_widget.py +176 -0
- pyTEMlib/microscope.py +61 -81
- pyTEMlib/peak_dialog.py +1047 -410
- pyTEMlib/peak_dlg.py +286 -242
- pyTEMlib/probe_tools.py +653 -207
- pyTEMlib/sidpy_tools.py +153 -136
- pyTEMlib/simulation_tools.py +104 -87
- pyTEMlib/version.py +6 -3
- pyTEMlib/xrpa_x_sections.py +20972 -0
- {pyTEMlib-0.2020.11.1.dist-info → pyTEMlib-0.2024.9.0.dist-info}/LICENSE +21 -21
- pyTEMlib-0.2024.9.0.dist-info/METADATA +92 -0
- pyTEMlib-0.2024.9.0.dist-info/RECORD +37 -0
- {pyTEMlib-0.2020.11.1.dist-info → pyTEMlib-0.2024.9.0.dist-info}/WHEEL +5 -5
- {pyTEMlib-0.2020.11.1.dist-info → pyTEMlib-0.2024.9.0.dist-info}/entry_points.txt +0 -1
- pyTEMlib/KinsCat.py +0 -2758
- pyTEMlib/__version__.py +0 -2
- pyTEMlib/data/TEMlibrc +0 -68
- pyTEMlib/data/edges_db.csv +0 -189
- pyTEMlib/data/edges_db.pkl +0 -0
- pyTEMlib/data/fparam.txt +0 -103
- pyTEMlib/data/microscopes.csv +0 -7
- pyTEMlib/data/microscopes.xml +0 -167
- pyTEMlib/data/path.txt +0 -1
- pyTEMlib/defaults_parser.py +0 -90
- pyTEMlib/dm3_reader.py +0 -613
- pyTEMlib/edges_db.py +0 -76
- pyTEMlib/eels_dlg.py +0 -224
- pyTEMlib/hdf_utils.py +0 -483
- pyTEMlib/image_tools1.py +0 -2194
- pyTEMlib/info_dialog.py +0 -237
- pyTEMlib/info_dlg.py +0 -202
- pyTEMlib/nion_reader.py +0 -297
- pyTEMlib/nsi_reader.py +0 -170
- pyTEMlib/structure_tools.py +0 -316
- pyTEMlib/test.py +0 -2072
- pyTEMlib-0.2020.11.1.dist-info/METADATA +0 -20
- pyTEMlib-0.2020.11.1.dist-info/RECORD +0 -45
- {pyTEMlib-0.2020.11.1.dist-info → pyTEMlib-0.2024.9.0.dist-info}/top_level.txt +0 -0
pyTEMlib/eds_tools.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
eds_tools
|
|
3
|
+
Model based quantification of energy-dispersive X-ray spectroscopy data
|
|
4
|
+
Copyright by Gerd Duscher
|
|
5
|
+
|
|
6
|
+
The University of Tennessee, Knoxville
|
|
7
|
+
Department of Materials Science & Engineering
|
|
8
|
+
|
|
9
|
+
Sources:
|
|
10
|
+
|
|
11
|
+
Units:
|
|
12
|
+
everything is in SI units, except length is given in nm and angles in mrad.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
See the notebooks for examples of these routines
|
|
16
|
+
|
|
17
|
+
All the input and output is done through a dictionary which is to be found in the meta_data
|
|
18
|
+
attribute of the sidpy.Dataset
|
|
19
|
+
"""
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
import scipy
|
|
23
|
+
from scipy.interpolate import interp1d, splrep # splev, splint
|
|
24
|
+
from scipy import interpolate
|
|
25
|
+
from scipy.signal import peak_prominences
|
|
26
|
+
from scipy.ndimage import gaussian_filter
|
|
27
|
+
from sklearn.mixture import GaussianMixture
|
|
28
|
+
from sklearn.cluster import KMeans
|
|
29
|
+
import scipy.constants as const
|
|
30
|
+
|
|
31
|
+
from scipy import constants
|
|
32
|
+
import matplotlib.pyplot as plt
|
|
33
|
+
# import matplotlib.patches as patches
|
|
34
|
+
|
|
35
|
+
# from matplotlib.widgets import SpanSelector
|
|
36
|
+
# import ipywidgets as widgets
|
|
37
|
+
# from IPython.display import display
|
|
38
|
+
|
|
39
|
+
import requests
|
|
40
|
+
|
|
41
|
+
from scipy.optimize import leastsq # least square fitting routine fo scipy
|
|
42
|
+
|
|
43
|
+
import sidpy
|
|
44
|
+
|
|
45
|
+
import pickle # pkg_resources
|
|
46
|
+
import pyTEMlib.eels_tools
|
|
47
|
+
from pyTEMlib.xrpa_x_sections import x_sections
|
|
48
|
+
|
|
49
|
+
elements_list = pyTEMlib.eels_tools.elements
|
|
50
|
+
|
|
51
|
+
shell_occupancy = {'K1': 2, 'L1': 2, 'L2': 2, 'L3': 4, 'M1': 2, 'M2': 2, 'M3': 4, 'M4': 4, 'M5': 6,
|
|
52
|
+
'N1': 2, 'N2': 2, 'N3': 4, 'N4': 4, 'N5': 6, 'N6': 6, 'N7': 8,
|
|
53
|
+
'O1': 2, 'O2': 2, 'O3': 4, 'O4': 4, 'O5': 6, 'O6': 6, 'O7': 8, 'O8': 8, 'O9': 10}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def detector_response(dataset):
|
|
57
|
+
tags = dataset.metadata['experiment']
|
|
58
|
+
|
|
59
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
|
|
60
|
+
if 'start_channel' not in tags['detector']:
|
|
61
|
+
tags['detector']['start_channel'] = np.searchsorted(energy_scale, 100)
|
|
62
|
+
|
|
63
|
+
start = tags['detector']['start_channel']
|
|
64
|
+
detector_efficiency = np.zeros(len(dataset))
|
|
65
|
+
detector_efficiency[start:] += get_detector_response(tags, energy_scale[start:])
|
|
66
|
+
tags['detector']['detector_efficiency'] = detector_efficiency
|
|
67
|
+
return detector_efficiency
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_detector_response(detector_definition, energy_scale):
|
|
71
|
+
"""
|
|
72
|
+
Calculates response of Si drift detector for EDS spectrum background based on detector parameters
|
|
73
|
+
|
|
74
|
+
Parameters:
|
|
75
|
+
----------
|
|
76
|
+
detector_definition: dictionary
|
|
77
|
+
definition of detector
|
|
78
|
+
energy_scale: numpy array (1 dim)
|
|
79
|
+
energy scale of spectrum should start at about 100eV!!
|
|
80
|
+
|
|
81
|
+
Return:
|
|
82
|
+
-------
|
|
83
|
+
response: numpy array with length(energy_scale)
|
|
84
|
+
detector response
|
|
85
|
+
|
|
86
|
+
Example
|
|
87
|
+
-------
|
|
88
|
+
|
|
89
|
+
tags ={}
|
|
90
|
+
tags['acceleration_voltage'] = 200000
|
|
91
|
+
|
|
92
|
+
tags['detector'] ={}
|
|
93
|
+
|
|
94
|
+
## layer thicknesses of commen materials in EDS detectors in m
|
|
95
|
+
tags['detector']['Al_thickness'] = 0.03 * 1e-6 # in m
|
|
96
|
+
tags['detector']['Be_thickness'] = 0. # in m
|
|
97
|
+
tags['detector']['Au_thickness'] = 0.0 * 1e-6 # in m
|
|
98
|
+
tags['detector']['Par_thickness'] = 0 *1e-6 # in m # Window
|
|
99
|
+
|
|
100
|
+
tags['detector']['SiDeadThickness'] = .03 *1e-6 # in m
|
|
101
|
+
|
|
102
|
+
tags['detector']['SiLiveThickness'] = 0.05 # in m
|
|
103
|
+
tags['detector']['detector_area'] = 30 * 1e-6 #in m2
|
|
104
|
+
tags['detector']['resolution'] = 125 # in eV
|
|
105
|
+
|
|
106
|
+
energy_scale = np.linspace(.01,20,1199)*1000 # i eV
|
|
107
|
+
start = np.searchsorted(spectrum.energy, 100)
|
|
108
|
+
energy_scale = spectrum.energy[start:]
|
|
109
|
+
detector_Efficiency= pyTEMlib.eds_tools.detector_response(tags, spectrum.energy[start:])
|
|
110
|
+
|
|
111
|
+
p = np.array([1, 37, .3])/10000*3
|
|
112
|
+
E_0= 200000
|
|
113
|
+
background = np.zeros(len(spectrum))
|
|
114
|
+
background[start:] = detector_Efficiency * (p[0] + p[1]*(E_0-energy_scale)/energy_scale + p[2]*(E_0-energy_scale)**2/energy_scale)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
plt.figure()
|
|
118
|
+
plt.plot(spectrum.energy, spectrum, label = 'spec')
|
|
119
|
+
plt.plot(spectrum.energy, background, label = 'background')
|
|
120
|
+
plt.show()
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
response = np.ones(len(energy_scale))
|
|
124
|
+
x_sections = pyTEMlib.eels_tools.get_x_sections()
|
|
125
|
+
|
|
126
|
+
def get_absorption(Z, t):
|
|
127
|
+
photoabsorption = x_sections[str(Z)]['dat']/1e10/x_sections[str(Z)]['photoabs_to_sigma']
|
|
128
|
+
lin = interp1d(x_sections[str(Z)]['ene'], photoabsorption, kind='linear')
|
|
129
|
+
mu = lin(energy_scale) * x_sections[str(Z)]['nominal_density']*100. #1/cm -> 1/m
|
|
130
|
+
return np.exp(-mu * t)
|
|
131
|
+
|
|
132
|
+
if 'Al_thickness' in detector_definition['detector']:
|
|
133
|
+
response *= get_absorption(13, detector_definition['detector']['Al_thickness'])
|
|
134
|
+
if 'Be_thickness' in detector_definition['detector']:
|
|
135
|
+
response *= get_absorption(5, detector_definition['detector']['Be_thickness'])
|
|
136
|
+
if 'Au_thickness' in detector_definition['detector']:
|
|
137
|
+
response *= get_absorption(79, detector_definition['detector']['Au_thickness'])
|
|
138
|
+
if 'Par_thickness' in detector_definition['detector']:
|
|
139
|
+
response *= get_absorption(6, detector_definition['detector']['Par_thickness'])
|
|
140
|
+
if 'SiDeadThickness' in detector_definition['detector']:
|
|
141
|
+
response *= get_absorption(14, detector_definition['detector']['SiDeadThickness'])
|
|
142
|
+
|
|
143
|
+
if 'SiLiveThickness' in detector_definition['detector']:
|
|
144
|
+
response *= 1-get_absorption(14, detector_definition['detector']['SiLiveThickness'])
|
|
145
|
+
return response
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def detect_peaks(dataset, minimum_number_of_peaks=30):
|
|
149
|
+
if not isinstance(dataset, sidpy.Dataset):
|
|
150
|
+
raise TypeError('Needs an sidpy dataset')
|
|
151
|
+
if not dataset.data_type.name == 'SPECTRUM':
|
|
152
|
+
raise TypeError('Need a spectrum')
|
|
153
|
+
|
|
154
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
|
|
155
|
+
if 'detector' not in dataset.metadata:
|
|
156
|
+
if 'energy_resolution' not in dataset.metadata['detector']:
|
|
157
|
+
dataset.metadata['detector']['energy_resolution'] = 138
|
|
158
|
+
print('Using energy resolution of 138 eV')
|
|
159
|
+
if 'start_channel' not in dataset.metadata['detector']:
|
|
160
|
+
dataset.metadata['detector']['start_channel'] = start = np.searchsorted(energy_scale, 100)
|
|
161
|
+
resolution = dataset.metadata['detector']['energy_resolution']
|
|
162
|
+
|
|
163
|
+
start = dataset.metadata['detector']['start_channel']
|
|
164
|
+
## we use half the width of the resolution for smearing
|
|
165
|
+
width = int(np.ceil(resolution/(energy_scale[1]-energy_scale[0])/2)+1)
|
|
166
|
+
new_spectrum = scipy.signal.savgol_filter(np.array(dataset)[start:], width, 2) ## we use half the width of the resolution for smearing
|
|
167
|
+
prominence = 10
|
|
168
|
+
minor_peaks, _ = scipy.signal.find_peaks(new_spectrum, prominence=prominence)
|
|
169
|
+
|
|
170
|
+
while len(minor_peaks) > minimum_number_of_peaks:
|
|
171
|
+
prominence+=10
|
|
172
|
+
minor_peaks, _ = scipy.signal.find_peaks(new_spectrum, prominence=prominence)
|
|
173
|
+
return np.array(minor_peaks)+start
|
|
174
|
+
|
|
175
|
+
def find_elements(spectrum, minor_peaks):
|
|
176
|
+
if not isinstance(spectrum, sidpy.Dataset):
|
|
177
|
+
raise TypeError(' Need a sidpy dataset')
|
|
178
|
+
energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
|
|
179
|
+
elements = []
|
|
180
|
+
for peak in minor_peaks:
|
|
181
|
+
found = False
|
|
182
|
+
for element in range(3,82):
|
|
183
|
+
if 'lines' in x_sections[str(element)]:
|
|
184
|
+
if 'K-L2' in x_sections[str(element)]['lines']:
|
|
185
|
+
if abs(x_sections[str(element)]['lines']['K-L2']['position']- energy_scale[peak]) <10:
|
|
186
|
+
found = True
|
|
187
|
+
if x_sections[str(element)]['name'] not in elements:
|
|
188
|
+
elements.append( x_sections[str(element)]['name'])
|
|
189
|
+
if not found:
|
|
190
|
+
if 'L3-M3' in x_sections[str(element)]['lines']:
|
|
191
|
+
if abs(x_sections[str(element)]['lines']['L3-M5']['position']- energy_scale[peak]) <30:
|
|
192
|
+
if x_sections[str(element)]['name'] not in elements:
|
|
193
|
+
elements.append( x_sections[str(element)]['name'])
|
|
194
|
+
return elements
|
|
195
|
+
|
|
196
|
+
def get_x_ray_lines(spectrum, elements):
|
|
197
|
+
out_tags = {}
|
|
198
|
+
alpha_K = 1e6
|
|
199
|
+
alpha_L = 6.5e7
|
|
200
|
+
alpha_M = 8*1e8 # 2.2e10
|
|
201
|
+
# My Fit
|
|
202
|
+
alpha_K = .9e6
|
|
203
|
+
alpha_L = 6.e7
|
|
204
|
+
alpha_M = 6*1e8 # 2.2e10
|
|
205
|
+
# omega_K = Z**4/(alpha_K+Z**4)
|
|
206
|
+
# omega_L = Z**4/(alpha_L+Z**4)
|
|
207
|
+
# omega_M = Z**4/(alpha_M+Z**4)
|
|
208
|
+
energy_scale = np.array(spectrum.get_spectral_dims(return_axis=True)[0].values)
|
|
209
|
+
for element in elements:
|
|
210
|
+
atomic_number = pyTEMlib.eds_tools.elements_list.index(element)
|
|
211
|
+
out_tags[element] ={'Z': atomic_number}
|
|
212
|
+
lines = x_sections[str(atomic_number)]['lines']
|
|
213
|
+
K_weight = 0
|
|
214
|
+
K_main = 'None'
|
|
215
|
+
K_lines = []
|
|
216
|
+
L_weight = 0
|
|
217
|
+
L_main = 'None'
|
|
218
|
+
L_lines = []
|
|
219
|
+
M_weight = 0
|
|
220
|
+
M_main = 'None'
|
|
221
|
+
M_lines = []
|
|
222
|
+
|
|
223
|
+
for key, line in lines.items():
|
|
224
|
+
if 'K' == key[0]:
|
|
225
|
+
if line['position'] < energy_scale[-1]:
|
|
226
|
+
K_lines.append(key)
|
|
227
|
+
if line['weight'] > K_weight:
|
|
228
|
+
K_weight = line['weight']
|
|
229
|
+
K_main = key
|
|
230
|
+
if 'L' == key[0]:
|
|
231
|
+
if line['position'] < energy_scale[-1]:
|
|
232
|
+
L_lines.append(key)
|
|
233
|
+
if line['weight'] > L_weight:
|
|
234
|
+
L_weight = line['weight']
|
|
235
|
+
L_main = key
|
|
236
|
+
if 'M' == key[0]:
|
|
237
|
+
if line['position'] < energy_scale[-1]:
|
|
238
|
+
M_lines.append(key)
|
|
239
|
+
if line['weight'] > M_weight:
|
|
240
|
+
M_weight = line['weight']
|
|
241
|
+
M_main = key
|
|
242
|
+
|
|
243
|
+
if K_weight > 0:
|
|
244
|
+
out_tags[element]['K-family'] = {'main': K_main, 'weight': K_weight, 'lines': K_lines}
|
|
245
|
+
height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines'][K_main]['position'] )].compute()
|
|
246
|
+
out_tags[element]['K-family']['height'] = height/K_weight
|
|
247
|
+
for key in K_lines:
|
|
248
|
+
out_tags[element]['K-family'][key] = x_sections[str(atomic_number)]['lines'][key]
|
|
249
|
+
if L_weight > 0:
|
|
250
|
+
out_tags[element]['L-family'] = {'main': L_main, 'weight': L_weight, 'lines': L_lines}
|
|
251
|
+
height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines'][L_main]['position'] )].compute()
|
|
252
|
+
out_tags[element]['L-family']['height'] = height/L_weight
|
|
253
|
+
for key in L_lines:
|
|
254
|
+
out_tags[element]['L-family'][key] = x_sections[str(atomic_number)]['lines'][key]
|
|
255
|
+
if M_weight > 0:
|
|
256
|
+
out_tags[element]['M-family'] = {'main': M_main, 'weight': M_weight, 'lines': M_lines}
|
|
257
|
+
height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines'][M_main]['position'] )].compute()
|
|
258
|
+
out_tags[element]['M-family']['height'] = height/M_weight
|
|
259
|
+
for key in M_lines:
|
|
260
|
+
out_tags[element]['M-family'][key] = x_sections[str(atomic_number)]['lines'][key]
|
|
261
|
+
|
|
262
|
+
xs = get_eds_cross_sections(atomic_number)
|
|
263
|
+
if 'K' in xs and 'K-family' in out_tags[element]:
|
|
264
|
+
out_tags[element]['K-family']['probability'] = xs['K']
|
|
265
|
+
if 'L' in xs and 'L-family' in out_tags[element]:
|
|
266
|
+
out_tags[element]['L-family']['probability'] = xs['L']
|
|
267
|
+
if 'M' in xs and 'M-family' in out_tags[element]:
|
|
268
|
+
out_tags[element]['M-family']['probability'] = xs['M']
|
|
269
|
+
|
|
270
|
+
if 'EDS' not in spectrum.metadata:
|
|
271
|
+
spectrum.metadata['EDS'] = {}
|
|
272
|
+
spectrum.metadata['EDS'].update(out_tags)
|
|
273
|
+
return out_tags
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def getFWHM(E, E_ref, FWHM_ref):
|
|
277
|
+
return np.sqrt(2.5*(E-E_ref)+FWHM_ref**2)
|
|
278
|
+
|
|
279
|
+
def gaussian(enrgy_scale, mu, FWHM):
|
|
280
|
+
sig = FWHM/2/np.sqrt(2*np.log(2))
|
|
281
|
+
return np.exp(-np.power(np.array(enrgy_scale) - mu, 2.) / (2 * np.power(sig, 2.)))
|
|
282
|
+
|
|
283
|
+
def get_peak(E, energy_scale):
|
|
284
|
+
E_ref = 5895.0
|
|
285
|
+
FWHM_ref = 136 #eV
|
|
286
|
+
FWHM = getFWHM(E, E_ref, FWHM_ref)
|
|
287
|
+
gaus = gaussian(energy_scale, E, FWHM)
|
|
288
|
+
|
|
289
|
+
return gaus /(gaus.sum()+1e-12)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def initial_model_parameter(spectrum):
|
|
293
|
+
tags = spectrum.metadata['EDS']
|
|
294
|
+
energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
|
|
295
|
+
p = []
|
|
296
|
+
peaks = []
|
|
297
|
+
keys = []
|
|
298
|
+
for element, lines in tags.items():
|
|
299
|
+
if 'K-family' in lines:
|
|
300
|
+
model = np.zeros(len(energy_scale))
|
|
301
|
+
for line, info in lines['K-family'].items():
|
|
302
|
+
if line[0] == 'K':
|
|
303
|
+
model += get_peak(info['position'], energy_scale)*info['weight']
|
|
304
|
+
lines['K-family']['peaks'] = model /model.sum() # *lines['K-family']['probability']
|
|
305
|
+
|
|
306
|
+
p.append(lines['K-family']['height'] / lines['K-family']['peaks'].max())
|
|
307
|
+
peaks.append(lines['K-family']['peaks'])
|
|
308
|
+
keys.append(element+':K-family')
|
|
309
|
+
if 'L-family' in lines:
|
|
310
|
+
model = np.zeros(len(energy_scale))
|
|
311
|
+
for line, info in lines['L-family'].items():
|
|
312
|
+
if line[0] == 'L':
|
|
313
|
+
model += get_peak(info['position'], energy_scale)*info['weight']
|
|
314
|
+
lines['L-family']['peaks'] = model /model.sum() # *lines['L-family']['probability']
|
|
315
|
+
p.append(lines['L-family']['height'] / lines['L-family']['peaks'].max())
|
|
316
|
+
peaks.append(lines['L-family']['peaks'])
|
|
317
|
+
keys.append(element+':L-family')
|
|
318
|
+
if 'M-family' in lines:
|
|
319
|
+
model = np.zeros(len(energy_scale))
|
|
320
|
+
for line, info in lines['M-family'].items():
|
|
321
|
+
if line[0] == 'M':
|
|
322
|
+
model += get_peak(info['position'], energy_scale)*info['weight']
|
|
323
|
+
lines['M-family']['peaks'] = model /model.sum()*lines['M-family']['probability']
|
|
324
|
+
p.append(lines['M-family']['height'] / lines['M-family']['peaks'].max())
|
|
325
|
+
peaks.append(lines['M-family']['peaks'])
|
|
326
|
+
keys.append(element+':M-family')
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
#p.extend([300, 10, 1.e-04])
|
|
330
|
+
# p.extend([1, 300, -.02])
|
|
331
|
+
p.extend([1e7, 1e-3, 1500, 20])
|
|
332
|
+
return np.array(peaks), np.array(p), keys
|
|
333
|
+
|
|
334
|
+
def get_model(spectrum, start=100):
|
|
335
|
+
model = np.zeros(len(spectrum))
|
|
336
|
+
for key in spectrum.metadata['EDS']:
|
|
337
|
+
for family in spectrum.metadata['EDS'][key]:
|
|
338
|
+
if isinstance(spectrum.metadata['EDS'][key][family], dict):
|
|
339
|
+
intensity = spectrum.metadata['EDS'][key][family]['areal_density']
|
|
340
|
+
peaks = spectrum.metadata['EDS'][key][family]['peaks']
|
|
341
|
+
model += peaks * intensity
|
|
342
|
+
|
|
343
|
+
if 'detector_efficiency' in spectrum.metadata['EDS']['detector'].keys():
|
|
344
|
+
detector_efficiency = spectrum.metadata['EDS']['detector']['detector_efficiency']
|
|
345
|
+
else:
|
|
346
|
+
detector_efficiency = None
|
|
347
|
+
E_0 = spectrum.metadata['experiment']['acceleration_voltage']
|
|
348
|
+
|
|
349
|
+
# if detector_efficiency is not None:
|
|
350
|
+
# model[start:] += detector_efficiency[start:] * (pp[-3] + pp[-2] * (E_0 - energy_scale) / energy_scale +
|
|
351
|
+
# pp[-1] * (E_0 - energy_scale) ** 2 / energy_scale)
|
|
352
|
+
|
|
353
|
+
return model
|
|
354
|
+
|
|
355
|
+
def fit_model(spectrum, elements, use_detector_efficiency=False):
|
|
356
|
+
out_tags = get_x_ray_lines(spectrum, elements)
|
|
357
|
+
peaks, pin, keys = initial_model_parameter(spectrum)
|
|
358
|
+
energy_scale = spectrum.get_spectral_dims(return_axis=True)[0].values
|
|
359
|
+
|
|
360
|
+
if 'detector' in spectrum.metadata['EDS'].keys():
|
|
361
|
+
if 'start_channel' not in spectrum.metadata['EDS']['detector']:
|
|
362
|
+
spectrum.metadata['EDS']['detector']['start_channel'] = np.searchsorted(energy_scale, 100)
|
|
363
|
+
if 'detector_efficiency' in spectrum.metadata['EDS']['detector'].keys():
|
|
364
|
+
if use_detector_efficiency:
|
|
365
|
+
detector_efficiency = spectrum.metadata['EDS']['detector']['detector_efficiency']
|
|
366
|
+
else:
|
|
367
|
+
use_detector_efficiency = False
|
|
368
|
+
else:
|
|
369
|
+
print('need detector information to fit spectrum')
|
|
370
|
+
return
|
|
371
|
+
start = spectrum.metadata['EDS']['detector']['start_channel']
|
|
372
|
+
energy_scale = energy_scale[start:]
|
|
373
|
+
|
|
374
|
+
E_0= spectrum.metadata['experiment']['acceleration_voltage']
|
|
375
|
+
|
|
376
|
+
def residuals(pp, yy):
|
|
377
|
+
#get_model(peaks, pp, detector_efficiency=None)
|
|
378
|
+
model = np.zeros(len(yy))
|
|
379
|
+
for i in range(len(pp)-4):
|
|
380
|
+
model += peaks[i]*pp[i]
|
|
381
|
+
# pp[-3:] = np.abs(pp[-3:])
|
|
382
|
+
|
|
383
|
+
if use_detector_efficiency:
|
|
384
|
+
bremsstrahlung = pp[-4] / (energy_scale + pp[-3] * energy_scale**2 + pp[-2] * energy_scale**.5) - pp[-1]
|
|
385
|
+
|
|
386
|
+
model[start:] += detector_efficiency[start:] * bremsstrahlung
|
|
387
|
+
#(pp[-3] + pp[-2] * (E_0 - energy_scale) / energy_scale +
|
|
388
|
+
# pp[-1] * (E_0-energy_scale) ** 2 / energy_scale))
|
|
389
|
+
|
|
390
|
+
err = np.abs((yy - model)[start:]) # /np.sqrt(np.abs(yy[start:])+1e-12)
|
|
391
|
+
|
|
392
|
+
return err
|
|
393
|
+
|
|
394
|
+
y = np.array(spectrum) # .compute()
|
|
395
|
+
[p, _] = leastsq(residuals, pin, args=(y))
|
|
396
|
+
|
|
397
|
+
# print(pin[-6:], p[-6:])
|
|
398
|
+
|
|
399
|
+
update_fit_values(out_tags, peaks, p)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if 'EDS' not in spectrum.metadata:
|
|
403
|
+
spectrum.metadata['EDS'] = {}
|
|
404
|
+
spectrum.metadata['EDS'].update(out_tags)
|
|
405
|
+
|
|
406
|
+
return np.array(peaks), np.array(p)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def update_fit_values(out_tags, peaks, p):
|
|
410
|
+
index = 0
|
|
411
|
+
for element, lines in out_tags.items():
|
|
412
|
+
if 'K-family' in lines:
|
|
413
|
+
lines['K-family']['areal_density'] = p[index]
|
|
414
|
+
lines['K-family']['peaks'] = peaks[index]
|
|
415
|
+
index += 1
|
|
416
|
+
if 'L-family' in lines:
|
|
417
|
+
lines['L-family']['areal_density'] = p[index]
|
|
418
|
+
lines['L-family']['peaks'] = peaks[index]
|
|
419
|
+
index += 1
|
|
420
|
+
if 'M-family' in lines:
|
|
421
|
+
lines['M-family']['areal_density'] =p[index]
|
|
422
|
+
lines['M-family']['peaks'] = peaks[index]
|
|
423
|
+
index += 1
|
|
424
|
+
|
|
425
|
+
def get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd):
|
|
426
|
+
background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [start_bgd, end_bgd], verbose=False)
|
|
427
|
+
cross_section_core = Xsection- background[0]
|
|
428
|
+
cross_section_core[cross_section_core < 0] = 0.0
|
|
429
|
+
cross_section_core[energy_scale < end_bgd] = 0.0
|
|
430
|
+
return cross_section_core
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def get_eds_cross_sections(z, acceleration_voltage=200000):
|
|
434
|
+
energy_scale = np.arange(1,20000)
|
|
435
|
+
Xsection = pyTEMlib.eels_tools.xsec_xrpa(energy_scale, acceleration_voltage/1000., z, 400.)
|
|
436
|
+
edge_info = pyTEMlib.eels_tools.get_x_sections(z)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
eds_cross_sections = {}
|
|
440
|
+
Xyield = edge_info['total_fluorescent_yield']
|
|
441
|
+
if 'K' in Xyield:
|
|
442
|
+
start_bgd = edge_info['K1']['onset'] * 0.8
|
|
443
|
+
end_bgd = edge_info['K1']['onset'] - 5
|
|
444
|
+
if start_bgd > end_bgd:
|
|
445
|
+
start_bgd = end_bgd-100
|
|
446
|
+
if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
|
|
447
|
+
eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
|
|
448
|
+
eds_xsection[eds_xsection<0] = 0.
|
|
449
|
+
start_sum = np.searchsorted(energy_scale, edge_info['K1']['onset'])
|
|
450
|
+
end_sum = start_sum+600
|
|
451
|
+
if end_sum> len(Xsection):
|
|
452
|
+
end_sum = len(Xsection)-1
|
|
453
|
+
eds_cross_sections['K1'] = eds_xsection[start_sum:end_sum].sum()
|
|
454
|
+
eds_cross_sections['K'] = eds_xsection[start_sum:end_sum].sum() * Xyield['K']
|
|
455
|
+
|
|
456
|
+
if 'L3' in Xyield:
|
|
457
|
+
start_bgd = edge_info['L3']['onset'] * 0.8
|
|
458
|
+
end_bgd = edge_info['L3']['onset'] - 5
|
|
459
|
+
if start_bgd > end_bgd:
|
|
460
|
+
start_bgd = end_bgd-100
|
|
461
|
+
if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
|
|
462
|
+
eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
|
|
463
|
+
eds_xsection[eds_xsection<0] = 0.
|
|
464
|
+
start_sum = np.searchsorted(energy_scale, edge_info['L3']['onset'])
|
|
465
|
+
end_sum = start_sum+600
|
|
466
|
+
if end_sum> len(Xsection):
|
|
467
|
+
end_sum = len(Xsection)-1
|
|
468
|
+
if end_sum >np.searchsorted(energy_scale, edge_info['K1']['onset'])-10:
|
|
469
|
+
end_sum = np.searchsorted(energy_scale, edge_info['K1']['onset'])-10
|
|
470
|
+
eds_cross_sections['L'] = eds_xsection[start_sum:end_sum].sum()
|
|
471
|
+
L1_channel = np.searchsorted(energy_scale, edge_info['L1']['onset'])
|
|
472
|
+
m_start = start_sum-100
|
|
473
|
+
if m_start < 2:
|
|
474
|
+
m_start = start_sum-20
|
|
475
|
+
l3_rise = np.max(Xsection[m_start: L1_channel-10])-np.min(Xsection[m_start: L1_channel-10])
|
|
476
|
+
l1_rise = np.max(Xsection[L1_channel-10: L1_channel+100])-np.min(Xsection[L1_channel-10: L1_channel+100])
|
|
477
|
+
l1_ratio = l1_rise/l3_rise
|
|
478
|
+
|
|
479
|
+
eds_cross_sections['L1'] = l1_ratio * eds_cross_sections['L']
|
|
480
|
+
eds_cross_sections['L2'] = eds_cross_sections['L']*(1-l1_ratio)*1/3
|
|
481
|
+
eds_cross_sections['L3'] = eds_cross_sections['L']*(1-l1_ratio)*2/3
|
|
482
|
+
eds_cross_sections['yield_L1'] = Xyield['L1']
|
|
483
|
+
eds_cross_sections['yield_L2'] = Xyield['L2']
|
|
484
|
+
eds_cross_sections['yield_L3'] = Xyield['L3']
|
|
485
|
+
|
|
486
|
+
eds_cross_sections['L'] = eds_cross_sections['L1']*Xyield['L1']+eds_cross_sections['L2']*Xyield['L2']+eds_cross_sections['L3']*Xyield['L3']
|
|
487
|
+
# eds_cross_sections['L'] /= 8
|
|
488
|
+
if 'M5' in Xyield:
|
|
489
|
+
start_bgd = edge_info['M5']['onset'] * 0.8
|
|
490
|
+
end_bgd = edge_info['M5']['onset'] - 5
|
|
491
|
+
if start_bgd > end_bgd:
|
|
492
|
+
start_bgd = end_bgd-100
|
|
493
|
+
if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
|
|
494
|
+
eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
|
|
495
|
+
eds_xsection[eds_xsection<0] = 0.
|
|
496
|
+
start_sum = np.searchsorted(energy_scale, edge_info['M5']['onset'])
|
|
497
|
+
end_sum = start_sum+600
|
|
498
|
+
if end_sum > np.searchsorted(energy_scale, edge_info['L3']['onset'])-10:
|
|
499
|
+
end_sum = np.searchsorted(energy_scale, edge_info['L3']['onset'])-10
|
|
500
|
+
eds_cross_sections['M'] = eds_xsection[start_sum:end_sum].sum()
|
|
501
|
+
#print(edge_info['M5']['onset'] - edge_info['M1']['onset'])
|
|
502
|
+
M3_channel = np.searchsorted(energy_scale, edge_info['M3']['onset'])
|
|
503
|
+
M1_channel = np.searchsorted(energy_scale, edge_info['M1']['onset'])
|
|
504
|
+
m5_rise = np.max(Xsection[start_sum-100: M3_channel-10])-np.min(Xsection[start_sum-100: M3_channel-10])
|
|
505
|
+
m3_rise = np.max(Xsection[M3_channel-10: M1_channel-10])-np.min(Xsection[M3_channel-10: M1_channel-10])
|
|
506
|
+
m1_rise = np.max(Xsection[M1_channel-10: M1_channel+100])-np.min(Xsection[M1_channel-10: M1_channel+100])
|
|
507
|
+
m1_ratio = m1_rise/m5_rise
|
|
508
|
+
m3_ratio = m3_rise/m5_rise
|
|
509
|
+
m5_ratio = 1-(m1_ratio+m3_ratio)
|
|
510
|
+
#print(m1_ratio, m3_ratio, 1-(m1_ratio+m3_ratio))
|
|
511
|
+
eds_cross_sections['M1'] = m1_ratio * eds_cross_sections['M']
|
|
512
|
+
eds_cross_sections['M2'] = m3_ratio * eds_cross_sections['M']*1/3
|
|
513
|
+
eds_cross_sections['M3'] = m3_ratio * eds_cross_sections['M']*2/3
|
|
514
|
+
eds_cross_sections['M4'] = m5_ratio * eds_cross_sections['M']*2/5
|
|
515
|
+
eds_cross_sections['M5'] = m5_ratio * eds_cross_sections['M']*3/5
|
|
516
|
+
eds_cross_sections['yield_M1'] = Xyield['M1']
|
|
517
|
+
eds_cross_sections['yield_M2'] = Xyield['M2']
|
|
518
|
+
eds_cross_sections['yield_M3'] = Xyield['M3']
|
|
519
|
+
eds_cross_sections['yield_M4'] = Xyield['M4']
|
|
520
|
+
eds_cross_sections['yield_M5'] = Xyield['M5']
|
|
521
|
+
eds_cross_sections['M'] = eds_cross_sections['M1']*Xyield['M1']+eds_cross_sections['M2']*Xyield['M2']+eds_cross_sections['M3']*Xyield['M3'] \
|
|
522
|
+
+eds_cross_sections['M4']*Xyield['M4']+eds_cross_sections['M5']*Xyield['M5']
|
|
523
|
+
#eds_cross_sections['M'] /= 18
|
|
524
|
+
return eds_cross_sections
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def get_phases(dataset, mode='kmeans', number_of_phases=4):
|
|
528
|
+
X_vec = np.array(dataset).reshape(dataset.shape[0]*dataset.shape[1], dataset.shape[2])
|
|
529
|
+
X_vec = np.divide(X_vec.T, X_vec.sum(axis=1)).T
|
|
530
|
+
if mode != 'kmeans':
|
|
531
|
+
gmm = GaussianMixture(n_components=number_of_phases, covariance_type="full") #choose number of components
|
|
532
|
+
|
|
533
|
+
gmm_results = gmm.fit(np.array(X_vec)) #we can intelligently fold the data and perform GM
|
|
534
|
+
gmm_labels = gmm_results.fit_predict(X_vec)
|
|
535
|
+
|
|
536
|
+
dataset.metadata['gaussian_mixing_model'] = {'map': gmm_labels.reshape(dataset.shape[0], dataset.shape[1]),
|
|
537
|
+
'covariances': gmm.covariances_,
|
|
538
|
+
'weights': gmm.weights_,
|
|
539
|
+
'means': gmm_results.means_}
|
|
540
|
+
else:
|
|
541
|
+
km = KMeans(number_of_phases, n_init =10) #choose number of clusters
|
|
542
|
+
km_results = km.fit(np.array(X_vec)) #we can intelligently fold the data and perform Kmeans
|
|
543
|
+
dataset.metadata['kmeans'] = {'map': km_results.labels_.reshape(dataset.shape[0], dataset.shape[1]),
|
|
544
|
+
'means': km_results.cluster_centers_}
|
|
545
|
+
|
|
546
|
+
def plot_phases(dataset, image=None, survey_image=None):
|
|
547
|
+
if survey_image is not None:
|
|
548
|
+
ncols = 3
|
|
549
|
+
else:
|
|
550
|
+
ncols = 2
|
|
551
|
+
axis_index = 0
|
|
552
|
+
fig, axes = plt.subplots(nrows=1, ncols=ncols, figsize = (10,3))
|
|
553
|
+
if survey_image is not None:
|
|
554
|
+
im = axes[0].imshow(survey_image.T)
|
|
555
|
+
axis_index += 1
|
|
556
|
+
#if 'gaussian_mixing_model' in dataset.metadata:
|
|
557
|
+
# phase_spectra = dataset.metadata['gaussian_mixing_model']['means']
|
|
558
|
+
# map = dataset.metadata['gaussian_mixing_model']['map']
|
|
559
|
+
#el
|
|
560
|
+
if 'kmeans' in dataset.metadata:
|
|
561
|
+
phase_spectra = dataset.metadata['kmeans']['means']
|
|
562
|
+
map = dataset.metadata['kmeans']['map']
|
|
563
|
+
|
|
564
|
+
cmap = plt.get_cmap('jet', len(phase_spectra))
|
|
565
|
+
im = axes[axis_index].imshow(image.T,cmap='gray')
|
|
566
|
+
im = axes[axis_index].imshow(map.T, cmap=cmap,vmin=np.min(map) - 0.5,
|
|
567
|
+
vmax=np.max(map) + 0.5,alpha=0.2)
|
|
568
|
+
|
|
569
|
+
cbar = fig.colorbar(im, ax=axes[axis_index])
|
|
570
|
+
cbar.ax.set_yticks(np.arange(0, len(phase_spectra) ))
|
|
571
|
+
cbar.ax.set_ylabel("GMM Phase", fontsize = 14)
|
|
572
|
+
axis_index += 1
|
|
573
|
+
for index, spectrum in enumerate(phase_spectra):
|
|
574
|
+
axes[axis_index].plot(dataset.energy/1000, spectrum, color = cmap(index), label=str(index))
|
|
575
|
+
axes[axis_index].set_xlabel('energy (keV)')
|
|
576
|
+
plt.legend()
|
|
577
|
+
plt.tight_layout()
|
|
578
|
+
plt.show()
|
|
579
|
+
return fig
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def plot_lines(eds_quantification: dict, axis: plt.Axes):
|
|
583
|
+
for key, lines in eds_quantification.items():
|
|
584
|
+
if 'K-family' in lines:
|
|
585
|
+
intensity = lines['K-family']['height']
|
|
586
|
+
for line in lines['K-family']:
|
|
587
|
+
if line[0] == 'K':
|
|
588
|
+
pos = lines['K-family'][line]['position']
|
|
589
|
+
axis.plot([pos,pos], [0, intensity*lines['K-family'][line]['weight']], color='blue')
|
|
590
|
+
if line == lines['K-family']['main']:
|
|
591
|
+
axis.text(pos,0, key+'\n'+line, verticalalignment='top')
|
|
592
|
+
|
|
593
|
+
if 'L-family' in lines:
|
|
594
|
+
intensity = lines['L-family']['height']
|
|
595
|
+
for line in lines['L-family']:
|
|
596
|
+
if line[0] == 'L':
|
|
597
|
+
pos = lines['L-family'][line]['position']
|
|
598
|
+
axis.plot([pos,pos], [0, intensity*lines['L-family'][line]['weight']], color='black')
|
|
599
|
+
if line in [lines['L-family']['main'], 'L3-M5', 'L3-N5', 'L1-M3']:
|
|
600
|
+
axis.text(pos,0, key+'\n'+line, verticalalignment='top')
|
|
601
|
+
|
|
602
|
+
if 'M-family' in lines:
|
|
603
|
+
intensity = lines['M-family']['height']
|
|
604
|
+
for line in lines['M-family']:
|
|
605
|
+
if line[0] == 'M':
|
|
606
|
+
pos = lines['M-family'][line]['position']
|
|
607
|
+
axis.plot([pos,pos], [0, intensity*lines['M-family'][line]['weight']], color='green')
|
|
608
|
+
if line in [lines['M-family']['main'], 'M5-N7', 'M4-N6']:
|
|
609
|
+
axis.text(pos,0, key+'\n'+line, verticalalignment='top')
|