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,655 @@
|
|
|
1
|
+
""" Part of eels_tools for pyTEMlib"""
|
|
2
|
+
from typing import Union
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import scipy
|
|
6
|
+
|
|
7
|
+
import sidpy
|
|
8
|
+
from sidpy.proc.fitter import SidFitter
|
|
9
|
+
|
|
10
|
+
from ..utilities import get_wave_length, effective_collection_angle
|
|
11
|
+
from ..utilities import gauss, lorentz
|
|
12
|
+
from .zero_loss_tools import zl
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def drude(energy_scale, peak_position, peak_width, gamma):
|
|
16
|
+
"""dielectric function according to Drude theory"""
|
|
17
|
+
|
|
18
|
+
eps = (1 - (peak_position ** 2 - peak_width * energy_scale * 1j) /
|
|
19
|
+
(energy_scale ** 2 + 2 * energy_scale * gamma * 1j)) # Mod drude term
|
|
20
|
+
return eps
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def drude_lorentz(eps_inf, leng, ep, eb, gamma, e, amplitude):
|
|
24
|
+
"""dielectric function according to Drude-Lorentz theory"""
|
|
25
|
+
eps = eps_inf
|
|
26
|
+
for i in range(leng):
|
|
27
|
+
eps = eps + amplitude[i] * (1 / (e + ep[i] + gamma[i]*1j) -
|
|
28
|
+
1 / (e - ep[i] + gamma[i]*1j))
|
|
29
|
+
return eps
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def energy_loss_function(energy: np.ndarray, p: np.ndarray, anglog=1) -> np.ndarray:
|
|
33
|
+
"""Energy loss function based on dielectric function."""
|
|
34
|
+
eps = 1 - p[0]**2/(energy**2+p[1]**2) + 1j * p[1] * p[0]**2/energy/(energy**2+p[1]**2)
|
|
35
|
+
elf = (-1/eps).imag
|
|
36
|
+
return elf*p[2]*anglog
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_plasmon_losses(energy, params):
|
|
40
|
+
""" Volume plasmons for spectrum images"""
|
|
41
|
+
dset = np.zeros((params.shape[0], params.shape[1], energy.shape[0]))
|
|
42
|
+
for x in range(params.shape[0]):
|
|
43
|
+
for y in range(params.shape[1]):
|
|
44
|
+
dset[x, y] += energy_loss_function(energy, params[x, y])
|
|
45
|
+
return dset
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fit_plasmon(dataset: Union[sidpy.Dataset, np.ndarray],
|
|
49
|
+
start_fit_energy: float, end_fit_energy: float,
|
|
50
|
+
number_workers: int = 4, number_threads: int = 8
|
|
51
|
+
) -> Union[sidpy.Dataset, np.ndarray]:
|
|
52
|
+
"""
|
|
53
|
+
Fit plasmon peak positions and widths in a TEM dataset using a Drude model.
|
|
54
|
+
|
|
55
|
+
This function applies the Drude model to fit plasmon peaks in a dataset obtained
|
|
56
|
+
from transmission electron microscopy (TEM). It processes the dataset to determine
|
|
57
|
+
peak positions, widths, and amplitudes within a specified energy range. The function
|
|
58
|
+
can handle datasets with different dimensions and offers parallel processing capabilities.
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
dataset: sidpy.Dataset or numpy.ndarray
|
|
62
|
+
The dataset containing TEM spectral data.
|
|
63
|
+
start_fit_energy: float
|
|
64
|
+
The start energy of the fitting window.
|
|
65
|
+
end_fit_energy: float
|
|
66
|
+
The end energy of the fitting window.
|
|
67
|
+
plot_result: bool, optional
|
|
68
|
+
If True, plots the fitting results (default is False).
|
|
69
|
+
number_workers: int, optional
|
|
70
|
+
The number of workers for parallel processing (default is 4).
|
|
71
|
+
number_threads: int, optional
|
|
72
|
+
The number of threads for parallel processing (default is 8).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
fitted_dataset: sidpy.Dataset or numpy.ndarray
|
|
76
|
+
The dataset with fitted plasmon peak parameters. The dimensions and
|
|
77
|
+
format depend on the input dataset.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If the input dataset does not have the expected dimensions or format.
|
|
81
|
+
|
|
82
|
+
Notes:
|
|
83
|
+
- The function uses the Drude model to fit plasmon peaks.
|
|
84
|
+
- The fitting parameters are peak position (e_p), peak width (e_w), and amplitude (A).
|
|
85
|
+
- If `plot_result` is True, the function plots e_p, e_w, and A as separate subplots.
|
|
86
|
+
"""
|
|
87
|
+
# define Drude function for plasmon fitting
|
|
88
|
+
|
|
89
|
+
anglog, _, _ = angle_correction(dataset)
|
|
90
|
+
|
|
91
|
+
def energy_loss_function2(e: np.ndarray, e_p: float,e_w: float,
|
|
92
|
+
amplitude: float) -> np.ndarray:
|
|
93
|
+
eps = 1 - e_p**2/(e**2+e_w**2) + 1j * e_w * e_p**2/e/(e**2+e_w**2)
|
|
94
|
+
elf = (-1/eps).imag
|
|
95
|
+
return amplitude*elf
|
|
96
|
+
|
|
97
|
+
# define window for fitting
|
|
98
|
+
energy = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
99
|
+
start_fit_pixel = np.searchsorted(energy, start_fit_energy)
|
|
100
|
+
end_fit_pixel = np.searchsorted(energy, end_fit_energy)
|
|
101
|
+
|
|
102
|
+
# rechunk dataset
|
|
103
|
+
if dataset.ndim == 3:
|
|
104
|
+
dataset = dataset.rechunk(chunks=(1, 1, -1))
|
|
105
|
+
fit_dset = dataset[:, :, start_fit_pixel:end_fit_pixel]
|
|
106
|
+
elif dataset.ndim == 2:
|
|
107
|
+
dataset = dataset.rechunk(chunks=(1, -1))
|
|
108
|
+
fit_dset = dataset[:, start_fit_pixel:end_fit_pixel]
|
|
109
|
+
else:
|
|
110
|
+
fit_dset = np.array(dataset[start_fit_pixel:end_fit_pixel]/ anglog[start_fit_pixel:end_fit_pixel])
|
|
111
|
+
guess_pos = np.argmax(fit_dset)
|
|
112
|
+
guess_amplitude = fit_dset[guess_pos]
|
|
113
|
+
guess_width =(end_fit_energy-start_fit_energy)/4
|
|
114
|
+
guess_pos = energy[start_fit_pixel+guess_pos]
|
|
115
|
+
|
|
116
|
+
if guess_width >8:
|
|
117
|
+
guess_width=8
|
|
118
|
+
try:
|
|
119
|
+
popt, _ = scipy.optimize.curve_fit(energy_loss_function2, energy[start_fit_pixel:end_fit_pixel], fit_dset,
|
|
120
|
+
p0=[guess_pos, guess_width, guess_amplitude])
|
|
121
|
+
except:
|
|
122
|
+
end_fit_pixel = np.searchsorted(energy, 30)
|
|
123
|
+
fit_dset = np.array(dataset[start_fit_pixel:end_fit_pixel]/ anglog[start_fit_pixel:end_fit_pixel])
|
|
124
|
+
try:
|
|
125
|
+
popt, _ = scipy.optimize.curve_fit(energy_loss_function,
|
|
126
|
+
energy[start_fit_pixel:end_fit_pixel], fit_dset,
|
|
127
|
+
p0=[guess_pos, guess_width, guess_amplitude])
|
|
128
|
+
except:
|
|
129
|
+
popt=[0,0,0]
|
|
130
|
+
|
|
131
|
+
plasmon = dataset.like_data(energy_loss_function2(energy, popt[0], popt[1], popt[2]))
|
|
132
|
+
plasmon *= anglog
|
|
133
|
+
start_plasmon = np.searchsorted(energy, 0)+1
|
|
134
|
+
plasmon[:start_plasmon] = 0.
|
|
135
|
+
|
|
136
|
+
epsilon = drude(energy, popt[0], popt[1], 1) * popt[2]
|
|
137
|
+
epsilon[:start_plasmon] = 0.
|
|
138
|
+
|
|
139
|
+
plasmon.metadata['plasmon'] = {'parameter': popt, 'epsilon':epsilon}
|
|
140
|
+
return plasmon
|
|
141
|
+
|
|
142
|
+
# if it can be parallelized:
|
|
143
|
+
fitter = SidFitter(fit_dset, energy_loss_function, num_workers=number_workers,
|
|
144
|
+
threads=number_threads, return_cov=False, return_fit=False, return_std=False,
|
|
145
|
+
km_guess=False, num_fit_parms=3)
|
|
146
|
+
[fit_parameter] = fitter.do_fit()
|
|
147
|
+
|
|
148
|
+
plasmon_dset = dataset * 0.0
|
|
149
|
+
fit_parameter = np.array(fit_parameter)
|
|
150
|
+
plasmon_dset += get_plasmon_losses(np.array(energy), fit_parameter)
|
|
151
|
+
if 'plasmon' not in plasmon_dset.metadata:
|
|
152
|
+
plasmon_dset.metadata['plasmon'] = {}
|
|
153
|
+
plasmon_dset.metadata['plasmon'].update({'start_fit_energy': start_fit_energy,
|
|
154
|
+
'end_fit_energy': end_fit_energy,
|
|
155
|
+
'fit_parameter': fit_parameter,
|
|
156
|
+
'original_low_loss': dataset.title})
|
|
157
|
+
|
|
158
|
+
return plasmon_dset
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def angle_correction(spectrum):
|
|
162
|
+
""" angle correction per energy loss"""
|
|
163
|
+
acceleration_voltage = spectrum.metadata['experiment']['acceleration_voltage']
|
|
164
|
+
energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
|
|
165
|
+
# eff_beta = effective_collection_angle(energy_scale,
|
|
166
|
+
# spectrum.metadata['experiment']['convergence_angle'],
|
|
167
|
+
# spectrum.metadata['experiment']['collection_angle'],
|
|
168
|
+
# acceleration_voltage)
|
|
169
|
+
|
|
170
|
+
epc = energy_scale.slope # input('ev per channel : ')
|
|
171
|
+
|
|
172
|
+
alpha = spectrum.metadata['experiment']['convergence_angle'] # input('Alpha (mrad) : ')
|
|
173
|
+
beta = spectrum.metadata['experiment']['collection_angle'] # input('Beta (mrad) : ')
|
|
174
|
+
e = energy_scale.values
|
|
175
|
+
e0 = acceleration_voltage/1000 # input('E0 (keV) : ')
|
|
176
|
+
|
|
177
|
+
t = 1000.0*e0*(1.+e0/1022.12)/(1.0+e0/511.06)**2 # %eV # equ.5.2a or Appendix E p 427
|
|
178
|
+
|
|
179
|
+
tgt=e0*(1.+e0/1022.)/(1+e0/511.)
|
|
180
|
+
thetae=(e+1e-6)/tgt # % avoid NaN for e=0
|
|
181
|
+
# % A2,B2,T2 ARE SQUARES OF ANGLES IN RADIANS**2
|
|
182
|
+
a2=alpha*alpha*1e-6 + 1e-7 # % avoid inf for alpha=0
|
|
183
|
+
b2=beta*beta*1e-6
|
|
184
|
+
t2=thetae*thetae*1e-6
|
|
185
|
+
eta1=np.sqrt((a2+b2+t2)**2-4*a2*b2)-a2-b2-t2
|
|
186
|
+
eta2=2.*b2*np.log(0.5/t2*(np.sqrt((a2+t2-b2)**2+4.*b2*t2)+a2+t2-b2))
|
|
187
|
+
eta3=2.*a2*np.log(0.5/t2*(np.sqrt((b2+t2-a2)**2+4.*a2*t2)+b2+t2-a2))
|
|
188
|
+
# eta=(eta1+eta2+eta3)/a2/np.log(4./t2)
|
|
189
|
+
f1=(eta1+eta2+eta3)/2./a2/np.log(1.+b2/t2)
|
|
190
|
+
f2=f1
|
|
191
|
+
if alpha/beta > 1:
|
|
192
|
+
f2=f1*a2/b2
|
|
193
|
+
|
|
194
|
+
bstar=thetae*np.sqrt(np.exp(f2*np.log(1.+b2/t2))-1.)
|
|
195
|
+
anglog = f2
|
|
196
|
+
"""
|
|
197
|
+
b = eff_beta/1000.0 # %rad
|
|
198
|
+
e0 = acceleration_voltage/1000.0 # %keV
|
|
199
|
+
t = 1000.0*e0*(1.+e0/1022.12)/(1.0+e0/511.06)**2 # %eV # equ.5.2a or Appendix E p 427
|
|
200
|
+
tgt = 1000*e0*(1022.12 + e0)/(511.06 + e0) # %eV Appendix E p 427
|
|
201
|
+
|
|
202
|
+
the = energy_scale/tgt # varies with energy loss! # Appendix E p 427
|
|
203
|
+
anglog = np.log(1.0+ b*b/the/the)
|
|
204
|
+
# 2 * t = m_0 v**2 !!! a_0 = 0.05292 nm epc is for sum over I0
|
|
205
|
+
"""
|
|
206
|
+
return anglog, (np.pi*0.05292* t / 2.0)/epc, bstar[0]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def inelastic_mean_free_path(e_p, spectrum):
|
|
210
|
+
""" inelastc mean free path"""
|
|
211
|
+
acceleration_voltage = spectrum.metadata['experiment']['acceleration_voltage']
|
|
212
|
+
energy_scale = spectrum.get_spectral_dims(return_axis=True)[0].values
|
|
213
|
+
|
|
214
|
+
e0 = acceleration_voltage/1000.0 # %keV
|
|
215
|
+
|
|
216
|
+
eff_beta = effective_collection_angle(energy_scale,
|
|
217
|
+
spectrum.metadata['experiment']['convergence_angle'],
|
|
218
|
+
spectrum.metadata['experiment']['collection_angle'],
|
|
219
|
+
acceleration_voltage)
|
|
220
|
+
beta = eff_beta/1000.0 # %rad
|
|
221
|
+
|
|
222
|
+
# %eV # equ.5.2a or Appendix E p 427
|
|
223
|
+
t = 1000.0*e0*(1.+e0/1022.12)/(1.0+e0/511.06)**2
|
|
224
|
+
# Appendix E p 427
|
|
225
|
+
tgt = 1000*e0*(1022.12 + e0)/(511.06 + e0) # %eV
|
|
226
|
+
# Appendix E p 427
|
|
227
|
+
theta_e = e_p/tgt # varies with energy loss!
|
|
228
|
+
|
|
229
|
+
# 2 * T = m_0 v**2 !!!
|
|
230
|
+
a_0 = 0.05292 # nm
|
|
231
|
+
imfp = 4 * t* a_0 / e_p / np.log(1 + beta**2/ theta_e**2)
|
|
232
|
+
return imfp, theta_e
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def multiple_scattering(energy_scale: np.ndarray, p: list, core_loss=False)-> np.ndarray:
|
|
236
|
+
"""Multiple scattering calculation based on plasmon peak fitting parameters."""
|
|
237
|
+
p = np.abs(p)
|
|
238
|
+
tmfp = p[3]
|
|
239
|
+
if core_loss:
|
|
240
|
+
dif = 1
|
|
241
|
+
else:
|
|
242
|
+
dif = 16
|
|
243
|
+
ll_energie = np.linspace(1, 2048-1,2048)/dif
|
|
244
|
+
|
|
245
|
+
ssd = energy_loss_function(ll_energie, p)
|
|
246
|
+
ssd = np.fft.fft(ssd)
|
|
247
|
+
ssd2 = ssd.copy()
|
|
248
|
+
|
|
249
|
+
### sum contribution from each order of scattering:
|
|
250
|
+
psd = np.zeros(len(ll_energie))
|
|
251
|
+
for order in range(15):
|
|
252
|
+
# This order convoluted spectrum
|
|
253
|
+
# convoluted ssd is SSD2
|
|
254
|
+
ssd2 = np.fft.ifft(ssd).real
|
|
255
|
+
|
|
256
|
+
# scale right (could be done better? GERD)
|
|
257
|
+
# And add this order to final spectrum
|
|
258
|
+
#using equation 4.1 of Egerton ed2
|
|
259
|
+
psd += ssd2*abs(sum(ssd)/sum(ssd2)) / scipy.special.factorial(order+1)*np.power(tmfp, (order+1))*np.exp(-tmfp)
|
|
260
|
+
|
|
261
|
+
# next order convolution
|
|
262
|
+
ssd = ssd * ssd2
|
|
263
|
+
|
|
264
|
+
psd /=tmfp*np.exp(-tmfp)
|
|
265
|
+
bgd_coef = scipy.interpolate.splrep(ll_energie, psd, s=0)
|
|
266
|
+
msd = scipy.interpolate.splev(energy_scale, bgd_coef)
|
|
267
|
+
start_plasmon = np.searchsorted(energy_scale, 0)+1
|
|
268
|
+
msd[:start_plasmon] = 0.0
|
|
269
|
+
return msd
|
|
270
|
+
|
|
271
|
+
def fit_multiple_scattering(dataset: Union[sidpy.Dataset, np.ndarray],
|
|
272
|
+
start_fit_energy: float, end_fit_energy: float, pin=None,
|
|
273
|
+
number_workers: int = 4, number_threads: int = 8
|
|
274
|
+
) -> Union[sidpy.Dataset, np.ndarray]:
|
|
275
|
+
"""
|
|
276
|
+
Fit multiple scattering of plasmon peak in a TEM dataset.
|
|
277
|
+
|
|
278
|
+
Parameters:
|
|
279
|
+
dataset: sidpy.Dataset or numpy.ndarray
|
|
280
|
+
The dataset containing TEM spectral data.
|
|
281
|
+
start_fit_energy: float
|
|
282
|
+
The start energy of the fitting window.
|
|
283
|
+
end_fit_energy: float
|
|
284
|
+
The end energy of the fitting window.
|
|
285
|
+
number_workers: int, optional
|
|
286
|
+
The number of workers for parallel processing (default is 4).
|
|
287
|
+
number_threads: int, optional
|
|
288
|
+
The number of threads for parallel processing (default is 8).
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
fitted_dataset: sidpy.Dataset or numpy.ndarray
|
|
292
|
+
The dataset with fitted plasmon peak parameters. The dimensions and
|
|
293
|
+
format depend on the input dataset.
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
ValueError: If the input dataset does not have the expected dimensions or format.
|
|
297
|
+
|
|
298
|
+
Notes:
|
|
299
|
+
- The function uses the Drude model to fit plasmon peaks.
|
|
300
|
+
- The fitting parameters are peak position (e_p), peak width (e_w), and amplitude (A).
|
|
301
|
+
- If `plot_result` is True, the function plots e_p, e_w, and A as separate subplots.
|
|
302
|
+
"""
|
|
303
|
+
# define window for fitting
|
|
304
|
+
energy = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
305
|
+
start_fit_pixel = np.searchsorted(energy, start_fit_energy)
|
|
306
|
+
end_fit_pixel = np.searchsorted(energy, end_fit_energy)
|
|
307
|
+
|
|
308
|
+
def errf_multi(p, y, x):
|
|
309
|
+
elf = multiple_scattering(x, p)
|
|
310
|
+
err = y - elf
|
|
311
|
+
return np.abs(err) # /np.sqrt(y)
|
|
312
|
+
|
|
313
|
+
if pin is None:
|
|
314
|
+
pin = np.array([9,1,.7, 0.3])
|
|
315
|
+
fit_dset = np.array(dataset[start_fit_pixel:end_fit_pixel])
|
|
316
|
+
popt, _ = scipy.optimize.leastsq(errf_multi, pin, args=(fit_dset,
|
|
317
|
+
energy[start_fit_pixel:end_fit_pixel]), maxfev=2000)
|
|
318
|
+
multi = dataset.like_data(multiple_scattering(energy, popt))
|
|
319
|
+
multi.metadata['multiple_scattering'] = {'parameter': popt}
|
|
320
|
+
return multi
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def drude_simulation(dset, e, ep, ew, tnm, eb):
|
|
324
|
+
"""probabilities of dielectric function eps relative to zero-loss integral (i0 = 1)
|
|
325
|
+
|
|
326
|
+
Gives probabilities of dielectric function eps relative to zero-loss integral (i0 = 1) per eV
|
|
327
|
+
Details in R.F.Egerton: EELS in the Electron Microscope, 3rd edition, Springer 2011
|
|
328
|
+
|
|
329
|
+
# Given the plasmon energy (ep), plasmon fwhm (ew) and binding energy(eb),
|
|
330
|
+
# this program generates:
|
|
331
|
+
# EPS1, EPS2 from modified Eq. (3.40), ELF=Im(-1/EPS) from Eq. (3.42),
|
|
332
|
+
# single scattering from Eq. (4.26) and SRFINT from Eq. (4.31)
|
|
333
|
+
# The output is e, ssd into the file drude.ssd (for use in Flog etc.)
|
|
334
|
+
# and e,eps1 ,eps2 into drude.eps (for use in Kroeger etc.)
|
|
335
|
+
# Gives probabilities relative to zero-loss integral (i0 = 1) per eV
|
|
336
|
+
# Details in R.F.Egerton: EELS in the Electron Microscope, 3rd edition, Springer 2011
|
|
337
|
+
# Version 10.11.26
|
|
338
|
+
|
|
339
|
+
"""
|
|
340
|
+
energy_scale = dset.get_spectral_dims(return_axis=True)[0].values
|
|
341
|
+
|
|
342
|
+
epc = energy_scale[1] - energy_scale[0] # input('ev per channel : ')
|
|
343
|
+
|
|
344
|
+
b = dset.metadata['collection_angle'] / 1000. # rad
|
|
345
|
+
epc = dset.energy_scale[1] - dset.energy_scale[0] # input('ev per channel : ');
|
|
346
|
+
e0 = dset.metadata['acceleration_voltage'] / 1000. # input('incident energy e0(kev) : ');
|
|
347
|
+
|
|
348
|
+
# effective kinetic energy: t = m_o v^2/2,
|
|
349
|
+
# eV # equ.5.2a or Appendix E p 427
|
|
350
|
+
t = 1000.0 * e0 * (1. + e0 / 1022.12) / (1.0 + e0 / 511.06)**2
|
|
351
|
+
|
|
352
|
+
# 2 gamma t
|
|
353
|
+
tgt = 1000 * e0 * (1022.12 + e0) / (511.06 + e0) # eV Appendix E p 427
|
|
354
|
+
|
|
355
|
+
rk0 = 2590 * (1.0 + e0 / 511.06) * np.sqrt(2.0 * t / 511060)
|
|
356
|
+
|
|
357
|
+
# os = e[0]
|
|
358
|
+
ew_mod = eb
|
|
359
|
+
tags = dset.metadata
|
|
360
|
+
|
|
361
|
+
eps = 1 - (ep ** 2 - ew_mod * e * 1j) / (e ** 2 + 2 * e * ew * 1j) # Mod drude term
|
|
362
|
+
|
|
363
|
+
eps[np.nonzero(eps == 0.0)] = 1e-19
|
|
364
|
+
elf = np.imag(-1 / eps)
|
|
365
|
+
|
|
366
|
+
the = e / tgt # varies with energy loss! # Appendix E p 427
|
|
367
|
+
# srfelf = 4..*eps2./((1+eps1).^2+eps2.^2) - elf; %equivalent
|
|
368
|
+
srfelf = np.imag(-4. / (1.0 + eps)) - elf # for 2 surfaces
|
|
369
|
+
angdep = np.arctan(b / the) / the - b / (b * b + the * the)
|
|
370
|
+
srfint = angdep * srfelf / (3.1416 * 0.05292 * rk0 * t) # probability per eV
|
|
371
|
+
anglog = np.log(1.0 + b * b / the / the)
|
|
372
|
+
i0 = dset.sum() # *tags['counts2e']
|
|
373
|
+
|
|
374
|
+
# 2 * t = m_0 v**2 !!! a_0 = 0.05292 nm
|
|
375
|
+
volint = abs(tnm / (np.pi * 0.05292 * t * 2.0) * elf * anglog) # S equ 4.26% probability per eV
|
|
376
|
+
volint = volint * i0 / epc # S probability per channel
|
|
377
|
+
ssd = volint # + srfint
|
|
378
|
+
|
|
379
|
+
if e[0] < -1.0:
|
|
380
|
+
xs = int(abs(-e[0] / epc))
|
|
381
|
+
|
|
382
|
+
ssd[0:xs] = 0.0
|
|
383
|
+
volint[0:xs] = 0.0
|
|
384
|
+
srfint[0:xs] = 0.0
|
|
385
|
+
|
|
386
|
+
# if os <0:
|
|
387
|
+
# 2 surfaces but includes negative Begrenzung contribution
|
|
388
|
+
# p_s = np.trapezoid(e, srfint)
|
|
389
|
+
|
|
390
|
+
# integrated volume probability
|
|
391
|
+
p_v = abs(np.trapezoid(e, abs(volint / tags['spec'].sum())))
|
|
392
|
+
# our data have he same epc and the trapez formula does not include
|
|
393
|
+
p_v = (volint / i0).sum()
|
|
394
|
+
# does NOT depend on free-electron approximation (no damping).
|
|
395
|
+
lam = tnm / p_v
|
|
396
|
+
# Eq.(3.44) approximation
|
|
397
|
+
lamfe = 4.0 * 0.05292 * t / ep / np.log(1 + (b * tgt / ep) ** 2)
|
|
398
|
+
|
|
399
|
+
tags['eps'] = eps
|
|
400
|
+
tags['lam'] = lam
|
|
401
|
+
tags['lamfe'] = lamfe
|
|
402
|
+
tags['p_v'] = p_v
|
|
403
|
+
|
|
404
|
+
return ssd # /np.pi
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def kroeger_core(e_data, a_data, eps_data, acceleration_voltage_kev, thickness, relativistic=True):
|
|
408
|
+
"""This function calculates the differential scattering probability
|
|
409
|
+
|
|
410
|
+
.. math::
|
|
411
|
+
\\frac{d^2P}{d \\Omega d_e}
|
|
412
|
+
of the low-loss region for total loss and volume plasmon loss
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
e_data (array): energy scale [eV]
|
|
416
|
+
a_data (array): angle or momentum range [rad]
|
|
417
|
+
eps_data (array) dielectric function
|
|
418
|
+
acceleration_voltage_kev (float): acceleration voltage [keV]
|
|
419
|
+
thickness (float): thickness in nm
|
|
420
|
+
relativistic (boolean): relativistic correction
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
P (numpy array 2d): total loss probability
|
|
424
|
+
p_vol (numpy array 2d): volume loss probability
|
|
425
|
+
|
|
426
|
+
return P, P*scale*1e2,p_vol*1e2, p_simple*1e2
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
$d^2P/(dEd\Omega) = \frac{1}{\pi^2 a_0 m_0 v^2} \Im \left[ \frac{t\mu^2}{\varepsilon \phi^2 } \right]
|
|
430
|
+
|
|
431
|
+
# Internally everything is calculated in SI units
|
|
432
|
+
# acceleration_voltage_kev = 200 #keV
|
|
433
|
+
# thick = 32.0*10-9 # m
|
|
434
|
+
"""
|
|
435
|
+
a_data = np.array(a_data)
|
|
436
|
+
e_data = np.array(e_data)
|
|
437
|
+
# adjust input to si units
|
|
438
|
+
# wavelength = get_wave_length(acceleration_voltage_kev * 1e3) # in m
|
|
439
|
+
thickness = thickness * 1e-9 # input thickness now in m
|
|
440
|
+
|
|
441
|
+
# Define constants
|
|
442
|
+
m_0 = scipy.constants.electron_mass # REST electron mass in kg
|
|
443
|
+
hbar = scipy.constants.hbar
|
|
444
|
+
|
|
445
|
+
c = scipy.constants.speed_of_light # speed of light m/s
|
|
446
|
+
bohr = scipy.constants.physical_constants['Bohr radius'][0] # Bohr radius in meters
|
|
447
|
+
e = scipy.constants.e # electron charge in Coulomb
|
|
448
|
+
|
|
449
|
+
# Calculate fixed terms of equation
|
|
450
|
+
# acceleration_voltage_kev is incident energy in keV
|
|
451
|
+
va = 1 - (511. / (511. + acceleration_voltage_kev))**2
|
|
452
|
+
v = c * np.sqrt(va)
|
|
453
|
+
|
|
454
|
+
if relativistic:
|
|
455
|
+
beta = v / c # non-relativistic for =1
|
|
456
|
+
gamma = 1. / np.sqrt(1 - beta ** 2)
|
|
457
|
+
else:
|
|
458
|
+
beta = 1
|
|
459
|
+
gamma = 1 # set = 1 to correspond to E+B & Siegle
|
|
460
|
+
|
|
461
|
+
momentum = m_0 * v * gamma # used for xya, E&B have no gamma
|
|
462
|
+
|
|
463
|
+
# ##### Define mapped variables
|
|
464
|
+
|
|
465
|
+
# Define independent variables E, theta
|
|
466
|
+
[energy, theta] = np.meshgrid(e_data + 1e-12, a_data)
|
|
467
|
+
# Define CONJUGATE dielectric function variable eps
|
|
468
|
+
[eps, _] = np.meshgrid(np.conj(eps_data), a_data)
|
|
469
|
+
|
|
470
|
+
# ##### Calculate lambda in equation EB 2.3
|
|
471
|
+
theta2 = theta ** 2 + 1e-15
|
|
472
|
+
|
|
473
|
+
theta_e = energy * e / momentum / v # critical angle
|
|
474
|
+
|
|
475
|
+
lambda2 = theta2 - eps * theta_e ** 2 * beta ** 2 # Eq 2.3
|
|
476
|
+
|
|
477
|
+
lambd = np.sqrt(lambda2)
|
|
478
|
+
if (np.real(lambd) < 0).any():
|
|
479
|
+
print(' error negative lambda')
|
|
480
|
+
|
|
481
|
+
# ##### Calculate lambda0 in equation EB 2.4
|
|
482
|
+
# According to Kröger real(lambda0) is defined as positive!
|
|
483
|
+
|
|
484
|
+
phi2 = lambda2 + theta_e ** 2 # Eq. 2.2
|
|
485
|
+
lambda02 = theta2 - theta_e ** 2 * beta ** 2 # eta=1 Eq 2.4
|
|
486
|
+
lambda02[lambda02 < 0] = 0
|
|
487
|
+
lambda0 = np.sqrt(lambda02)
|
|
488
|
+
if not (np.real(lambda0) >= 0).any():
|
|
489
|
+
print(' error negative lambda0')
|
|
490
|
+
|
|
491
|
+
de = thickness * energy * e / (2.0 * hbar * v) # Eq 2.5
|
|
492
|
+
xya = lambd * de / theta_e # used in Eqs 2.6, 2.7, 4.4
|
|
493
|
+
|
|
494
|
+
lplus = lambda0 * eps + lambd * np.tanh(xya) # eta=1 %Eq 2.6
|
|
495
|
+
lminus = lambda0 * eps + lambd / np.tanh(xya) # eta=1 %Eq 2.7
|
|
496
|
+
|
|
497
|
+
mue2 = 1 - (eps * beta ** 2) # Eq. 4.5
|
|
498
|
+
phi20 = lambda02 + theta_e ** 2 # Eq 4.6
|
|
499
|
+
phi201 = theta2 + theta_e ** 2 * (1 - (eps + 1) * beta ** 2) # eta=1, eps-1 in E+b Eq.(4.7)
|
|
500
|
+
|
|
501
|
+
# Eq 4.2
|
|
502
|
+
a1 = phi201 ** 2 / eps
|
|
503
|
+
a2 = np.sin(de) ** 2 / lplus + np.cos(de) ** 2 / lminus
|
|
504
|
+
a = a1 * a2
|
|
505
|
+
|
|
506
|
+
# Eq 4.3
|
|
507
|
+
b1 = beta ** 2 * lambda0 * theta_e * phi201
|
|
508
|
+
b2 = (1. / lplus - 1. / lminus) * np.sin(2. * de)
|
|
509
|
+
b = b1 * b2
|
|
510
|
+
|
|
511
|
+
# Eq 4.4
|
|
512
|
+
c1 = -beta ** 4 * lambda0 * lambd * theta_e ** 2
|
|
513
|
+
c2 = np.cos(de) ** 2 * np.tanh(xya) / lplus
|
|
514
|
+
c3 = np.sin(de) ** 2 / np.tanh(xya) / lminus
|
|
515
|
+
c = c1 * (c2 + c3)
|
|
516
|
+
|
|
517
|
+
# Put all the pieces together...
|
|
518
|
+
p_coef = e / (bohr * np.pi ** 2 * m_0 * v ** 2)
|
|
519
|
+
|
|
520
|
+
p_v = thickness * mue2 / eps / phi2
|
|
521
|
+
|
|
522
|
+
p_s1 = 2. * theta2 * (eps - 1) ** 2 / phi20 ** 2 / phi2 ** 2 # ASSUMES eta=1
|
|
523
|
+
p_s2 = hbar / momentum
|
|
524
|
+
p_s3 = a + b + c
|
|
525
|
+
|
|
526
|
+
p_s = p_s1 * p_s2 * p_s3
|
|
527
|
+
|
|
528
|
+
# print(p_v.min(),p_v.max(),p_s.min(),p_s.max())
|
|
529
|
+
# Calculate P and p_vol (volume only)
|
|
530
|
+
dtheta = a_data[1] - a_data[0]
|
|
531
|
+
scale = np.sin(np.abs(theta)) * dtheta * 2 * np.pi
|
|
532
|
+
|
|
533
|
+
p = p_coef * np.imag(p_v - p_s) # Eq 4.1
|
|
534
|
+
p_vol = p_coef * np.imag(p_v) * scale
|
|
535
|
+
|
|
536
|
+
# lplus_min = e_data[np.argmin(np.real(lplus), axis=1)]
|
|
537
|
+
# lminus_min = e_data[np.argmin(np.imag(lminus), axis=1)]
|
|
538
|
+
|
|
539
|
+
p_simple = p_coef * np.imag(1 / eps) * thickness / (theta2 + theta_e ** 2) * scale
|
|
540
|
+
# Watch it: eps is conjugated dielectric function
|
|
541
|
+
|
|
542
|
+
return p, p * scale * 1e2, p_vol * 1e2, p_simple * 1e2 # ,lplus_min,lminus_min
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def plot_dispersion(plotdata, units, a_data, e_data, max_p, title, ee):
|
|
546
|
+
"""Plot loss function """
|
|
547
|
+
|
|
548
|
+
# [x, y] = np.meshgrid(e_data + 1e-12, a_data[1024:2048] * 1000)
|
|
549
|
+
|
|
550
|
+
z = plotdata
|
|
551
|
+
# lev = np.array([0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 3, 4, 4.9]) * max_p / 5
|
|
552
|
+
|
|
553
|
+
wavelength = get_wave_length(ee)
|
|
554
|
+
# q = a_data[1024:2048] / (wavelength * 1e9) # in [1/nm]
|
|
555
|
+
scale = np.array([0, a_data[-1], e_data[0], e_data[-1]])
|
|
556
|
+
ev2hertz = scipy.constants.value('electron volt-hertz relationship')
|
|
557
|
+
|
|
558
|
+
if units[0] == 'mrad':
|
|
559
|
+
units[0] = 'scattering angle [mrad]'
|
|
560
|
+
scale[1] = scale[1] * 1000.
|
|
561
|
+
light_line = scipy.constants.c * a_data # for mrad
|
|
562
|
+
elif units[0] == '1/nm':
|
|
563
|
+
units[0] = 'scattering vector [1/nm]'
|
|
564
|
+
scale[1] = scale[1] / (wavelength * 1e9)
|
|
565
|
+
light_line = 1 / (scipy.constants.c / ev2hertz) * 1e-9
|
|
566
|
+
|
|
567
|
+
if units[1] == 'eV':
|
|
568
|
+
units[1] = 'energy loss [eV]'
|
|
569
|
+
|
|
570
|
+
if units[2] == 'ppm':
|
|
571
|
+
units[2] = 'probability [ppm]'
|
|
572
|
+
if units[2] == '1/eV':
|
|
573
|
+
units[2] = 'probability [eV$^{-1}$ srad$^{-1}$]'
|
|
574
|
+
|
|
575
|
+
# alpha = 3. / 5. * ef / ep
|
|
576
|
+
|
|
577
|
+
ax2 = plt.gca()
|
|
578
|
+
fig2 = plt.gcf()
|
|
579
|
+
fig2.suptitle(title)
|
|
580
|
+
im = ax2.imshow(z.t, clim=(0, max_p), origin='lower', aspect='auto', extent=scale)
|
|
581
|
+
# co = ax2.contour(y, x, z, levels=lev, colors='k', origin='lower')
|
|
582
|
+
# ,extent=(-ang*1000.,ang*1000.,e_data[0],e_data[-1]))#, vmin = p_vol.min(), vmax = 1000)
|
|
583
|
+
|
|
584
|
+
fig2.colorbar(im, ax=ax2, label=units[2])
|
|
585
|
+
|
|
586
|
+
ax2.plot(a_data, light_line, c='r', label='light line')
|
|
587
|
+
# ax2.plot(e_data*light_line*np.sqrt(np.real(eps_data)),e_data, color='steelblue',
|
|
588
|
+
# label='$\omega = c q \sqrt{\epsilon_2}$')
|
|
589
|
+
|
|
590
|
+
# ax2.plot(q, Ep_disp, c='r')
|
|
591
|
+
ax2.plot([11.5 * light_line, 0.12], [11.5, 11.5], c='r')
|
|
592
|
+
|
|
593
|
+
ax2.text(.05, 11.7, 'surface plasmon', color='r')
|
|
594
|
+
ax2.plot([0.0, 0.12], [16.8, 16.8], c='r')
|
|
595
|
+
ax2.text(.05, 17, 'volume plasmon', color='r')
|
|
596
|
+
ax2.set_xlim(0, scale[1])
|
|
597
|
+
ax2.set_ylim(0, 20)
|
|
598
|
+
# Interband transitions
|
|
599
|
+
ax2.plot([0.0, 0.25], [4.2, 4.2], c='g', label='interband transitions')
|
|
600
|
+
ax2.plot([0.0, 0.25], [5.2, 5.2], c='g')
|
|
601
|
+
ax2.set_ylabel(units[1])
|
|
602
|
+
ax2.set_xlabel(units[0])
|
|
603
|
+
ax2.legend(loc='lower right')
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def add_peaks(x, y, peaks, pin_in=None, peak_shape_in=None, shape='Gaussian'):
|
|
607
|
+
""" add peaks to fitting parameters"""
|
|
608
|
+
if pin_in is None:
|
|
609
|
+
return [], []
|
|
610
|
+
if peak_shape_in is None:
|
|
611
|
+
return [], []
|
|
612
|
+
pin = pin_in.copy()
|
|
613
|
+
|
|
614
|
+
peak_shape = peak_shape_in.copy()
|
|
615
|
+
if isinstance(shape, str): # if peak_shape is only a string make a list of it.
|
|
616
|
+
shape = [shape]
|
|
617
|
+
|
|
618
|
+
if len(shape) == 1:
|
|
619
|
+
shape = shape * len(peaks)
|
|
620
|
+
for i, peak in enumerate(peaks):
|
|
621
|
+
pin.append(x[peak])
|
|
622
|
+
pin.append(y[peak])
|
|
623
|
+
pin.append(.3)
|
|
624
|
+
peak_shape.append(shape[i])
|
|
625
|
+
return pin, peak_shape
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def model3(x, p, number_of_peaks, peak_shape, p_zl, pin=None, restrict_pos=0, restrict_width=0):
|
|
629
|
+
""" model for fitting low-loss spectrum"""
|
|
630
|
+
if pin is None:
|
|
631
|
+
pin = p
|
|
632
|
+
y = np.zeros(len(x))
|
|
633
|
+
|
|
634
|
+
for i in range(number_of_peaks):
|
|
635
|
+
index = int(i * 3)
|
|
636
|
+
if restrict_pos > 0:
|
|
637
|
+
if p[index] > pin[index] * (1.0 + restrict_pos):
|
|
638
|
+
p[index] = pin[index] * (1.0 + restrict_pos)
|
|
639
|
+
if p[index] < pin[index] * (1.0 - restrict_pos):
|
|
640
|
+
p[index] = pin[index] * (1.0 - restrict_pos)
|
|
641
|
+
|
|
642
|
+
p[index + 1] = abs(p[index + 1])
|
|
643
|
+
# print(p[index + 1])
|
|
644
|
+
p[index + 2] = abs(p[index + 2])
|
|
645
|
+
if restrict_width > 0:
|
|
646
|
+
p[index + 2] = pin[index + 2]
|
|
647
|
+
if p[index + 2] > pin[index + 2] * (1.0 + restrict_width):
|
|
648
|
+
p[index + 2] = pin[index + 2] * (1.0 + restrict_width)
|
|
649
|
+
if peak_shape[i] == 'Lorentzian':
|
|
650
|
+
y = y + lorentz(x, p[index:])
|
|
651
|
+
elif peak_shape[i] == 'zl':
|
|
652
|
+
y = y + zl(x, p[index:], p_zl)
|
|
653
|
+
else:
|
|
654
|
+
y = y + gauss(x, p[index:])
|
|
655
|
+
return y
|