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,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
peak_fit-tools of eels_tools
|
|
3
|
+
Model based quantification of electron energy-loss data
|
|
4
|
+
Copyright by Gerd Duscher
|
|
5
|
+
|
|
6
|
+
The University of Tennessee, Knoxville
|
|
7
|
+
Department of Materials Science & Engineering
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Units:
|
|
11
|
+
everything is in SI units, except length is given in nm and angles in mrad.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
See the notebooks for examples of these routines
|
|
15
|
+
|
|
16
|
+
All the input and output is done through a dictionary which is to be found in the meta_data
|
|
17
|
+
attribute of the sidpy.Dataset
|
|
18
|
+
"""
|
|
19
|
+
import numpy as np
|
|
20
|
+
from numba import jit
|
|
21
|
+
|
|
22
|
+
import scipy
|
|
23
|
+
import sidpy
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ###############################################################
|
|
27
|
+
# Peak Fit Functions
|
|
28
|
+
# ################################################################
|
|
29
|
+
|
|
30
|
+
def residuals_smooth(p: np.ndarray,
|
|
31
|
+
x: np.ndarray,
|
|
32
|
+
y: np.ndarray,
|
|
33
|
+
only_positive_intensity: bool) -> np.ndarray:
|
|
34
|
+
"""part of fit"""
|
|
35
|
+
err = y - model_smooth(x, p, only_positive_intensity)
|
|
36
|
+
return err
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def model_smooth(x: np.ndarray,
|
|
40
|
+
p: np.ndarray,
|
|
41
|
+
only_positive_intensity: bool = False) -> np.ndarray:
|
|
42
|
+
"""part of fit"""
|
|
43
|
+
|
|
44
|
+
y = np.zeros(len(x))
|
|
45
|
+
number_of_peaks = int(len(p) / 3)
|
|
46
|
+
for i in range(number_of_peaks):
|
|
47
|
+
if only_positive_intensity:
|
|
48
|
+
p[i * 3 + 1] = abs(p[i * 3 + 1])
|
|
49
|
+
p[i * 3 + 2] = abs(p[i * 3 + 2])
|
|
50
|
+
if p[i * 3 + 2] > abs(p[i * 3]) * 4.29193 / 2.0:
|
|
51
|
+
# width cannot extend beyond zero, maximum is FWTM/2
|
|
52
|
+
p[i * 3 + 2] = abs(p[i * 3]) * 4.29193 / 2.0
|
|
53
|
+
y = y + gauss(x, p[i * 3:])
|
|
54
|
+
return y
|
|
55
|
+
|
|
56
|
+
# ###############################################################
|
|
57
|
+
# Gaussian Mixing Model Functions
|
|
58
|
+
# ################################################################
|
|
59
|
+
|
|
60
|
+
@jit
|
|
61
|
+
def gauss(x, p):
|
|
62
|
+
"""Gaussian Function
|
|
63
|
+
|
|
64
|
+
p[0]==mean, p[1]= amplitude p[2]==fwhm
|
|
65
|
+
area = np.sqrt(2* np.pi)* p[1] * np.abs(p[2] / 2.3548)
|
|
66
|
+
FWHM = 2 * np.sqrt(2 np.log(2)) * sigma = 2.3548 * sigma
|
|
67
|
+
sigma = FWHM/3548
|
|
68
|
+
"""
|
|
69
|
+
if p[2] == 0:
|
|
70
|
+
return x * 0.
|
|
71
|
+
return p[1] * np.exp(-(x - p[0])**2 / (2.0 * (p[2] / 2.3548)**2))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@jit
|
|
75
|
+
def gmm(x, p):
|
|
76
|
+
"""Gaussian Mixture Model"""
|
|
77
|
+
y = np.zeros(len(x))
|
|
78
|
+
number_of_peaks= int(len(p)/3)
|
|
79
|
+
for i in range(number_of_peaks):
|
|
80
|
+
index = i*3
|
|
81
|
+
p[index + 1] = p[index + 1]
|
|
82
|
+
# print(p[index + 1])
|
|
83
|
+
p[index + 2] = abs(p[index + 2])
|
|
84
|
+
y = y + gauss(x, p[index:index+3])
|
|
85
|
+
return y
|
|
86
|
+
|
|
87
|
+
@jit
|
|
88
|
+
def residuals3(pp, xx, yy):
|
|
89
|
+
"""Residuals for Gaussian Mixture Model"""
|
|
90
|
+
err = yy - gmm(xx, pp)
|
|
91
|
+
return err
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def find_maxima(y, number_of_peaks):
|
|
95
|
+
""" find the first most prominent peaks
|
|
96
|
+
|
|
97
|
+
peaks are then sorted by energy
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
y: numpy array
|
|
102
|
+
(part) of spectrum
|
|
103
|
+
number_of_peaks: int
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
numpy array
|
|
108
|
+
indices of peaks
|
|
109
|
+
"""
|
|
110
|
+
blurred2 = scipy.ndimage.gaussian_filter(y, sigma=2)
|
|
111
|
+
peaks, _ = scipy.signal.find_peaks(blurred2)
|
|
112
|
+
prominences = scipy.signal.peak_prominences(blurred2, peaks)[0]
|
|
113
|
+
prominences_sorted = np.argsort(prominences)
|
|
114
|
+
peaks = peaks[prominences_sorted[-number_of_peaks:]]
|
|
115
|
+
|
|
116
|
+
peak_indices = np.argsort(peaks)
|
|
117
|
+
return peaks[peak_indices]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def find_peaks(dataset, energy_scale): #, fit_start, fit_end, sensitivity=2):
|
|
121
|
+
"""find peaks in spectrum"""
|
|
122
|
+
|
|
123
|
+
peaks, _ = scipy.signal.find_peaks(np.abs(dataset)+1, width=5)
|
|
124
|
+
results_half = scipy.signal.peak_widths(np.abs(dataset)+1, peaks, rel_height=0.5)[0]
|
|
125
|
+
disp = energy_scale[1] - energy_scale[0]
|
|
126
|
+
p_in = []
|
|
127
|
+
if len(peaks) > 0:
|
|
128
|
+
p_in = np.ravel([[energy_scale[peaks[i]], dataset[peaks[i]],
|
|
129
|
+
results_half[i]*disp] for i in range(len(peaks))])
|
|
130
|
+
return p_in # model, p_in
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def gaussian_mixture_model(dataset, p_in=None):
|
|
134
|
+
"""Fit a Gaussian mixture model to a spectrum or a spectrum image"""
|
|
135
|
+
peak_model = None
|
|
136
|
+
if isinstance(dataset, sidpy.Dataset):
|
|
137
|
+
if dataset.data_type.name == 'SPECTRAL_IMAGE':
|
|
138
|
+
if hasattr(dataset.view, 'get_spectrum'):
|
|
139
|
+
spectrum = dataset.view.get_spectrum()
|
|
140
|
+
else:
|
|
141
|
+
spectrum = dataset[0,0]
|
|
142
|
+
spectrum.data_type = 'SPECTRUM'
|
|
143
|
+
else:
|
|
144
|
+
spectrum = dataset
|
|
145
|
+
spectrum.data_type = 'SPECTRUM'
|
|
146
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
147
|
+
else:
|
|
148
|
+
spectrum = np.array(dataset)
|
|
149
|
+
energy_scale = np.arange(len(spectrum))
|
|
150
|
+
spectrum = np.array(spectrum)
|
|
151
|
+
#spectrum -= np.min(spectrum)-1
|
|
152
|
+
if p_in is None:
|
|
153
|
+
p_in = find_peaks(spectrum, energy_scale)
|
|
154
|
+
|
|
155
|
+
p = fit_gmm(energy_scale, np.array(spectrum), list(p_in))
|
|
156
|
+
peak_model = gmm(energy_scale, p)
|
|
157
|
+
return peak_model, p
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def fit_gmm(x, y, pin):
|
|
161
|
+
"""fit a Gaussian mixture model to a spectrum"""
|
|
162
|
+
[p, _] = scipy.optimize.leastsq(residuals3, pin, args=(x, y),maxfev = 10000)
|
|
163
|
+
return p
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def sort_peaks(p, peak_shape):
|
|
167
|
+
"""sort fitting parameters by peak position"""
|
|
168
|
+
number_of_peaks = int(len(p) / 3)
|
|
169
|
+
p3 = np.reshape(p, (number_of_peaks, 3))
|
|
170
|
+
sort_pin = np.argsort(p3[:, 0])
|
|
171
|
+
|
|
172
|
+
p = p3[sort_pin].flatten()
|
|
173
|
+
peak_shape = np.array(peak_shape)[sort_pin].tolist()
|
|
174
|
+
|
|
175
|
+
return p, peak_shape
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
zero-loss tools part of eels tools in pyTEMlib
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy
|
|
7
|
+
|
|
8
|
+
import sidpy
|
|
9
|
+
from sidpy.proc.fitter import SidFitter
|
|
10
|
+
|
|
11
|
+
from ..utilities import lorentz, gauss
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def zero_loss_function(x, p):
|
|
15
|
+
""" zero-loss function as product of two lorentzians """
|
|
16
|
+
return zl_func(x, *p)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def zl_func(x, center1, amplitude1, width1, center2, amplitude2, width2):
|
|
20
|
+
""" zero loss function as product of two lorentzians """
|
|
21
|
+
zero_loss = lorentz(x, center1, amplitude1, width1) * lorentz(x, center2, amplitude2, width2)
|
|
22
|
+
return zero_loss
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def zl(x, p, p_zl):
|
|
26
|
+
"""zero-loss function"""
|
|
27
|
+
p_zl_local = p_zl.copy()
|
|
28
|
+
p_zl_local[2] += p[0]
|
|
29
|
+
p_zl_local[5] += p[0]
|
|
30
|
+
zero_loss = zero_loss_function(x, p_zl_local)
|
|
31
|
+
return p[1] * zero_loss / zero_loss.max()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_channel_zero(spectrum: np.ndarray, energy: np.ndarray, width: int = 8):
|
|
35
|
+
"""Determine shift of energy scale according to zero-loss peak position
|
|
36
|
+
|
|
37
|
+
This function assumes that the zero loss peak is the maximum of the spectrum.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
zero = scipy.signal.find_peaks(spectrum/np.max(spectrum), height=0.98)[0][0]
|
|
41
|
+
width = int(width/2)
|
|
42
|
+
x = np.array(energy[int(zero-width):int(zero+width)])
|
|
43
|
+
y = np.array(spectrum[int(zero-width):int(zero+width)]).copy()
|
|
44
|
+
|
|
45
|
+
y[np.nonzero(y <= 0)] = 1e-12
|
|
46
|
+
|
|
47
|
+
p0 = [energy[zero], spectrum.max(), .5] # Initial guess is a normal distribution
|
|
48
|
+
|
|
49
|
+
def errfunc(pp, xx, yy):
|
|
50
|
+
return (gauss(xx, pp) - yy) / np.sqrt(yy) # Distance to the target function
|
|
51
|
+
|
|
52
|
+
[p1, _] = scipy.optimize.leastsq(errfunc, np.array(p0[:]), args=(x, y))
|
|
53
|
+
fit_mu, _, fwhm = p1
|
|
54
|
+
|
|
55
|
+
return fwhm, fit_mu
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_zero_loss_energy(dataset: sidpy.Dataset) -> np.ndarray:
|
|
59
|
+
""" Determine zero-loss peaks of EELS spectral sidpy dataset """
|
|
60
|
+
spectrum = dataset.sum(axis=tuple(range(dataset.ndim - 1)))
|
|
61
|
+
startx = scipy.signal.find_peaks(spectrum/np.max(spectrum), height=0.98)[0][0]
|
|
62
|
+
|
|
63
|
+
end = startx + 3
|
|
64
|
+
start = startx - 3
|
|
65
|
+
for i in range(10):
|
|
66
|
+
if spectrum[startx - i] < 0.3 * spectrum[startx]:
|
|
67
|
+
start = startx - i
|
|
68
|
+
if spectrum[startx + i] < 0.3 * spectrum[startx]:
|
|
69
|
+
end = startx + i
|
|
70
|
+
if end - start < 7:
|
|
71
|
+
end = startx + 4
|
|
72
|
+
start = startx - 4
|
|
73
|
+
width = int((end-start)/2+0.5)
|
|
74
|
+
|
|
75
|
+
energy = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
76
|
+
|
|
77
|
+
if dataset.ndim == 1: # single spectrum
|
|
78
|
+
_, shifts = get_channel_zero(np.array(dataset), energy, width)
|
|
79
|
+
shifts = np.array([shifts])
|
|
80
|
+
elif dataset.ndim == 2: # line scan
|
|
81
|
+
shifts = np.zeros(dataset.shape[:1])
|
|
82
|
+
for x in range(dataset.shape[0]):
|
|
83
|
+
_, shifts[x] = get_channel_zero(dataset[x, :], energy, width)
|
|
84
|
+
elif dataset.ndim == 3: # spectral image
|
|
85
|
+
shifts = np.zeros(dataset.shape[:2])
|
|
86
|
+
for x in range(dataset.shape[0]):
|
|
87
|
+
for y in range(dataset.shape[1]):
|
|
88
|
+
_, shifts[x, y] = get_channel_zero(dataset[x, y, :], energy, width)
|
|
89
|
+
return shifts
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def shift_energy(dataset: sidpy.Dataset, shifts: np.ndarray) -> sidpy.Dataset:
|
|
93
|
+
""" Align zero-loss peaks of any spectral sidpy dataset """
|
|
94
|
+
|
|
95
|
+
new_si = dataset.copy()
|
|
96
|
+
new_si *= 0.0
|
|
97
|
+
|
|
98
|
+
image_dims = dataset.get_image_dims()
|
|
99
|
+
if len(image_dims) == 0:
|
|
100
|
+
image_dims =[0]
|
|
101
|
+
if len(image_dims) != shifts.ndim:
|
|
102
|
+
raise TypeError('array of energy shifts have to have same dimension as dataset')
|
|
103
|
+
if not isinstance(dataset, sidpy.Dataset):
|
|
104
|
+
raise TypeError('This function needs a sidpy Dataset to shift energy scale')
|
|
105
|
+
energy_scale = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
106
|
+
if dataset.ndim == 1: # single spectrum
|
|
107
|
+
tck = scipy.interpolate.splrep(np.array(energy_scale - shifts), np.array(dataset), k=1, s=0)
|
|
108
|
+
new_si[:] = scipy.interpolate.splev(energy_scale, tck, der=0)
|
|
109
|
+
new_si.data_type = 'Spectrum'
|
|
110
|
+
elif dataset.ndim == 2: # line scan
|
|
111
|
+
for x in range(dataset.shape[0]):
|
|
112
|
+
tck = scipy.interpolate.splrep(np.array(energy_scale - shifts[x]),
|
|
113
|
+
np.array(dataset[x, :]), k=1, s=0)
|
|
114
|
+
new_si[x, :] = scipy.interpolate.splev(energy_scale, tck, der=0)
|
|
115
|
+
elif dataset.ndim == 3: # spectral image
|
|
116
|
+
for x in range(dataset.shape[0]):
|
|
117
|
+
for y in range(dataset.shape[1]):
|
|
118
|
+
tck = scipy.interpolate.splrep(np.array(energy_scale - shifts[x, y]),
|
|
119
|
+
np.array(dataset[x, y]), k=1, s=0)
|
|
120
|
+
new_si[x, y, :] = scipy.interpolate.splev(energy_scale, tck, der=0)
|
|
121
|
+
|
|
122
|
+
return new_si
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def align_zero_loss(dataset: sidpy.Dataset) -> sidpy.Dataset:
|
|
126
|
+
"""
|
|
127
|
+
Shifts the energy axis of the input dataset to be aligned with the zero-loss peak.
|
|
128
|
+
|
|
129
|
+
Parameters:
|
|
130
|
+
-----------
|
|
131
|
+
dataset : sidpy.Dataset
|
|
132
|
+
The input dataset containing the energy axis to be aligned.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
--------
|
|
136
|
+
sidpy.Dataset
|
|
137
|
+
The dataset with the energy axis shifted to align the zero-loss peak.
|
|
138
|
+
"""
|
|
139
|
+
shifts = get_zero_loss_energy(dataset)
|
|
140
|
+
# print(shifts, dataset)
|
|
141
|
+
new_si = shift_energy(dataset, shifts)
|
|
142
|
+
new_si.metadata.update({'zero_loss': {'shifted': shifts}})
|
|
143
|
+
return new_si
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_zero_losses(energy, z_loss_params):
|
|
147
|
+
"""Calculate zero-loss peaks for a given energy range and parameters."""
|
|
148
|
+
z_loss_dset = np.zeros((z_loss_params.shape[0], z_loss_params.shape[1], energy.shape[0]))
|
|
149
|
+
for x in range(z_loss_params.shape[0]):
|
|
150
|
+
for y in range(z_loss_params.shape[1]):
|
|
151
|
+
z_loss_dset[x, y] += zl_func(energy, *z_loss_params[x, y])
|
|
152
|
+
return z_loss_dset
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_resolution_functions(dataset: sidpy.Dataset, start_fit_energy: float=-1,
|
|
158
|
+
end_fit_energy: float=+1,
|
|
159
|
+
n_workers: int=1, n_threads: int=8):
|
|
160
|
+
"""
|
|
161
|
+
Analyze and fit low-loss EELS data within a specified energy range to determine zero-loss peaks.
|
|
162
|
+
|
|
163
|
+
This function processes a low-loss EELS dataset from transmission electron microscopy
|
|
164
|
+
(TEM) data, focusing on a specified energy range for analyzing and fitting the spectrum.
|
|
165
|
+
It determines fitting parameters and applies these to extract zero-loss peak information
|
|
166
|
+
from the dataset. The function handles both 2D and 3D datasets.
|
|
167
|
+
|
|
168
|
+
Parameters:
|
|
169
|
+
-----------
|
|
170
|
+
dataset (sidpy.Dataset): The dataset containing TEM spectral data.
|
|
171
|
+
start_fit_energy (float): The start energy of the fitting window.
|
|
172
|
+
end_fit_energy (float): The end energy of the fitting window.
|
|
173
|
+
n_workers (int, optional): The number of workers for parallel processing (default is 1).
|
|
174
|
+
n_threads (int, optional): The number of threads for parallel processing (default is 8).
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
--------
|
|
178
|
+
tuple: A tuple containing:
|
|
179
|
+
- z_loss_dset (sidpy.Dataset): The dataset with added zero-loss peak information.
|
|
180
|
+
- z_loss_params (numpy.ndarray): Array of parameters used
|
|
181
|
+
for the zero-loss peak fitting.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
-------
|
|
185
|
+
ValueError: If the input dataset does not have the expected dimensions or format.
|
|
186
|
+
|
|
187
|
+
Notes:
|
|
188
|
+
------
|
|
189
|
+
- The function expects `dset` to have specific dimensionalities and will raise an error
|
|
190
|
+
if they are not met.
|
|
191
|
+
- Parallel processing is employed to enhance performance, particularly for large datasets.
|
|
192
|
+
"""
|
|
193
|
+
energy = dataset.get_spectral_dims(return_axis=True)[0].values
|
|
194
|
+
start_fit_pixel = np.searchsorted(energy, start_fit_energy)
|
|
195
|
+
end_fit_pixel = np.searchsorted(energy, end_fit_energy)
|
|
196
|
+
guess_width = (end_fit_pixel - start_fit_pixel)/2
|
|
197
|
+
if end_fit_pixel - start_fit_pixel < 5:
|
|
198
|
+
start_fit_pixel -= 2
|
|
199
|
+
end_fit_pixel += 2
|
|
200
|
+
|
|
201
|
+
def get_good_guess(zl_peak, energy, spectrum):
|
|
202
|
+
popt, _ = scipy.optimize.curve_fit(zl_peak, energy, spectrum,
|
|
203
|
+
p0=[0, guess_amplitude, guess_width,
|
|
204
|
+
0, guess_amplitude, guess_width])
|
|
205
|
+
return popt
|
|
206
|
+
|
|
207
|
+
fit_energy = energy[start_fit_pixel:end_fit_pixel]
|
|
208
|
+
# get a good guess for the fit parameters
|
|
209
|
+
if len(dataset.shape) == 3:
|
|
210
|
+
fit_dset = dataset[:, :, start_fit_pixel:end_fit_pixel]
|
|
211
|
+
guess_amplitude = np.sqrt(fit_dset.max())
|
|
212
|
+
image_size = fit_dset.shape[0]/fit_dset.shape[1]
|
|
213
|
+
guess_params = get_good_guess(zl_func, fit_energy,
|
|
214
|
+
fit_dset.sum(axis=(0, 1))/image_size)
|
|
215
|
+
elif len(dataset.shape) == 2:
|
|
216
|
+
fit_dset = dataset[:, start_fit_pixel:end_fit_pixel]
|
|
217
|
+
fit_energy = energy[start_fit_pixel:end_fit_pixel]
|
|
218
|
+
guess_amplitude = np.sqrt(fit_dset.max())
|
|
219
|
+
guess_params = get_good_guess(zl_func, fit_energy,
|
|
220
|
+
fit_dset.sum(axis=0)/fit_dset.shape[0])
|
|
221
|
+
elif len(dataset.shape) == 1:
|
|
222
|
+
fit_dset = dataset[start_fit_pixel:end_fit_pixel]
|
|
223
|
+
fit_energy = energy[start_fit_pixel:end_fit_pixel]
|
|
224
|
+
guess_amplitude = np.sqrt(fit_dset.max())
|
|
225
|
+
guess_params = get_good_guess(zl_func, fit_energy, fit_dset)
|
|
226
|
+
z_loss_dset = dataset.copy()
|
|
227
|
+
z_loss_dset *= 0.0
|
|
228
|
+
z_loss_dset += zl_func(energy, *guess_params)
|
|
229
|
+
if 'zero_loss' not in z_loss_dset.metadata:
|
|
230
|
+
z_loss_dset.metadata['zero_loss'] = {}
|
|
231
|
+
tags = {'start_fit_energy': start_fit_energy,
|
|
232
|
+
'end_fit_energy': end_fit_energy,
|
|
233
|
+
'fit_parameter': guess_params,
|
|
234
|
+
'original_low_loss': dataset.title}
|
|
235
|
+
z_loss_dset.metadata['zero_loss'].update(tags)
|
|
236
|
+
return z_loss_dset
|
|
237
|
+
else:
|
|
238
|
+
print('Error: need a spectrum or spectral image sidpy dataset')
|
|
239
|
+
print('Not dset.shape = ', dataset.shape)
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
# define guess function for SidFitter
|
|
243
|
+
def guess_function(xvec, yvec):
|
|
244
|
+
return guess_params
|
|
245
|
+
|
|
246
|
+
# apply to all spectra
|
|
247
|
+
zero_loss_fitter = SidFitter(fit_dset, zl_func, num_workers=n_workers,
|
|
248
|
+
guess_fn=guess_function, threads=n_threads,
|
|
249
|
+
return_cov=False, return_fit=False,
|
|
250
|
+
return_std=False, km_guess=False, num_fit_parms=6)
|
|
251
|
+
[z_loss_params] = zero_loss_fitter.do_fit()
|
|
252
|
+
z_loss_dset = dataset.copy()
|
|
253
|
+
z_loss_dset *= 0.0
|
|
254
|
+
z_loss_params = np.array(z_loss_params)
|
|
255
|
+
z_loss_dset += get_zero_losses(np.array(energy), np.array(z_loss_params))
|
|
256
|
+
|
|
257
|
+
# shifts = z_loss_params[:, :, 0] * z_loss_params[:, :, 3]
|
|
258
|
+
# widths = z_loss_params[:, :, 2] * z_loss_params[:, :, 5]
|
|
259
|
+
tags = {'start_fit_energy': start_fit_energy,
|
|
260
|
+
'end_fit_energy': end_fit_energy,
|
|
261
|
+
'fit_parameter': z_loss_params,
|
|
262
|
+
'original_low_loss': dataset.title}
|
|
263
|
+
z_loss_dset.metadata['zero_loss'].update(tags)
|
|
264
|
+
return z_loss_dset
|