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,751 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#################################################################
|
|
3
|
+
# Core-Loss functions
|
|
4
|
+
#################################################################
|
|
5
|
+
of eels_tools
|
|
6
|
+
Model based quantification of electron energy-loss data
|
|
7
|
+
Copyright by Gerd Duscher
|
|
8
|
+
|
|
9
|
+
The University of Tennessee, Knoxville
|
|
10
|
+
Department of Materials Science & Engineering
|
|
11
|
+
|
|
12
|
+
Sources:
|
|
13
|
+
M. Tian et al.
|
|
14
|
+
|
|
15
|
+
Units:
|
|
16
|
+
everything is in SI units, except length is given in nm and angles in mrad.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
See the notebooks for examples of these routines
|
|
20
|
+
|
|
21
|
+
All the input and output is done through a dictionary which is to be found in the meta_data
|
|
22
|
+
attribute of the sidpy.Dataset
|
|
23
|
+
|
|
24
|
+
Update by Austin Houston, UTK 12-2023 : Parallization of spectrum images
|
|
25
|
+
"""
|
|
26
|
+
from typing import Union
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
import scipy
|
|
30
|
+
import sidpy
|
|
31
|
+
|
|
32
|
+
from ..utilities import major_edges, all_edges, elements
|
|
33
|
+
from ..utilities import effective_collection_angle
|
|
34
|
+
from ..utilities import get_z, get_x_sections, second_derivative
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def list_all_edges(z: Union[str, int]=0, verbose=False)->list[str, dict]:
|
|
39
|
+
"""List all ionization edges of an element with atomic number z
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
z: int
|
|
44
|
+
atomic number
|
|
45
|
+
verbose: bool, optional
|
|
46
|
+
more info if set to True
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
out_string: str
|
|
51
|
+
string with all major edges in energy range
|
|
52
|
+
"""
|
|
53
|
+
x_section = get_x_sections(get_z(z))
|
|
54
|
+
out_string = ''
|
|
55
|
+
if verbose:
|
|
56
|
+
print('Major edges')
|
|
57
|
+
element = x_section.get('name', None)
|
|
58
|
+
edge_list = {element: {}}
|
|
59
|
+
|
|
60
|
+
for key in all_edges:
|
|
61
|
+
onset = x_section.get(key, {}).get('onset', None)
|
|
62
|
+
if onset is None:
|
|
63
|
+
continue
|
|
64
|
+
out = f" {element}-{key}: {onset:8.1f} eV "
|
|
65
|
+
if verbose:
|
|
66
|
+
print(out)
|
|
67
|
+
out_string = out_string + f"{out} /n"
|
|
68
|
+
edge_list[element][key] = onset
|
|
69
|
+
return out_string, edge_list
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def find_all_edges(edge_onset: float,
|
|
73
|
+
maximal_chemical_shift: float=5.0,
|
|
74
|
+
major_edges_only: bool=False) -> str:
|
|
75
|
+
"""Find all (major and minor) edges within an energy range
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
edge_onset: float
|
|
80
|
+
approximate energy of ionization edge
|
|
81
|
+
maximal_chemical_shift: float, default = 5eV
|
|
82
|
+
range of energy window around edge_onset to look for major edges
|
|
83
|
+
major_edges_only: boolean, default = False
|
|
84
|
+
only major edges are considered if True
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
text: str
|
|
88
|
+
string with all edges in energy range
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
out_text = ''
|
|
93
|
+
for z in np.arange(1, 93):
|
|
94
|
+
x_section = get_x_sections(z)
|
|
95
|
+
name = x_section.get('name', '')
|
|
96
|
+
for key in x_section:
|
|
97
|
+
if not isinstance(x_section[key], dict):
|
|
98
|
+
continue
|
|
99
|
+
onset = x_section[key].get('onset', 0)
|
|
100
|
+
if abs(onset - edge_onset) > maximal_chemical_shift:
|
|
101
|
+
continue
|
|
102
|
+
if major_edges_only:
|
|
103
|
+
if key in major_edges:
|
|
104
|
+
out_text += f"\n {name:2s}-{key}: {onset:8.1f} eV "
|
|
105
|
+
else:
|
|
106
|
+
out_text += f"\n {name:2s}-{key}: {onset:8.1f} eV "
|
|
107
|
+
return out_text
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def find_associated_edges(dataset: sidpy.Dataset) -> None:
|
|
111
|
+
"""Find edges associated with peaks in the dataset"""
|
|
112
|
+
onsets = []
|
|
113
|
+
core_loss = dataset.metadata.get('core_loss', {}).get('edges', {})
|
|
114
|
+
for key, edge in core_loss.items():
|
|
115
|
+
if key.isdigit():
|
|
116
|
+
onsets.append(edge['onset'])
|
|
117
|
+
core_loss[key]['associated_peaks'] = {}
|
|
118
|
+
peaks = dataset.metadata['peak_fit'].get('peaks', [])
|
|
119
|
+
for key, peak in enumerate(peaks):
|
|
120
|
+
distances = (onsets-peak[0]) * -1
|
|
121
|
+
distances[distances < -0.3] = 1e6
|
|
122
|
+
if np.min(distances) < 50:
|
|
123
|
+
index = np.argmin(distances)
|
|
124
|
+
core_loss[str(index)]['associated_peaks'][key] = peak
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def find_white_lines(dataset: sidpy.Dataset) -> Union[None, dict]:
|
|
128
|
+
"""Find white lines in the dataset"""
|
|
129
|
+
white_lines_out ={'sum': {}, 'ratio': {}}
|
|
130
|
+
white_lines = []
|
|
131
|
+
peaks = dataset.metadata.get('peak_fit', {}).get('peaks', [])
|
|
132
|
+
core_loss = dataset.metadata.get('core_loss', {})
|
|
133
|
+
for index, edge in core_loss.get('edges', {}).items():
|
|
134
|
+
if not index.isdigit():
|
|
135
|
+
continue
|
|
136
|
+
peaks = edge.get('associated_peaks', {})
|
|
137
|
+
if edge['symmetry'][-2:] == 'L3' and 'L3' in edge['all_edges']:
|
|
138
|
+
onset_l3 = edge['all_edges']['L3']['onset']
|
|
139
|
+
onset_l2 = edge['all_edges']['L2']['onset']
|
|
140
|
+
end_range1 = onset_l2 + edge['chemical_shift']
|
|
141
|
+
end_range2 = onset_l2*2 - onset_l3 + edge['chemical_shift']
|
|
142
|
+
white_lines = ['L3', 'L2']
|
|
143
|
+
elif edge['symmetry'][-2:] == 'M5' and 'M5' in edge['all_edges']:
|
|
144
|
+
onset_m5 = edge['all_edges']['M5']['onset']
|
|
145
|
+
onset_m4 = edge['all_edges']['M4']['onset']
|
|
146
|
+
end_range1 = onset_m4 + edge['chemical_shift']
|
|
147
|
+
end_range2 = onset_m4*2 - onset_m5 + edge['chemical_shift']
|
|
148
|
+
white_lines = ['M5', 'M4']
|
|
149
|
+
else:
|
|
150
|
+
continue
|
|
151
|
+
white_line_areas = [0., 0.]
|
|
152
|
+
for key, peak in peaks.items():
|
|
153
|
+
if not str(key).isdigit():
|
|
154
|
+
continue
|
|
155
|
+
area = np.sqrt(2 * np.pi) * peak[1] * np.abs(peak[2]/np.sqrt(2 * np.log(2)))
|
|
156
|
+
if peak[0] < end_range1:
|
|
157
|
+
white_line_areas[0] += area
|
|
158
|
+
elif peak[0] < end_range2:
|
|
159
|
+
white_line_areas[1] += area
|
|
160
|
+
|
|
161
|
+
edge['white_lines'] = {white_lines[0]: white_line_areas[0],
|
|
162
|
+
white_lines[1]: white_line_areas[1]}
|
|
163
|
+
reference_counts = edge['areal_density'] * core_loss['xsections'][int(index)].sum()
|
|
164
|
+
key = f"{edge['element']}-{white_lines[0]}+{white_lines[1]}"
|
|
165
|
+
white_lines_out['sum'][key] = (white_line_areas[0] + white_line_areas[1])/reference_counts
|
|
166
|
+
key = f"{edge['element']}-{white_lines[0]}/{white_lines[1]}"
|
|
167
|
+
white_lines_out['ratio'][key] = white_line_areas[0] / white_line_areas[1]
|
|
168
|
+
return white_lines_out
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def find_edges(dataset: sidpy.Dataset) -> None:
|
|
172
|
+
"""find edges within a sidpy.Dataset"""
|
|
173
|
+
|
|
174
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
175
|
+
|
|
176
|
+
second_dif, noise_level = second_derivative(dataset)
|
|
177
|
+
|
|
178
|
+
[indices, peaks] = scipy.signal.find_peaks(second_dif, noise_level)
|
|
179
|
+
|
|
180
|
+
peaks['peak_positions'] = energy_scale[indices]
|
|
181
|
+
peaks['peak_indices'] = indices
|
|
182
|
+
edge_energies = [energy_scale[50]]
|
|
183
|
+
edge_indices = []
|
|
184
|
+
|
|
185
|
+
[indices, _] = scipy.signal.find_peaks(-second_dif, noise_level)
|
|
186
|
+
minima = energy_scale[indices]
|
|
187
|
+
|
|
188
|
+
for peak_number in range(len(peaks['peak_positions'])):
|
|
189
|
+
position = peaks['peak_positions'][peak_number]
|
|
190
|
+
if position - edge_energies[-1] > 20:
|
|
191
|
+
impossible = minima[minima < position]
|
|
192
|
+
impossible = impossible[impossible > position - 5]
|
|
193
|
+
if len(impossible) == 0:
|
|
194
|
+
possible = minima[minima > position]
|
|
195
|
+
possible = possible[possible < position + 5]
|
|
196
|
+
if len(possible) > 0:
|
|
197
|
+
edge_energies.append((position + possible[0])/2)
|
|
198
|
+
edge_indices.append(np.searchsorted(energy_scale, (position + possible[0])/2))
|
|
199
|
+
|
|
200
|
+
selected_edges = []
|
|
201
|
+
for peak in edge_indices:
|
|
202
|
+
if 525 < energy_scale[peak] < 533:
|
|
203
|
+
selected_edges.append('O-K1')
|
|
204
|
+
else:
|
|
205
|
+
selected_edge = ''
|
|
206
|
+
edges = find_all_edges(energy_scale[peak], 20, major_edges_only=True)
|
|
207
|
+
edges = edges.split('\n')
|
|
208
|
+
minimum_dist = 100.
|
|
209
|
+
for edge in edges[1:]:
|
|
210
|
+
edge = edge[:-3].split(':')
|
|
211
|
+
name = edge[0].strip()
|
|
212
|
+
energy = float(edge[1].strip())
|
|
213
|
+
if np.abs(energy - energy_scale[peak]) < minimum_dist:
|
|
214
|
+
minimum_dist = np.abs(energy - energy_scale[peak])
|
|
215
|
+
selected_edge = name
|
|
216
|
+
if selected_edge != '':
|
|
217
|
+
selected_edges.append(selected_edge)
|
|
218
|
+
return selected_edges
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def assign_likely_edges(edge_channels: Union[list, np.ndarray], energy: np.ndarray):
|
|
222
|
+
"""Assign likely edges to energy channels"""
|
|
223
|
+
edges_in_list = []
|
|
224
|
+
result = {}
|
|
225
|
+
for channel in edge_channels:
|
|
226
|
+
if channel not in edge_channels[edges_in_list]:
|
|
227
|
+
shift = 5
|
|
228
|
+
element_list = find_all_edges(energy[channel], maximal_chemical_shift=shift,
|
|
229
|
+
major_edges_only=True)
|
|
230
|
+
while len(element_list) < 1:
|
|
231
|
+
shift += 1
|
|
232
|
+
element_list = find_all_edges(energy[channel], maximal_chemical_shift=shift,
|
|
233
|
+
major_edges_only=True)
|
|
234
|
+
if len(element_list) > 1:
|
|
235
|
+
while len(element_list) > 0:
|
|
236
|
+
shift-=1
|
|
237
|
+
element_list = find_all_edges(energy[channel], maximal_chemical_shift=shift,
|
|
238
|
+
major_edges_only=True)
|
|
239
|
+
element_list = find_all_edges(energy[channel], maximal_chemical_shift=shift+1,
|
|
240
|
+
major_edges_only=True)
|
|
241
|
+
element = (element_list[:4]).strip()
|
|
242
|
+
z = get_z(element)
|
|
243
|
+
result[element] =[]
|
|
244
|
+
_, edge_list = list_all_edges(z)
|
|
245
|
+
|
|
246
|
+
for edge in edge_list.values():
|
|
247
|
+
possible_minor_edge = np.argmin(np.abs(energy[edge_channels]-edge))
|
|
248
|
+
if np.abs(energy[edge_channels[possible_minor_edge]]-edge) < 3:
|
|
249
|
+
edges_in_list.append(possible_minor_edge)
|
|
250
|
+
result[element].append(edge)
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def auto_id_edges(dataset):
|
|
255
|
+
"""Automatically identifies edges in a dataset"""
|
|
256
|
+
edge_channels = identify_edges(dataset)
|
|
257
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
258
|
+
found_edges = assign_likely_edges(edge_channels, energy_scale)
|
|
259
|
+
return found_edges
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def identify_edges(dataset: sidpy.Dataset, noise_level: float=2.0):
|
|
263
|
+
"""
|
|
264
|
+
Using first derivative to determine edge onsets
|
|
265
|
+
Any peak in first derivative higher than noise_level times standard deviation will be considered
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
dataset: sidpy.Dataset
|
|
270
|
+
the spectrum
|
|
271
|
+
noise_level: float
|
|
272
|
+
ths number times standard deviation in first derivative decides
|
|
273
|
+
on whether an edge onset is significant
|
|
274
|
+
|
|
275
|
+
Return
|
|
276
|
+
------
|
|
277
|
+
edge_channel: numpy.ndarray
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
|
|
281
|
+
dispersion = energy_scale.slope
|
|
282
|
+
|
|
283
|
+
spec = scipy.ndimage.gaussian_filter(dataset, 3/dispersion) # smooth with 3eV wideGaussian
|
|
284
|
+
|
|
285
|
+
first_derivative = spec - np.roll(spec, +2)
|
|
286
|
+
first_derivative[:3] = 0
|
|
287
|
+
first_derivative[-3:] = 0
|
|
288
|
+
|
|
289
|
+
# find if there is a strong edge at high energy_scale
|
|
290
|
+
noise_level = noise_level*np.std(first_derivative[3:50])
|
|
291
|
+
[edge_channels, _] = scipy.signal.find_peaks(first_derivative, noise_level)
|
|
292
|
+
return edge_channels
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def add_element_to_dataset(dataset: sidpy.Dataset, z: Union[int, str]):
|
|
296
|
+
"""Adds an element to the dataset"""
|
|
297
|
+
# We check whether this element is already in the
|
|
298
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
|
|
299
|
+
|
|
300
|
+
zz = get_z(z)
|
|
301
|
+
if 'edges' not in dataset.metadata:
|
|
302
|
+
dataset.metadata['edges'] = {'model': {}, 'use_low_loss': False}
|
|
303
|
+
index = 0
|
|
304
|
+
for key, edge in dataset.metadata['edges'].items():
|
|
305
|
+
if not key.isdigit():
|
|
306
|
+
continue
|
|
307
|
+
index += 1
|
|
308
|
+
if zz == edge.get('z', ''):
|
|
309
|
+
index = int(key)
|
|
310
|
+
break
|
|
311
|
+
|
|
312
|
+
major_edge = ''
|
|
313
|
+
minor_edge = ''
|
|
314
|
+
all_edges2 = {}
|
|
315
|
+
x_section = get_x_sections(zz)
|
|
316
|
+
edge_start = 10 # int(15./ft.get_slope(self.energy_scale)+0.5)
|
|
317
|
+
for key in x_section:
|
|
318
|
+
if len(key) == 2 and key[0] in ['K', 'L', 'M', 'N', 'O'] and key[1].isdigit():
|
|
319
|
+
if energy_scale[edge_start] < x_section[key]['onset'] < energy_scale[-edge_start]:
|
|
320
|
+
if key in ['K1', 'L3', 'M5', 'M3']:
|
|
321
|
+
major_edge = key
|
|
322
|
+
all_edges2[key] = {'onset': x_section[key]['onset']}
|
|
323
|
+
|
|
324
|
+
if major_edge != '':
|
|
325
|
+
key = major_edge
|
|
326
|
+
elif minor_edge != '':
|
|
327
|
+
key = minor_edge
|
|
328
|
+
else:
|
|
329
|
+
print(f'Could not find no edge of {zz} in spectrum')
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
edge = dataset.metadata['edges'].setdefault(str(index), {})
|
|
333
|
+
|
|
334
|
+
start_exclude = x_section[key]['onset'] - x_section[key]['excl before']
|
|
335
|
+
end_exclude = x_section[key]['onset'] + x_section[key]['excl after']
|
|
336
|
+
|
|
337
|
+
edge.update({'z': zz, 'symmetry': key, 'element': elements[zz],
|
|
338
|
+
'onset': x_section[key]['onset'], 'end_exclude': end_exclude,
|
|
339
|
+
'start_exclude': start_exclude})
|
|
340
|
+
edge['all_edges'] = all_edges2
|
|
341
|
+
edge['chemical_shift'] = 0.0
|
|
342
|
+
edge['areal_density'] = 0.0
|
|
343
|
+
edge['original_onset'] = edge['onset']
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def make_edges(edges_present: dict, energy_scale: np.ndarray, e_0:float,
|
|
348
|
+
coll_angle:float, low_loss:np.ndarray=None)->dict:
|
|
349
|
+
"""Makes the edges dictionary for quantification
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
edges_present: list
|
|
354
|
+
list of edges
|
|
355
|
+
energy_scale: numpy array
|
|
356
|
+
energy scale on which to make cross-section
|
|
357
|
+
e_0: float
|
|
358
|
+
acceleration voltage (in V)
|
|
359
|
+
coll_angle: float
|
|
360
|
+
collection angle in mrad
|
|
361
|
+
low_loss: numpy array with same length as energy_scale
|
|
362
|
+
low_less spectrum with which to convolve the cross-section (default=None)
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
edges: dict
|
|
367
|
+
dictionary with all information on cross-section
|
|
368
|
+
"""
|
|
369
|
+
x_sections = get_x_sections()
|
|
370
|
+
edges = {}
|
|
371
|
+
for i, edge in enumerate(edges_present):
|
|
372
|
+
element, symmetry = edge.split('-')
|
|
373
|
+
z = 0
|
|
374
|
+
for key in x_sections:
|
|
375
|
+
if element == x_sections[key]['name']:
|
|
376
|
+
z = int(key)
|
|
377
|
+
edges[i] = {}
|
|
378
|
+
edges[i]['z'] = z
|
|
379
|
+
edges[i]['symmetry'] = symmetry
|
|
380
|
+
edges[i]['element'] = element
|
|
381
|
+
|
|
382
|
+
for key, edge in edges.items():
|
|
383
|
+
xsec = x_sections[str(edge['z'])]
|
|
384
|
+
if 'chemical_shift' not in edge:
|
|
385
|
+
edge['chemical_shift'] = 0
|
|
386
|
+
if 'symmetry' not in edge:
|
|
387
|
+
edge['symmetry'] = 'K1'
|
|
388
|
+
if 'K' in edge['symmetry']:
|
|
389
|
+
edge['symmetry'] = 'K1'
|
|
390
|
+
elif 'L' in edge['symmetry']:
|
|
391
|
+
edge['symmetry'] = 'L3'
|
|
392
|
+
elif 'M' in edge['symmetry']:
|
|
393
|
+
edge['symmetry'] = 'M5'
|
|
394
|
+
else:
|
|
395
|
+
edge['symmetry'] = edge['symmetry'][0:2]
|
|
396
|
+
|
|
397
|
+
edge['original_onset'] = xsec[edge['symmetry']]['onset']
|
|
398
|
+
edge['onset'] = edge['original_onset'] + edge['chemical_shift']
|
|
399
|
+
edge['start_exclude'] = edge['onset'] - xsec[edge['symmetry']]['excl before']
|
|
400
|
+
edge['end_exclude'] = edge['onset'] + xsec[edge['symmetry']]['excl after']
|
|
401
|
+
|
|
402
|
+
edges = make_cross_sections(edges, energy_scale, e_0, coll_angle, low_loss)
|
|
403
|
+
return edges
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def auto_chemical_composition(dataset:sidpy.Dataset)->None:
|
|
407
|
+
"""Automatically identifies edges in a dataset and adds them to the core_loss dictionary"""
|
|
408
|
+
found_edges = auto_id_edges(dataset)
|
|
409
|
+
for key in found_edges:
|
|
410
|
+
add_element_to_dataset(dataset, key)
|
|
411
|
+
fit_dataset(dataset)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def make_cross_sections(edges:dict, energy_scale:np.ndarray, e_0:float,
|
|
415
|
+
coll_angle:float, low_loss:np.ndarray=None)->dict:
|
|
416
|
+
"""
|
|
417
|
+
Updates the edges dictionary with collection angle-integrated
|
|
418
|
+
X-ray photo-absorption cross-sections
|
|
419
|
+
"""
|
|
420
|
+
for key in edges:
|
|
421
|
+
if str(key).isdigit():
|
|
422
|
+
if edges[key]['z'] <1:
|
|
423
|
+
break
|
|
424
|
+
# from barnes to 1/nm^2
|
|
425
|
+
edges[key]['data'] = xsec_xrpa(energy_scale, e_0 / 1000., edges[key]['z'], coll_angle,
|
|
426
|
+
edges[key]['chemical_shift']) / 1e10
|
|
427
|
+
if low_loss is not None:
|
|
428
|
+
low_loss = np.roll(np.array(low_loss), 1024 - np.argmax(np.array(low_loss)))
|
|
429
|
+
edges[key]['data'] = scipy.signal.convolve(edges[key]['data'],
|
|
430
|
+
low_loss/low_loss.sum(), mode='same')
|
|
431
|
+
|
|
432
|
+
edges[key]['onset'] = edges[key]['original_onset'] + edges[key]['chemical_shift']
|
|
433
|
+
edges[key]['X_section_type'] = 'XRPA'
|
|
434
|
+
edges[key]['X_section_source'] = 'pyTEMlib'
|
|
435
|
+
|
|
436
|
+
return edges
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def power_law(energy: np.ndarray, a:float, r:float)->np.ndarray:
|
|
440
|
+
"""power law for power_law_background"""
|
|
441
|
+
return a * np.power(energy, -r)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def power_law_background(spectrum:np.ndarray, energy_scale:np.ndarray,
|
|
445
|
+
fit_area:list, verbose:bool=False):
|
|
446
|
+
"""fit of power law to spectrum """
|
|
447
|
+
|
|
448
|
+
# Determine energy window for background fit in pixels
|
|
449
|
+
startx = np.searchsorted(energy_scale, fit_area[0])
|
|
450
|
+
endx = np.searchsorted(energy_scale, fit_area[1])
|
|
451
|
+
|
|
452
|
+
x = np.array(energy_scale)[startx:endx]
|
|
453
|
+
y = np.array(spectrum)[startx:endx].flatten()
|
|
454
|
+
|
|
455
|
+
# Initial values of parameters
|
|
456
|
+
p0 = np.array([1.0E+20, 3])
|
|
457
|
+
|
|
458
|
+
# background fitting
|
|
459
|
+
def bgdfit(pp, yy, xx):
|
|
460
|
+
err = yy - power_law(xx, pp[0], pp[1])
|
|
461
|
+
return err
|
|
462
|
+
|
|
463
|
+
[p, _] = scipy.optimize.leastsq(bgdfit, p0, args=(y, x), maxfev=2000)
|
|
464
|
+
|
|
465
|
+
background_difference = y - power_law(x, p[0], p[1])
|
|
466
|
+
background_noise_level = std_dev = np.std(background_difference)
|
|
467
|
+
if verbose:
|
|
468
|
+
print(f'Power-law background with amplitude A: {p[0]:.1f} and exponent -r: {p[1]:.2f}')
|
|
469
|
+
print(background_difference.max() / background_noise_level)
|
|
470
|
+
|
|
471
|
+
print(f'Noise level in spectrum {std_dev:.3f} counts')
|
|
472
|
+
|
|
473
|
+
# Calculate background over the whole energy scale
|
|
474
|
+
background = power_law(energy_scale, p[0], p[1])
|
|
475
|
+
return background, p
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def cl_model(xx, pp, number_of_edges, xsec):
|
|
479
|
+
""" core loss model for fitting"""
|
|
480
|
+
yy = pp[0] * xx**pp[1] + pp[2] + pp[3]* xx + pp[4] * xx * xx
|
|
481
|
+
for i in range(number_of_edges):
|
|
482
|
+
pp[i+5] = np.abs(pp[i+5])
|
|
483
|
+
yy = yy + pp[i+5] * xsec[i, :]
|
|
484
|
+
return yy
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def get_mask(energy_scale, edges):
|
|
488
|
+
""" Create a mask for the fitting area"""
|
|
489
|
+
mask = np.ones(len(energy_scale))
|
|
490
|
+
edges.setdefault('fit_area', {})
|
|
491
|
+
background_fit_start = edges.get('fit_area', {}).get('fit_start', 0)
|
|
492
|
+
background_fit_end = edges.get('fit_area', {}).setdefault('fit_end', energy_scale[-1])
|
|
493
|
+
if background_fit_start == 0:
|
|
494
|
+
return mask
|
|
495
|
+
edges['fit_area']['fit_end'] = background_fit_end
|
|
496
|
+
|
|
497
|
+
start_bgd = np.searchsorted(energy_scale, background_fit_start)
|
|
498
|
+
end_bgd = np.searchsorted(energy_scale, background_fit_end)
|
|
499
|
+
# Determine fitting ranges and masks to exclude ranges
|
|
500
|
+
|
|
501
|
+
mask[0:start_bgd] = 0.0
|
|
502
|
+
mask[end_bgd:-1] = 0.0
|
|
503
|
+
for key in edges:
|
|
504
|
+
if not key.isdigit():
|
|
505
|
+
continue
|
|
506
|
+
start_exclude = np.searchsorted(energy_scale, edges[key]['start_exclude'])
|
|
507
|
+
end_exclude = np.searchsorted(energy_scale, edges[key]['end_exclude'])
|
|
508
|
+
if start_bgd+1 < start_exclude < end_bgd-2 and end_exclude < end_bgd:
|
|
509
|
+
start_exclude = max (start_exclude, 2)
|
|
510
|
+
mask[start_exclude:end_exclude] = 0.0
|
|
511
|
+
return mask
|
|
512
|
+
|
|
513
|
+
def fit_edges2(spectrum, energy_scale, edges):
|
|
514
|
+
""" Fit edges in a spectrum """
|
|
515
|
+
mask = get_mask(energy_scale, edges)
|
|
516
|
+
|
|
517
|
+
########################
|
|
518
|
+
# Background Fit
|
|
519
|
+
########################
|
|
520
|
+
bgd_fit_area = [edges['fit_area']['fit_start'], edges['fit_area']['fit_end']]
|
|
521
|
+
_, [amplitude, r] = power_law_background(spectrum, energy_scale, bgd_fit_area, verbose=False)
|
|
522
|
+
|
|
523
|
+
#######################
|
|
524
|
+
# Edge Fit
|
|
525
|
+
#######################
|
|
526
|
+
|
|
527
|
+
blurred = scipy.ndimage.gaussian_filter(spectrum, sigma=5)
|
|
528
|
+
blurred[np.where(blurred < 1e-8)] = 1e-8
|
|
529
|
+
|
|
530
|
+
xsec = []
|
|
531
|
+
number_of_edges = 0
|
|
532
|
+
for key in edges:
|
|
533
|
+
if key.isdigit():
|
|
534
|
+
xsec.append(edges[key]['data'])
|
|
535
|
+
number_of_edges += 1
|
|
536
|
+
xsec = np.array(xsec)
|
|
537
|
+
|
|
538
|
+
def model(xx, pp):
|
|
539
|
+
yy = pp[0] * xx**pp[1] + pp[2] + pp[3] * xx + pp[4] * xx**2
|
|
540
|
+
for i in range(number_of_edges):
|
|
541
|
+
pp[i+5] = np.abs(pp[i+5])
|
|
542
|
+
yy = yy + pp[i+5] * xsec[i, :]
|
|
543
|
+
return yy
|
|
544
|
+
|
|
545
|
+
def residuals(pp, xx, yy):
|
|
546
|
+
err = np.abs((yy - model(xx, pp)) * mask) / np.sqrt(np.abs(yy))
|
|
547
|
+
return err
|
|
548
|
+
|
|
549
|
+
scale = blurred[100]
|
|
550
|
+
pin = np.array([amplitude, -r, 10., 1., 0.00] + [scale/5] * number_of_edges)
|
|
551
|
+
[p, _] = scipy.optimize.leastsq(residuals, pin, args=(energy_scale, blurred))
|
|
552
|
+
|
|
553
|
+
for key in edges:
|
|
554
|
+
if key.isdigit():
|
|
555
|
+
edges[key]['areal_density'] = p[int(key)+5]
|
|
556
|
+
# print(p)
|
|
557
|
+
background = p[0] * np.power(energy_scale, -p[1])
|
|
558
|
+
background += p[2] + energy_scale**p[3] + p[4]*energy_scale**2
|
|
559
|
+
edges['model'] = {'background': background,
|
|
560
|
+
'background-poly_0': p[2],
|
|
561
|
+
'background-poly_1': p[3],
|
|
562
|
+
'background-poly_2': p[4],
|
|
563
|
+
'background-A': p[0],
|
|
564
|
+
'background-r': p[1],
|
|
565
|
+
'spectrum': model(energy_scale, p),
|
|
566
|
+
'blurred': blurred,
|
|
567
|
+
'mask': mask,
|
|
568
|
+
'fit_parameter': p,
|
|
569
|
+
'fit_area_start': edges['fit_area']['fit_start'],
|
|
570
|
+
'fit_area_end': edges['fit_area']['fit_end'],
|
|
571
|
+
'xsec': xsec}
|
|
572
|
+
return edges
|
|
573
|
+
|
|
574
|
+
def fit_dataset(dataset: sidpy.Dataset):
|
|
575
|
+
"""Fit edges in a sidpy.Dataset"""
|
|
576
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
577
|
+
dataset.metadata['edges'].setdefault('fit_area', {})
|
|
578
|
+
dataset.metadata['edges']['fit_area'].setdefault('fit_start', energy_scale[50])
|
|
579
|
+
dataset.metadata['edges']['fit_area'].setdefault('fit_end', energy_scale[-2])
|
|
580
|
+
dataset.metadata['edges'].setdefault('use_low_loss', False)
|
|
581
|
+
|
|
582
|
+
exp = dataset.metadata.get('experiment', {})
|
|
583
|
+
alpha = exp.get('convergence_angle', None)
|
|
584
|
+
if alpha is None:
|
|
585
|
+
raise ValueError('need a convergence_angle in experiment of metadata dictionary ')
|
|
586
|
+
beta = exp.get('collection_angle', 0)
|
|
587
|
+
beam_kv = exp.get('acceleration_voltage', 0)
|
|
588
|
+
eff_beta = effective_collection_angle(energy_scale, alpha, beta, beam_kv)
|
|
589
|
+
edges = make_cross_sections(dataset.metadata['edges'], energy_scale, beam_kv, eff_beta)
|
|
590
|
+
dataset.metadata['edges'] = fit_edges2(dataset, energy_scale, edges)
|
|
591
|
+
areal_density = []
|
|
592
|
+
element_list = []
|
|
593
|
+
for key in edges:
|
|
594
|
+
if key.isdigit(): # only edges have numbers in that dictionary
|
|
595
|
+
element_list.append(edges[key]['element'])
|
|
596
|
+
areal_density.append(edges[key]['areal_density'])
|
|
597
|
+
areal_density = np.array(areal_density)
|
|
598
|
+
out_string = '\nRelative composition: \n'
|
|
599
|
+
for i, element in enumerate(element_list):
|
|
600
|
+
out_string += f'{element}: {areal_density[i] / areal_density.sum() * 100:.1f}% '
|
|
601
|
+
print(out_string)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def core_loss_model(energy_scale, pp, number_of_edges, xsec):
|
|
605
|
+
""" core loss model from fitting parameters"""
|
|
606
|
+
xx = np.array(energy_scale)
|
|
607
|
+
yy = pp[0] * xx**pp[1] + pp[2] + pp[3]* xx + pp[4] * xx * xx
|
|
608
|
+
for i in range(number_of_edges):
|
|
609
|
+
pp[i+5] = np.abs(pp[i+5])
|
|
610
|
+
yy = yy + pp[i+5] * xsec[i, :]
|
|
611
|
+
return yy
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def fit_edges(spectrum, energy_scale, region_tags, edges):
|
|
615
|
+
"""fit edges for quantification"""
|
|
616
|
+
|
|
617
|
+
# Determine fitting ranges and masks to exclude ranges
|
|
618
|
+
mask = np.ones(len(spectrum))
|
|
619
|
+
|
|
620
|
+
background_fit_end = energy_scale[-1]
|
|
621
|
+
for key in region_tags:
|
|
622
|
+
end = region_tags[key]['start_x'] + region_tags[key]['width_x']
|
|
623
|
+
|
|
624
|
+
startx = np.searchsorted(energy_scale, region_tags[key]['start_x'])
|
|
625
|
+
endx = np.searchsorted(energy_scale, end)
|
|
626
|
+
|
|
627
|
+
if key == 'fit_area':
|
|
628
|
+
mask[0:startx] = 0.0
|
|
629
|
+
mask[endx:-1] = 0.0
|
|
630
|
+
else:
|
|
631
|
+
mask[startx:endx] = 0.0
|
|
632
|
+
background_fit_end = min(region_tags[key]['start_x'], background_fit_end)
|
|
633
|
+
|
|
634
|
+
fit_area = region_tags['fit_area']
|
|
635
|
+
########################
|
|
636
|
+
# Background Fit
|
|
637
|
+
########################
|
|
638
|
+
bgd_fit_area = [region_tags['fit_area']['start_x'], background_fit_end]
|
|
639
|
+
background, [amplitude, r] = power_law_background(spectrum, energy_scale,
|
|
640
|
+
bgd_fit_area, verbose=False)
|
|
641
|
+
|
|
642
|
+
#######################
|
|
643
|
+
# Edge Fit
|
|
644
|
+
#######################
|
|
645
|
+
x = energy_scale
|
|
646
|
+
blurred = scipy.ndimage.gaussian_filter(spectrum, sigma=5)
|
|
647
|
+
|
|
648
|
+
y = blurred # now in probability
|
|
649
|
+
y[np.where(y < 1e-8)] = 1e-8
|
|
650
|
+
|
|
651
|
+
xsec = []
|
|
652
|
+
number_of_edges = 0
|
|
653
|
+
for key in edges:
|
|
654
|
+
if key.isdigit():
|
|
655
|
+
xsec.append(edges[key]['data'])
|
|
656
|
+
number_of_edges += 1
|
|
657
|
+
xsec = np.array(xsec)
|
|
658
|
+
|
|
659
|
+
def model(xx, pp):
|
|
660
|
+
yy = background + pp[6] + pp[7] * xx + pp[8] * xx * xx
|
|
661
|
+
for i in range(number_of_edges):
|
|
662
|
+
pp[i] = np.abs(pp[i])
|
|
663
|
+
yy = yy + pp[i] * xsec[i, :]
|
|
664
|
+
return yy
|
|
665
|
+
|
|
666
|
+
def residuals(pp, xx, yy):
|
|
667
|
+
err = np.abs((yy - model(xx, pp)) * mask) # / np.sqrt(np.abs(y))
|
|
668
|
+
return err
|
|
669
|
+
|
|
670
|
+
scale = y[100]
|
|
671
|
+
pin = np.array([scale/5, scale/5, scale/5, scale/5, scale/5, scale/5, -scale/10, 1.0, 0.001])
|
|
672
|
+
[p, _] = scipy.optimize.leastsq(residuals, pin, args=(x, y))
|
|
673
|
+
|
|
674
|
+
for key in edges:
|
|
675
|
+
if key.isdigit():
|
|
676
|
+
edges[key]['areal_density'] = p[int(key) - 1]
|
|
677
|
+
edges['model'] = {}
|
|
678
|
+
edges['model']['background'] = background + p[6] + p[7] * x + p[8] * x**2
|
|
679
|
+
edges['model']['background-poly_0'] = p[6]
|
|
680
|
+
edges['model']['background-poly_1'] = p[7]
|
|
681
|
+
edges['model']['background-poly_2'] = p[8]
|
|
682
|
+
edges['model']['background-A'] = amplitude
|
|
683
|
+
edges['model']['background-r'] = r
|
|
684
|
+
edges['model']['spectrum'] = model(x, p)
|
|
685
|
+
edges['model']['blurred'] = blurred
|
|
686
|
+
edges['model']['mask'] = mask
|
|
687
|
+
edges['model']['fit_parameter'] = p
|
|
688
|
+
edges['model']['fit_area_sta' \
|
|
689
|
+
'rt'] = fit_area['start_x']
|
|
690
|
+
edges['model']['fit_area_end'] = fit_area['start_x'] + fit_area['width_x']
|
|
691
|
+
return edges
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def xsec_xrpa(energy_scale, e0, z, beta, shift=0):
|
|
695
|
+
""" Calculate momentum-integrated cross-section for EELS
|
|
696
|
+
from X-ray photo-absorption cross-sections.
|
|
697
|
+
|
|
698
|
+
X-ray photo-absorption cross-sections from NIST.
|
|
699
|
+
Momentum-integrated cross-section for EELS according to
|
|
700
|
+
Egerton Ultramicroscopy 50 (1993) 13-28 equation (4)
|
|
701
|
+
|
|
702
|
+
Parameters
|
|
703
|
+
----------
|
|
704
|
+
energy_scale: numpy array
|
|
705
|
+
energy scale of spectrum to be analyzed
|
|
706
|
+
e0: float
|
|
707
|
+
acceleration voltage in keV
|
|
708
|
+
z: int
|
|
709
|
+
atomic number of element
|
|
710
|
+
beta: float
|
|
711
|
+
effective collection angle in mrad
|
|
712
|
+
shift: float
|
|
713
|
+
chemical shift of edge in eV
|
|
714
|
+
"""
|
|
715
|
+
beta = beta * 0.001 # collection half angle theta [rad]
|
|
716
|
+
# theta_max = self.parent.spec[0].convAngle * 0.001 # collection half angle theta [rad]
|
|
717
|
+
dispersion = energy_scale[1] - energy_scale[0]
|
|
718
|
+
|
|
719
|
+
x_sections = get_x_sections(z)
|
|
720
|
+
enexs = x_sections['ene']
|
|
721
|
+
datxs = x_sections['dat']
|
|
722
|
+
|
|
723
|
+
#####
|
|
724
|
+
# Cross Section according to Egerton Ultramicroscopy 50 (1993) 13-28 equation (4)
|
|
725
|
+
#####
|
|
726
|
+
|
|
727
|
+
# Relativistic correction factors
|
|
728
|
+
t = 511060.0 * (1.0 - 1.0 / (1.0 + e0 / 511.06) ** 2) / 2.0
|
|
729
|
+
gamma = 1 + e0 / 511.06
|
|
730
|
+
a = 6.5 # e-14 *10**14
|
|
731
|
+
b = beta
|
|
732
|
+
|
|
733
|
+
theta_e = enexs / (2 * gamma * t)
|
|
734
|
+
# ToDo: is there and error in the (gamma-1) factor at the las should be (1-1/gamma**2)
|
|
735
|
+
g = 2 * np.log(gamma) - np.log((b**2 + theta_e**2) / (b**2 + theta_e**2 / gamma**2)) - (
|
|
736
|
+
(1-1/gamma**2)) * b**2 / (b**2 + theta_e**2 / gamma**2)
|
|
737
|
+
datxs = datxs * (a / enexs / t) * (np.log(1 + b**2 / theta_e**2) + g) / 1e8
|
|
738
|
+
|
|
739
|
+
datxs = datxs * dispersion # from per eV to per dispersion
|
|
740
|
+
# coeff = splrep(enexs, datxs, s=0) # now in areal density atoms / m^2
|
|
741
|
+
xsec = np.zeros(len(energy_scale))
|
|
742
|
+
# shift = 0# int(ek -onsetXRPS)#/dispersion
|
|
743
|
+
# Linear instead of spline interpolation to avoid oscillations.
|
|
744
|
+
lin = scipy.interpolate.interp1d(enexs, datxs, kind='linear')
|
|
745
|
+
if energy_scale[0] < enexs[0]:
|
|
746
|
+
start = np.searchsorted(energy_scale, enexs[0])+1
|
|
747
|
+
else:
|
|
748
|
+
start = 0
|
|
749
|
+
xsec[start:] = lin(energy_scale[start:] - shift)
|
|
750
|
+
|
|
751
|
+
return xsec
|