pyTEMlib 0.2025.4.2__py3-none-any.whl → 0.2025.9.1__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.
- build/lib/pyTEMlib/__init__.py +33 -0
- build/lib/pyTEMlib/animation.py +640 -0
- build/lib/pyTEMlib/atom_tools.py +238 -0
- build/lib/pyTEMlib/config_dir.py +31 -0
- build/lib/pyTEMlib/crystal_tools.py +1219 -0
- build/lib/pyTEMlib/diffraction_plot.py +756 -0
- build/lib/pyTEMlib/dynamic_scattering.py +293 -0
- build/lib/pyTEMlib/eds_tools.py +826 -0
- build/lib/pyTEMlib/eds_xsections.py +432 -0
- build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
- build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
- build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- build/lib/pyTEMlib/file_reader.py +274 -0
- build/lib/pyTEMlib/file_tools.py +811 -0
- build/lib/pyTEMlib/get_bote_salvat.py +69 -0
- build/lib/pyTEMlib/graph_tools.py +1153 -0
- build/lib/pyTEMlib/graph_viz.py +599 -0
- build/lib/pyTEMlib/image/__init__.py +37 -0
- build/lib/pyTEMlib/image/image_atoms.py +270 -0
- build/lib/pyTEMlib/image/image_clean.py +197 -0
- build/lib/pyTEMlib/image/image_distortion.py +299 -0
- build/lib/pyTEMlib/image/image_fft.py +277 -0
- build/lib/pyTEMlib/image/image_graph.py +926 -0
- build/lib/pyTEMlib/image/image_registration.py +316 -0
- build/lib/pyTEMlib/image/image_utilities.py +309 -0
- build/lib/pyTEMlib/image/image_window.py +421 -0
- build/lib/pyTEMlib/image_tools.py +699 -0
- build/lib/pyTEMlib/interactive_image.py +1 -0
- build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
- build/lib/pyTEMlib/microscope.py +61 -0
- build/lib/pyTEMlib/probe_tools.py +906 -0
- build/lib/pyTEMlib/sidpy_tools.py +153 -0
- build/lib/pyTEMlib/simulation_tools.py +104 -0
- build/lib/pyTEMlib/test.py +437 -0
- build/lib/pyTEMlib/utilities.py +314 -0
- build/lib/pyTEMlib/version.py +5 -0
- build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
- pyTEMlib/__init__.py +25 -3
- pyTEMlib/animation.py +31 -22
- pyTEMlib/atom_tools.py +29 -34
- pyTEMlib/config_dir.py +2 -28
- pyTEMlib/crystal_tools.py +129 -165
- pyTEMlib/eds_tools.py +559 -342
- pyTEMlib/eds_xsections.py +432 -0
- pyTEMlib/eels_tools/__init__.py +44 -0
- pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- pyTEMlib/eels_tools/eels_database.py +134 -0
- pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- pyTEMlib/file_reader.py +274 -0
- pyTEMlib/file_tools.py +260 -1130
- pyTEMlib/get_bote_salvat.py +69 -0
- pyTEMlib/graph_tools.py +101 -174
- pyTEMlib/graph_viz.py +150 -0
- pyTEMlib/image/__init__.py +37 -0
- pyTEMlib/image/image_atoms.py +270 -0
- pyTEMlib/image/image_clean.py +197 -0
- pyTEMlib/image/image_distortion.py +299 -0
- pyTEMlib/image/image_fft.py +277 -0
- pyTEMlib/image/image_graph.py +926 -0
- pyTEMlib/image/image_registration.py +316 -0
- pyTEMlib/image/image_utilities.py +309 -0
- pyTEMlib/image/image_window.py +421 -0
- pyTEMlib/image_tools.py +154 -928
- pyTEMlib/kinematic_scattering.py +1 -1
- pyTEMlib/probe_tools.py +1 -1
- pyTEMlib/test.py +437 -0
- pyTEMlib/utilities.py +314 -0
- pyTEMlib/version.py +2 -3
- pyTEMlib/xrpa_x_sections.py +14 -10
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
- pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
- pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
- pyTEMlib/core_loss_widget.py +0 -721
- pyTEMlib/eels_dialog.py +0 -754
- pyTEMlib/eels_dialog_utilities.py +0 -1199
- pyTEMlib/eels_tools.py +0 -2359
- pyTEMlib/file_tools_qt.py +0 -193
- pyTEMlib/image_dialog.py +0 -158
- pyTEMlib/image_dlg.py +0 -146
- pyTEMlib/info_widget.py +0 -1086
- pyTEMlib/info_widget3.py +0 -1120
- pyTEMlib/low_loss_widget.py +0 -479
- pyTEMlib/peak_dialog.py +0 -1129
- pyTEMlib/peak_dlg.py +0 -286
- pytemlib-0.2025.4.2.dist-info/RECORD +0 -38
- pytemlib-0.2025.4.2.dist-info/top_level.txt +0 -1
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for calculating electron ionization cross sections for EDS quantification.
|
|
3
|
+
Based on the Bote & Salvat (2008, 2009) cross sections for inner shell ionization
|
|
4
|
+
by electron impact.
|
|
5
|
+
Also includes cascade corrections for Coster-Kronig, Auger, and fluorescence
|
|
6
|
+
emission.
|
|
7
|
+
Values from xraylib package are used for fluorescence yields, radiative rates,
|
|
8
|
+
Coster-Kronig probabilities, and Auger yields/rates.
|
|
9
|
+
References:
|
|
10
|
+
- D. Bote and F. Salvat, "Calculations of inner-shell ionization by electron
|
|
11
|
+
impact with the distorted-wave and plane-wave Born approximations","
|
|
12
|
+
- Bote, David, et al. "Cross sections for ionization of K, L and M shells of
|
|
13
|
+
atoms by impact of electrons and positrons with energies up to 1 GeV:
|
|
14
|
+
Analytical formulas."
|
|
15
|
+
Atomic Data and Nuclear Data Tables 95.6 (2009): 871-909.
|
|
16
|
+
- Browning, N. D., et al. "A model for calculating cross sections for electron
|
|
17
|
+
impact ionization of atoms from threshold to 10 MeV."
|
|
18
|
+
Journal of Applied Physics 83.11 (1998): 5736-5744.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import numpy as np
|
|
25
|
+
import scipy
|
|
26
|
+
|
|
27
|
+
import xraylib as xr
|
|
28
|
+
|
|
29
|
+
from .config_dir import config_path
|
|
30
|
+
from .utilities import elements as elements_list
|
|
31
|
+
from .utilities import get_z
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_atomic_number(z):
|
|
35
|
+
"""Returns the atomic number independent of input as a string or number"""
|
|
36
|
+
return get_z(z)
|
|
37
|
+
|
|
38
|
+
line_list = []
|
|
39
|
+
for _key in xr.__dict__:
|
|
40
|
+
if '_LINE' in _key:
|
|
41
|
+
line_list.append(_key.split('_')[0])
|
|
42
|
+
|
|
43
|
+
shell_list = []
|
|
44
|
+
for _key in xr.__dict__:
|
|
45
|
+
if '_SHELL' in _key:
|
|
46
|
+
shell_list.append(_key.split('_')[0])
|
|
47
|
+
|
|
48
|
+
auger_list = []
|
|
49
|
+
for _key in xr.__dict__:
|
|
50
|
+
if '_AUGER' in _key:
|
|
51
|
+
auger_list.append(_key.split('_')[:-1])
|
|
52
|
+
|
|
53
|
+
def get_bote_salvat_dict(z=0):
|
|
54
|
+
"""Load the Bote & Salvat cross section data from JSON file."""
|
|
55
|
+
filename = os.path.join(config_path, 'Bote_Salvat.json')
|
|
56
|
+
x_sections = json.load(open(filename, 'r', encoding='utf-8'))
|
|
57
|
+
if z > 0:
|
|
58
|
+
return x_sections[str(z)]
|
|
59
|
+
return x_sections
|
|
60
|
+
|
|
61
|
+
def bote_salvat_xsection(x_section, subshell, acceleration_energy):
|
|
62
|
+
"""
|
|
63
|
+
Using Tabulated Values for electrons of:
|
|
64
|
+
- D. Bote and F. Salvat, "Calculations of inner-shell ionization by electron
|
|
65
|
+
impact with the distorted-wave and plane-wave Born approximations",
|
|
66
|
+
Phys. rest_energy. A77, 042701 (2008).
|
|
67
|
+
|
|
68
|
+
- Bote, David, et al. "Cross sections for ionization of K, L and M shells of
|
|
69
|
+
atoms by impact of electrons and positrons with energies up to 1 GeV:
|
|
70
|
+
Analytical formulas."
|
|
71
|
+
Atomic Data and Nuclear Data Tables 95.6 (2009): 871-909.
|
|
72
|
+
|
|
73
|
+
Computes the inner sub-shell ionization cross section for energetic electrons.
|
|
74
|
+
`subshell` is 1->K, 2->L₁, 3->L₂, ..., 9->M₅
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
z : int
|
|
78
|
+
The atomic number z in the range 1:99
|
|
79
|
+
subshell : int
|
|
80
|
+
The atomic sub-shell being ionized 1->K, 2->L₁, 3->L₂, ..., 9->M₅
|
|
81
|
+
acceleration_energy : float
|
|
82
|
+
The kinetic energy of the incident electron in eV
|
|
83
|
+
edge_energy : float
|
|
84
|
+
The edge energy of the sub-shell in eV
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
float
|
|
89
|
+
The ionization cross section in barns
|
|
90
|
+
"""
|
|
91
|
+
edge_energy = x_section['edge'][subshell-1]
|
|
92
|
+
over_voltage = acceleration_energy / edge_energy
|
|
93
|
+
if over_voltage < 1.0:
|
|
94
|
+
return
|
|
95
|
+
if over_voltage <= 16:
|
|
96
|
+
a = np.array(x_section['A'])[subshell, :]
|
|
97
|
+
opu = 1.0 / (1.0 + over_voltage)
|
|
98
|
+
ffitlo = a[0] + a[1] * over_voltage + opu*(a[2] + opu**2*(a[3] + opu**2*a[4]))
|
|
99
|
+
x_ion_e = (over_voltage - 1.0) * (ffitlo / over_voltage)**2
|
|
100
|
+
else:
|
|
101
|
+
rest_ene = 5.10998918e5 # electron rest energy in eV
|
|
102
|
+
e_0 = acceleration_energy
|
|
103
|
+
beta2 = (e_0 * (e_0 + 2.0 * rest_ene)) / ((e_0 + rest_ene)**2)
|
|
104
|
+
x = np.sqrt(e_0 * (e_0 + 2.0 * rest_ene)) / rest_ene
|
|
105
|
+
g = x_section['G'][subshell-1]
|
|
106
|
+
ffitup = ((((2.0 * np.log(x)) - beta2) * (1.0 + g[0] / x)) + g[1] + g[2]
|
|
107
|
+
* np.sqrt(rest_ene / (e_0 + rest_ene)) + g[3] / x)
|
|
108
|
+
factr = x_section['Anlj'][subshell-1] / beta2
|
|
109
|
+
x_ion_e = ((factr * over_voltage) / (over_voltage + x_section['Be'][subshell-1])) * ffitup
|
|
110
|
+
return 4.0 * np.pi * 5.291772108e-9**2 * x_ion_e # in barns
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def casnati_x_section(occupancy, edge_energy, beam_energy):
|
|
114
|
+
"""(Casnati's equation) was found to fit cross-section data to
|
|
115
|
+
typically better than +-10% over the range 1<=Uk<=20 and 6<=Z<=79."
|
|
116
|
+
Note: This result is for K shell. L & M are much less wellcharacterized.
|
|
117
|
+
C. Powell indicated in conversation with Richie that he believed that Casnati's
|
|
118
|
+
expression was the best available for L & M also.
|
|
119
|
+
"""
|
|
120
|
+
rest_energy = 5.10998918e5 # electron rest energy in eV
|
|
121
|
+
res = 0.0
|
|
122
|
+
ee = edge_energy
|
|
123
|
+
u = beam_energy / ee
|
|
124
|
+
if u > 1.0:
|
|
125
|
+
phi = 10.57 * np.exp((-1.736 / u) + (0.317 / u**2))
|
|
126
|
+
psi = np.power(ee / scipy.constants.R, -0.0318 + (0.3160 / u) + (-0.1135 / u**2))
|
|
127
|
+
i = ee / rest_energy
|
|
128
|
+
t = beam_energy / rest_energy
|
|
129
|
+
f = ((2.0 + i) / (2.0 + t)) * np.square((1.0 + t) / (1.0 + i))
|
|
130
|
+
f *= np.power(((i + t) * (2.0 + t) * np.square(1.0 + i))
|
|
131
|
+
/ ((t * (2.0 + t) * np.square(1.0 + i))
|
|
132
|
+
+ (i * (2.0 + i))), 1.5)
|
|
133
|
+
res = ((occupancy * np.sqrt((scipy.constants.value('Bohr radius')
|
|
134
|
+
* scipy.constants.Rydberg) / ee)
|
|
135
|
+
* f * psi * phi * np.log(u)) / u)
|
|
136
|
+
return res
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def browning_x_section(z, acceleration_energy):
|
|
140
|
+
"""
|
|
141
|
+
Browning, N. D., et al. "A model for calculating cross sections for electron
|
|
142
|
+
impact ionization of atoms from threshold to 10 MeV."
|
|
143
|
+
Journal of Applied Physics 83.11 (1998): 5736-5744.
|
|
144
|
+
|
|
145
|
+
Computes the total ionization cross section for energetic electrons.
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
z : int
|
|
149
|
+
The atomic number z in the range 1:99
|
|
150
|
+
acceleration_energy : float
|
|
151
|
+
The kinetic energy of the incident electron in eV
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
float
|
|
156
|
+
The ionization cross section in square meters
|
|
157
|
+
"""
|
|
158
|
+
e = acceleration_energy
|
|
159
|
+
x_section = (3.0e-22 * z**1.7) / (e + (0.005 * z**1.7 * np.sqrt(e))
|
|
160
|
+
+ ((0.0007 * z**2) / np.sqrt(e)))
|
|
161
|
+
return x_section
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def get_transition_dict(shell):
|
|
165
|
+
"""Get the transition dictionary for a given shell."""
|
|
166
|
+
transition_dict = {}
|
|
167
|
+
for index, line in enumerate(line_list):
|
|
168
|
+
if line[:-2] == shell:
|
|
169
|
+
# lines have negative !!!! indices in xraylib
|
|
170
|
+
transition_dict[line] = -index - 1
|
|
171
|
+
return transition_dict
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def radiation_ionization(element, shell):
|
|
175
|
+
"""Radiative transitions from higher shells to the given shell"""
|
|
176
|
+
end_family = shell[0]
|
|
177
|
+
ionization = 0.0
|
|
178
|
+
for index, line in enumerate(shell_list):
|
|
179
|
+
start_family = line[0]
|
|
180
|
+
if end_family > start_family:
|
|
181
|
+
tr_dict = get_transition_dict(line)
|
|
182
|
+
try:
|
|
183
|
+
fy = xr.FluorYield(element, index)
|
|
184
|
+
rr = xr.RadRate(element, tr_dict[line + shell])
|
|
185
|
+
ionization += fy * rr
|
|
186
|
+
except ValueError:
|
|
187
|
+
pass
|
|
188
|
+
return ionization
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_all_subshells(line_family):
|
|
192
|
+
"""Get all subshells for a given line family."""
|
|
193
|
+
subshells = []
|
|
194
|
+
for subshell in shell_list:
|
|
195
|
+
if line_family[0] == subshell[0]:
|
|
196
|
+
subshells.append(subshell)
|
|
197
|
+
return subshells
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def full_auger_ionization (element, shell) :
|
|
201
|
+
""" Auger transitions from lower shells to the given shell"""
|
|
202
|
+
ionization = 0
|
|
203
|
+
for i in range(shell_list.index(shell)) :
|
|
204
|
+
ionization += auger_ionization(element, shell, shell_list[i])
|
|
205
|
+
return ionization
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_auger_dict(shell, lower_shell) :
|
|
209
|
+
"""Get the Auger transition dictionary for a given shell and lower shell."""
|
|
210
|
+
auger_dict = {}
|
|
211
|
+
for key, item in xr.__dict__.items():
|
|
212
|
+
if '_AUGER' in key:
|
|
213
|
+
if key[:2] == lower_shell and key[3:5] == shell:
|
|
214
|
+
auger_dict[key[:7]] = item
|
|
215
|
+
return auger_dict
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_trans_dictionary(shell):
|
|
219
|
+
"""Get the transition dictionary for a given shell."""
|
|
220
|
+
trans = {}
|
|
221
|
+
shell_group = shell[0]
|
|
222
|
+
if shell[0] == 'K':
|
|
223
|
+
return trans
|
|
224
|
+
for i in range(1, int(shell[1])):
|
|
225
|
+
trans[f"{shell_group}{i}{shell[1]}"] = xr.__dict__[f"F{shell_group}{i}{shell[1]}_TRANS"]
|
|
226
|
+
return trans
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def coster_kronic_ionization (tags, shell) :
|
|
230
|
+
""" Coster Kronig transitions
|
|
231
|
+
For the L1, there is not coster kronig
|
|
232
|
+
transitions from higher to lower shells are added
|
|
233
|
+
"""
|
|
234
|
+
element = int(tags['atomic_number'])
|
|
235
|
+
|
|
236
|
+
ionization = 0
|
|
237
|
+
if shell[0] == 'K':
|
|
238
|
+
return ionization
|
|
239
|
+
for trans, item in get_trans_dictionary(shell).items():
|
|
240
|
+
try:
|
|
241
|
+
ionization += tags[trans[:2]] * xr.CosKronTransProb(element, item)
|
|
242
|
+
except ValueError:
|
|
243
|
+
pass
|
|
244
|
+
return ionization
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def auger_ionization (element, shell, lower_shell):
|
|
248
|
+
""" Auger transitions from lower shells to the given shell"""
|
|
249
|
+
if shell == "K":
|
|
250
|
+
return 0
|
|
251
|
+
else:
|
|
252
|
+
auger_dict = get_auger_dict(shell, lower_shell)
|
|
253
|
+
try:
|
|
254
|
+
auger_yield = xr.AugerYield(element, shell_list.index(lower_shell))
|
|
255
|
+
auger_rate = 0
|
|
256
|
+
for trans in auger_dict.values():
|
|
257
|
+
try:
|
|
258
|
+
auger_rate += xr.AugerRate(element, trans)
|
|
259
|
+
except ValueError:
|
|
260
|
+
pass
|
|
261
|
+
return auger_rate * auger_yield
|
|
262
|
+
except ValueError:
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def radiation_emission(element, shell):
|
|
267
|
+
"""Radiative transitions from the given shell to higher shells
|
|
268
|
+
Does not work"""
|
|
269
|
+
|
|
270
|
+
start_family = shell[0]
|
|
271
|
+
if start_family > 'M':
|
|
272
|
+
return 0.0
|
|
273
|
+
tr_dict = get_transition_dict(shell)
|
|
274
|
+
radiation_propability = 0.0
|
|
275
|
+
for index, line in enumerate(shell_list):
|
|
276
|
+
end_family = line[0]
|
|
277
|
+
if end_family > start_family and end_family < 'N':
|
|
278
|
+
try:
|
|
279
|
+
fluorescent_yield = xr.FluorYield(element, index)
|
|
280
|
+
rate = xr.RadRate(element, tr_dict[shell + line])
|
|
281
|
+
radiation_propability += fluorescent_yield * rate
|
|
282
|
+
except ValueError:
|
|
283
|
+
pass
|
|
284
|
+
return radiation_propability
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def family_ionization(tags, line_family, acceleration_energy=200000):
|
|
288
|
+
"""Calculate ionization cross sections for all subshells in a line family."""
|
|
289
|
+
element_z = tags['atomic_number']
|
|
290
|
+
x_sections = get_bote_salvat_dict(z=element_z)
|
|
291
|
+
# direct
|
|
292
|
+
for subshell in get_all_subshells(line_family):
|
|
293
|
+
index = shell_list.index(subshell)
|
|
294
|
+
tags[subshell] = bote_salvat_xsection(x_sections, index, acceleration_energy)
|
|
295
|
+
# cascade corrections
|
|
296
|
+
for subshell in get_all_subshells(line_family):
|
|
297
|
+
index = shell_list.index(subshell)
|
|
298
|
+
coster_kronig = coster_kronic_ionization (tags, subshell)
|
|
299
|
+
cascade_ionization = 1 + radiation_ionization(element_z, subshell)
|
|
300
|
+
cascade_ionization += full_auger_ionization(element_z, subshell)
|
|
301
|
+
tags[subshell] *= cascade_ionization
|
|
302
|
+
tags[subshell] += coster_kronig
|
|
303
|
+
return tags
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_families(spectrum):
|
|
307
|
+
"""Get the line families for all elements in the spectrum."""
|
|
308
|
+
spectrum.metadata['EDS'].setdefault('GUI', {})
|
|
309
|
+
for key in spectrum.metadata['EDS']:
|
|
310
|
+
if key in ['detector', 'quantification']:
|
|
311
|
+
pass
|
|
312
|
+
elif isinstance(spectrum.metadata['EDS'][key], dict) and key in elements_list:
|
|
313
|
+
family = spectrum.metadata['EDS'][key].get('GUI', {}).get('symmetry', None)
|
|
314
|
+
if family is None:
|
|
315
|
+
if 'K-family' in spectrum.metadata['EDS'][key]:
|
|
316
|
+
family = 'K-family'
|
|
317
|
+
elif 'L-family' in spectrum.metadata['EDS'][key]:
|
|
318
|
+
family = 'L-family'
|
|
319
|
+
elif 'M-family' in spectrum.metadata['EDS'][key]:
|
|
320
|
+
family = 'M-family'
|
|
321
|
+
spectrum.metadata['EDS']['GUI'][key] = {'symmetry': family}
|
|
322
|
+
return spectrum.metadata['EDS']['GUI']
|
|
323
|
+
|
|
324
|
+
def quantify_cross_section(spectrum, x_section=None, mask=None):
|
|
325
|
+
"""Calculate quantification for EDS spectrum with cross sections."""
|
|
326
|
+
spectrum.metadata['EDS'].setdefault('GUI', {})
|
|
327
|
+
acceleration_voltage = spectrum.metadata.get('experiment', {}).get('acceleration_voltage',
|
|
328
|
+
200000)
|
|
329
|
+
families = get_families(spectrum)
|
|
330
|
+
total = 0
|
|
331
|
+
total_amu = 0
|
|
332
|
+
for key, family in families.items():
|
|
333
|
+
if key in mask:
|
|
334
|
+
continue
|
|
335
|
+
amu = spectrum.metadata['EDS'][key]['atomic_weight']
|
|
336
|
+
intensity = spectrum.metadata['EDS'][key][family['symmetry']].get('areal_density', 0)
|
|
337
|
+
z = get_atomic_number(key)
|
|
338
|
+
peaks = spectrum.metadata['EDS'][key][family['symmetry']].get('peaks', np.array([1]))
|
|
339
|
+
f_yield = spectrum.metadata['EDS'][key][family['symmetry']].get('fluorescent_yield',0)
|
|
340
|
+
tags = {'atomic_number':z}
|
|
341
|
+
tags = family_ionization(tags, family['symmetry'], acceleration_voltage)
|
|
342
|
+
x_sect = tags['K']*1e14 / f_yield / peaks.max()
|
|
343
|
+
# print(x_sect)
|
|
344
|
+
# x_sect = get_cross_section(x_section[str(z)], family['symmetry'],
|
|
345
|
+
# acceleration_voltage)*1e14/ f_yield/peaks.max()
|
|
346
|
+
spectrum.metadata['EDS']['GUI'][key]['cross_section'] = x_sect
|
|
347
|
+
spectrum.metadata['EDS']['GUI'][key]['composition_counts'] = intensity*x_sect
|
|
348
|
+
# print(key, ' - ', family['symmetry'], intensity, x_sect, f_yield, peaks.max())
|
|
349
|
+
|
|
350
|
+
total += intensity * x_sect
|
|
351
|
+
total_amu += intensity * x_sect * amu
|
|
352
|
+
|
|
353
|
+
for key, family in families.items():
|
|
354
|
+
if key in mask:
|
|
355
|
+
continue
|
|
356
|
+
amu = spectrum.metadata['EDS'][key]['atomic_weight']
|
|
357
|
+
intensity = spectrum.metadata['EDS'][key][family['symmetry']].get('areal_density', 0)
|
|
358
|
+
x_sect = spectrum.metadata['EDS']['GUI'][key]['cross_section']
|
|
359
|
+
spectrum.metadata['EDS']['GUI'][key] = {'atom%': intensity*x_sect/total,
|
|
360
|
+
'weight%': intensity*x_sect*amu/total}
|
|
361
|
+
out_text = f"{key:2}: {intensity*x_sect/total*100:.2f} at% "
|
|
362
|
+
out_text += f"{intensity*x_sect*amu/total_amu*100:.2f} wt%"
|
|
363
|
+
print(out_text)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def quantification_k_factors(spectrum, mask=None):
|
|
367
|
+
"""Calculate quantification for EDS spectrum with k-factors."""
|
|
368
|
+
tags = {}
|
|
369
|
+
if not isinstance(mask, list) or mask is None:
|
|
370
|
+
mask = []
|
|
371
|
+
atom_sum = 0.
|
|
372
|
+
weight_sum = 0.
|
|
373
|
+
spectrum.metadata['EDS'].setdefault('GUI', {})
|
|
374
|
+
for key in spectrum.metadata['EDS']:
|
|
375
|
+
intensity = 0.
|
|
376
|
+
k_factor = 0.
|
|
377
|
+
if key in mask + ['detector', 'quantification']:
|
|
378
|
+
pass
|
|
379
|
+
elif isinstance(spectrum.metadata['EDS'][key], dict) and key in elements_list:
|
|
380
|
+
family = spectrum.metadata['EDS'][key].get('GUI', {}).get('symmetry', None)
|
|
381
|
+
if family is None:
|
|
382
|
+
if 'K-family' in spectrum.metadata['EDS'][key]:
|
|
383
|
+
family = 'K-family'
|
|
384
|
+
elif 'L-family' in spectrum.metadata['EDS'][key]:
|
|
385
|
+
family = 'L-family'
|
|
386
|
+
elif 'M-family' in spectrum.metadata['EDS'][key]:
|
|
387
|
+
family = 'M-family'
|
|
388
|
+
spectrum.metadata['EDS']['GUI'][key] = {'symmetry': family}
|
|
389
|
+
|
|
390
|
+
intensity = spectrum.metadata['EDS'][key][family]['areal_density'] # /peaks_max
|
|
391
|
+
k_factor = spectrum.metadata['EDS'][key][family]['k_factor']
|
|
392
|
+
atomic_weight = spectrum.metadata['EDS'][key]['atomic_weight']
|
|
393
|
+
tags[key] = {'atom%': intensity*k_factor/ atomic_weight,
|
|
394
|
+
'weight%': (intensity*k_factor) ,
|
|
395
|
+
'k_factor': k_factor,
|
|
396
|
+
'intensity': intensity}
|
|
397
|
+
atom_sum += intensity*k_factor/ atomic_weight
|
|
398
|
+
weight_sum += intensity*k_factor
|
|
399
|
+
tags['sums'] = {'atom%': atom_sum, 'weight%': weight_sum}
|
|
400
|
+
|
|
401
|
+
spectrum.metadata['EDS']['quantification'] = tags
|
|
402
|
+
eds_dict = spectrum.metadata['EDS']
|
|
403
|
+
for key in eds_dict['quantification']:
|
|
404
|
+
if key != 'sums':
|
|
405
|
+
tags = eds_dict['quantification']
|
|
406
|
+
out_string = f"{key:2}: {tags[key]['atom%']/tags['sums']['atom%']*100:.2f} at%"
|
|
407
|
+
out_string += f" {tags[key]['weight%']/tags['sums']['weight%']*100:.2f} wt%"
|
|
408
|
+
if key in eds_dict['GUI']:
|
|
409
|
+
eds_dict['GUI'][key]['atom%'] = tags[key]['atom%']/tags['sums']['atom%']*100
|
|
410
|
+
eds_dict['GUI'][key]['weight%'] = tags[key]['weight%']/tags['sums']['weight%']*100
|
|
411
|
+
print(out_string)
|
|
412
|
+
print('excluded from quantification ', mask)
|
|
413
|
+
|
|
414
|
+
return tags
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def get_emission (element, line_family, accelerating_energy=200000):
|
|
418
|
+
"""Calculate the total emission for a given element and line family."""
|
|
419
|
+
element_z = get_z(element)
|
|
420
|
+
tags = {element:{'atomic_number': element_z}}
|
|
421
|
+
family_ionization(tags[element], line_family, accelerating_energy)
|
|
422
|
+
|
|
423
|
+
tags[element][f'{line_family}_emission'] = 0.
|
|
424
|
+
for line in shell_list:
|
|
425
|
+
if line_family in line:
|
|
426
|
+
print(line, radiation_emission(element_z, line))
|
|
427
|
+
print('emission', tags[element][line] * radiation_emission(element_z, line))
|
|
428
|
+
tags[element][f'{line_family}_emission'] += (tags[element][line]
|
|
429
|
+
* radiation_emission(element_z, line))
|
|
430
|
+
# tags[element][f'{line_family}_emission']
|
|
431
|
+
|
|
432
|
+
return tags
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
eels_tools - A collection of tools to analyze EELS data
|
|
3
|
+
Model based quantification of electron energy-loss data
|
|
4
|
+
part of pyTEMlib
|
|
5
|
+
Copyright by Gerd Duscher
|
|
6
|
+
seperated into different files 09/2025
|
|
7
|
+
"""
|
|
8
|
+
from ..utilities import major_edges, all_edges, first_close_edges, elements
|
|
9
|
+
from ..utilities import get_wave_length, effective_collection_angle, set_default_metadata
|
|
10
|
+
from ..utilities import lorentz, gauss, get_x_sections, get_z, get_spectrum
|
|
11
|
+
|
|
12
|
+
from .zero_loss_tools import zero_loss_function, get_resolution_functions
|
|
13
|
+
from .zero_loss_tools import get_zero_loss_energy, shift_energy, align_zero_loss
|
|
14
|
+
|
|
15
|
+
from .low_loss_tools import drude_simulation, kroeger_core
|
|
16
|
+
from .low_loss_tools import get_plasmon_losses, drude, drude_lorentz
|
|
17
|
+
from .low_loss_tools import energy_loss_function, angle_correction, fit_plasmon
|
|
18
|
+
from .low_loss_tools import fit_multiple_scattering, multiple_scattering
|
|
19
|
+
from .low_loss_tools import inelastic_mean_free_path, model3, add_peaks
|
|
20
|
+
|
|
21
|
+
from .peak_fit_tools import model_smooth, gaussian_mixture_model, find_peaks, find_maxima
|
|
22
|
+
from .peak_fit_tools import sort_peaks
|
|
23
|
+
|
|
24
|
+
from .core_loss_tools import make_cross_sections, fit_edges2, power_law_background
|
|
25
|
+
from .core_loss_tools import list_all_edges, find_all_edges, find_associated_edges
|
|
26
|
+
from .core_loss_tools import find_white_lines, find_edges, assign_likely_edges
|
|
27
|
+
from .core_loss_tools import auto_id_edges, identify_edges, add_element_to_dataset
|
|
28
|
+
from .core_loss_tools import fit_dataset, auto_chemical_composition, make_edges
|
|
29
|
+
from .core_loss_tools import cl_model, core_loss_model, fit_edges, xsec_xrpa
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = ['major_edges', 'all_edges', 'first_close_edges', 'elements', 'get_wave_length',
|
|
33
|
+
'effective_collection_angle', 'set_default_metadata', 'lorentz', 'gauss',
|
|
34
|
+
'get_x_sections', 'get_z', 'get_spectrum', 'zero_loss_function',
|
|
35
|
+
'get_resolution_functions', 'get_zero_loss_energy', 'shift_energy', 'align_zero_loss',
|
|
36
|
+
'drude_simulation', 'kroeger_core','get_plasmon_losses', 'drude', 'drude_lorentz',
|
|
37
|
+
'energy_loss_function', 'angle_correction', 'fit_plasmon', 'fit_multiple_scattering',
|
|
38
|
+
'multiple_scattering', 'inelastic_mean_free_path', 'model3', 'add_peaks',
|
|
39
|
+
'model_smooth', 'gaussian_mixture_model', 'find_peaks', 'find_maxima', 'sort_peaks',
|
|
40
|
+
'make_cross_sections', 'fit_edges2', 'power_law_background', 'list_all_edges',
|
|
41
|
+
'find_all_edges', 'find_associated_edges', 'find_white_lines', 'find_edges',
|
|
42
|
+
'assign_likely_edges', 'auto_id_edges', 'identify_edges', 'add_element_to_dataset',
|
|
43
|
+
'fit_dataset', 'auto_chemical_composition', 'make_edges', 'cl_model', 'core_loss_model',
|
|
44
|
+
'fit_edges', 'xsec_xrpa']
|