pyTEMlib 0.2025.4.1__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 -915
- 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.1.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.1.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.1.dist-info/RECORD +0 -38
- pytemlib-0.2025.4.1.dist-info/top_level.txt +0 -1
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic Scattering Library for Multi-Slice Calculations
|
|
3
|
+
|
|
4
|
+
author: Gerd Duscher
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import scipy.constants
|
|
9
|
+
import scipy.special
|
|
10
|
+
|
|
11
|
+
import pyTEMlib.kinematic_scattering as ks # kinematic scattering Library
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def potential_1dim(element, r):
|
|
15
|
+
""" Calculates the projected potential of an atom of element
|
|
16
|
+
|
|
17
|
+
The projected potential will be in units of V nm^2,
|
|
18
|
+
however, internally we will use Angstrom instead of nm!
|
|
19
|
+
The basis for these calculations are the atomic form factors of Kirkland 2𝑛𝑑 edition
|
|
20
|
+
following the equation in Appendix C page 252.
|
|
21
|
+
|
|
22
|
+
Parameter
|
|
23
|
+
---------
|
|
24
|
+
element: str
|
|
25
|
+
name of 'element
|
|
26
|
+
r: numpy array [nxn]
|
|
27
|
+
impact parameters (distances from atom position) in nm
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
numpy array (nxn)
|
|
32
|
+
projected potential in units of V nm^2
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# get elementary constants
|
|
36
|
+
a0 = scipy.constants.value('Bohr radius') * 1e10 # in Angstrom
|
|
37
|
+
rydberg_div_e = scipy.constants.value('Rydberg constant times hc in eV') # in V
|
|
38
|
+
e0 = 2 * rydberg_div_e * scipy.constants.value('Bohr radius') * 1e10 # now in V A
|
|
39
|
+
|
|
40
|
+
pre_factor = 2 * np.pi ** 2 * a0 * e0
|
|
41
|
+
|
|
42
|
+
param = ks.electronFF[element] # parametrized form factors
|
|
43
|
+
f_lorentz = r * 0 # Lorentzian term
|
|
44
|
+
f_gauss = r * 0 # Gaussian term
|
|
45
|
+
for i in range(3):
|
|
46
|
+
f_lorentz += param['fa'][i] * scipy.special.k0(2 * np.pi * r * np.sqrt(param['fb'][i]))
|
|
47
|
+
f_gauss += param['fc'][i] / param['fd'][i] * np.exp(-np.pi ** 2 * r ** 2 / param['fd'][i])
|
|
48
|
+
f_lorentz[0, 0] = f_lorentz[0, 1]
|
|
49
|
+
# / 100 is conversion from V Angstrom^2 to V nm^2
|
|
50
|
+
return pre_factor * (2 * f_lorentz + f_gauss) # V Angstrom^2
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def potential_2dim(element, nx, ny, n_cell_x, n_cell_y, lattice_parameter, base):
|
|
54
|
+
"""Make a super-cell with potentials
|
|
55
|
+
|
|
56
|
+
Limitation is that we only place atom potential with single pixel resolution
|
|
57
|
+
"""
|
|
58
|
+
n_cell_x = int(2 ** np.log2(n_cell_x))
|
|
59
|
+
n_cell_y = int(2 ** np.log2(n_cell_y))
|
|
60
|
+
|
|
61
|
+
pixel_size = lattice_parameter / (nx / n_cell_x)
|
|
62
|
+
|
|
63
|
+
a_nx = a_ny = int(1 / pixel_size)
|
|
64
|
+
x, y = np.mgrid[0:a_nx, 0:a_ny] * pixel_size
|
|
65
|
+
a = int(nx / n_cell_x)
|
|
66
|
+
r = x ** 2 + y ** 2
|
|
67
|
+
|
|
68
|
+
atom_potential = potential_1dim(element, r)
|
|
69
|
+
|
|
70
|
+
potential = np.zeros([nx, ny])
|
|
71
|
+
|
|
72
|
+
atom_potential_corner = np.zeros([nx, ny])
|
|
73
|
+
atom_potential_corner[0:a_nx, 0:a_ny] = atom_potential
|
|
74
|
+
atom_potential_corner[nx - a_nx:, 0:a_ny] = np.flip(atom_potential, axis=0)
|
|
75
|
+
atom_potential_corner[0:a_nx, ny - a_ny:] = np.flip(atom_potential, axis=1)
|
|
76
|
+
atom_potential_corner[nx - a_nx:, ny - a_ny:] = np.flip(np.flip(atom_potential, axis=0), axis=1)
|
|
77
|
+
|
|
78
|
+
unit_cell_base = np.array(base) * a
|
|
79
|
+
unit_cell_base = np.array(unit_cell_base, dtype=int)
|
|
80
|
+
|
|
81
|
+
for pos in unit_cell_base:
|
|
82
|
+
potential = potential + np.roll(atom_potential_corner, shift=np.array(pos), axis=[0, 1])
|
|
83
|
+
|
|
84
|
+
for column in range(int(np.log2(n_cell_x))):
|
|
85
|
+
potential = potential + np.roll(potential, shift=2 ** column * a, axis=1)
|
|
86
|
+
for row in range(int(np.log2(n_cell_y))):
|
|
87
|
+
potential = potential + np.roll(potential, shift=2 ** row * a, axis=0)
|
|
88
|
+
|
|
89
|
+
return potential
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def interaction_parameter(acceleration_voltage):
|
|
93
|
+
"""Calculates interaction parameter sigma
|
|
94
|
+
|
|
95
|
+
Parameter
|
|
96
|
+
---------
|
|
97
|
+
acceleration_voltage: float
|
|
98
|
+
acceleration voltage in volt
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
interaction parameter: float
|
|
103
|
+
interaction parameter (dimensionless)
|
|
104
|
+
"""
|
|
105
|
+
e0 = 510998.95 # m_0 c^2 in eV
|
|
106
|
+
|
|
107
|
+
wavelength = ks.get_wavelength(acceleration_voltage)
|
|
108
|
+
e = acceleration_voltage
|
|
109
|
+
|
|
110
|
+
return 2. * np.pi / (wavelength * e) * (e0 + e) / (2. * e0 + e)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_transmission(potential, acceleration_voltage):
|
|
114
|
+
""" Get transmission function
|
|
115
|
+
|
|
116
|
+
has to be multiplied in real space with wave function
|
|
117
|
+
|
|
118
|
+
Parameter
|
|
119
|
+
---------
|
|
120
|
+
potential: numpy array (nxn)
|
|
121
|
+
potential of a layer
|
|
122
|
+
acceleration_voltage: float
|
|
123
|
+
acceleration voltage in V
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
complex numpy array (nxn)
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
sigma = interaction_parameter(acceleration_voltage)
|
|
131
|
+
|
|
132
|
+
return np.exp(1j * sigma * potential)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_propagator(size_in_pixel, delta_z, number_layers, wavelength, field_of_view, bandwidth_factor, verbose=True):
|
|
136
|
+
"""Get propagator function
|
|
137
|
+
|
|
138
|
+
has to be convoluted with wave function after transmission
|
|
139
|
+
|
|
140
|
+
Parameter
|
|
141
|
+
---------
|
|
142
|
+
size_in_pixel: int
|
|
143
|
+
number of pixels of one axis in square image
|
|
144
|
+
delta_z: float
|
|
145
|
+
distance between layers
|
|
146
|
+
number_layers: int
|
|
147
|
+
number of layers to make a propagator
|
|
148
|
+
wavelength: float
|
|
149
|
+
wavelength of incident electrons
|
|
150
|
+
field_of_view: float
|
|
151
|
+
field of view of image
|
|
152
|
+
bandwidth_factor: float
|
|
153
|
+
relative bandwidth to avoid anti-aliasing
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
propagator: complex numpy array (layers x size_in_pixel x size_in_pixel)
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
k2max = size_in_pixel / field_of_view / 2. * bandwidth_factor
|
|
162
|
+
print(k2max)
|
|
163
|
+
if verbose:
|
|
164
|
+
print(f"Bandwidth limited to a real space resolution of {1.0 / k2max * 1000} pm")
|
|
165
|
+
print(f" (= {wavelength * k2max * 1000.0:.2f} mrad) for symmetrical anti-aliasing.")
|
|
166
|
+
k2max = k2max * k2max
|
|
167
|
+
|
|
168
|
+
kx, ky = np.mgrid[-size_in_pixel / 2:size_in_pixel / 2, -size_in_pixel / 2:size_in_pixel / 2] / field_of_view
|
|
169
|
+
k_square = kx ** 2 + ky ** 2
|
|
170
|
+
k_square[k_square > k2max] = 0 # bandwidth limiting
|
|
171
|
+
|
|
172
|
+
if verbose:
|
|
173
|
+
temp = np.zeros([size_in_pixel, size_in_pixel])
|
|
174
|
+
temp[k_square > 0] = 1
|
|
175
|
+
print(f"Number of symmetrical non-aliasing beams = {temp.sum():.0f}")
|
|
176
|
+
|
|
177
|
+
propagator = np.zeros([number_layers, size_in_pixel, size_in_pixel], dtype=complex)
|
|
178
|
+
for i in range(number_layers):
|
|
179
|
+
propagator[i] = np.exp(-1j * np.pi * wavelength * k_square * delta_z[i])
|
|
180
|
+
|
|
181
|
+
return propagator
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def multi_slice(wave, number_of_unit_cell_z, number_layers, transmission, propagator):
|
|
185
|
+
"""Multi-Slice Calculation
|
|
186
|
+
|
|
187
|
+
The wave function will be changed iteratively
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
wave: complex numpy array (nxn)
|
|
192
|
+
starting wave function
|
|
193
|
+
number_of_unit_cell_z: int
|
|
194
|
+
this gives the thickness in multiples of c lattice parameter
|
|
195
|
+
number_layers: int
|
|
196
|
+
number of layers per unit cell
|
|
197
|
+
transmission: complex numpy array
|
|
198
|
+
transmission function
|
|
199
|
+
propagator: complex numpy array
|
|
200
|
+
propagator function
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
complex numpy array
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
for i in range(number_of_unit_cell_z):
|
|
208
|
+
for layer in range(number_layers):
|
|
209
|
+
wave = wave * transmission[layer] # transmission - real space
|
|
210
|
+
wave = np.fft.fft2(wave)
|
|
211
|
+
wave = wave * propagator[layer] # propagation; propagator is defined in reciprocal space
|
|
212
|
+
wave = np.fft.ifft2(wave) # back to real space
|
|
213
|
+
return wave
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def make_chi(theta, phi, aberrations):
|
|
217
|
+
"""
|
|
218
|
+
###
|
|
219
|
+
# Aberration function chi
|
|
220
|
+
###
|
|
221
|
+
phi and theta are meshgrids of the angles in polar coordinates.
|
|
222
|
+
aberrations is a dictionary with the aberrations coefficients
|
|
223
|
+
Attention: an empty aberration dictionary will give you a perfect aberration
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
chi = np.zeros(theta.shape)
|
|
227
|
+
for n in range(6): # First Sum up to fifth order
|
|
228
|
+
term_first_sum = np.power(theta, n + 1) / (n + 1) # term in first sum
|
|
229
|
+
|
|
230
|
+
second_sum = np.zeros(theta.shape) # second Sum initialized with zeros
|
|
231
|
+
for m in range((n + 1) % 2, n + 2, 2):
|
|
232
|
+
# print(n, m)
|
|
233
|
+
|
|
234
|
+
if m > 0:
|
|
235
|
+
if f'C{n}{m}a' not in aberrations: # Set non existent aberrations coefficient to zero
|
|
236
|
+
aberrations[f'C{n}{m}a'] = 0.
|
|
237
|
+
if f'C{n}{m}b' not in aberrations:
|
|
238
|
+
aberrations[f'C{n}{m}b'] = 0.
|
|
239
|
+
|
|
240
|
+
# term in second sum
|
|
241
|
+
second_sum = second_sum + aberrations[f'C{n}{m}a'] * np.cos(m * phi) + aberrations[
|
|
242
|
+
f'C{n}{m}b'] * np.sin(m * phi)
|
|
243
|
+
else:
|
|
244
|
+
if f'C{n}{m}' not in aberrations: # Set non existent aberrations coefficient to zero
|
|
245
|
+
aberrations[f'C{n}{m}'] = 0.
|
|
246
|
+
|
|
247
|
+
# term in second sum
|
|
248
|
+
second_sum = second_sum + aberrations[f'C{n}{m}']
|
|
249
|
+
chi = chi + term_first_sum * second_sum * 2 * np.pi / aberrations['wavelength']
|
|
250
|
+
|
|
251
|
+
return chi
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def objective_lens_function(ab, nx, ny, field_of_view, aperture_size=10):
|
|
255
|
+
"""Objective len function to be convoluted with exit wave to derive image function
|
|
256
|
+
|
|
257
|
+
Parameter:
|
|
258
|
+
----------
|
|
259
|
+
ab: dict
|
|
260
|
+
aberrations in nm should at least contain defocus (C10), and spherical aberration (C30)
|
|
261
|
+
nx: int
|
|
262
|
+
number of pixel in x direction
|
|
263
|
+
ny: int
|
|
264
|
+
number of pixel in y direction
|
|
265
|
+
field_of_view: float
|
|
266
|
+
field of view of potential
|
|
267
|
+
wavelength: float
|
|
268
|
+
wavelength in nm
|
|
269
|
+
aperture_size: float
|
|
270
|
+
aperture size in 1/nm
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
--------
|
|
274
|
+
object function: numpy array (nx x ny)
|
|
275
|
+
extent: list
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
wavelength = ab['wavelength']
|
|
279
|
+
# Reciprocal plane in 1/nm
|
|
280
|
+
dk = 1 / field_of_view
|
|
281
|
+
t_xv, t_yv = np.mgrid[int(-nx / 2):int(nx / 2), int(-ny / 2):int(ny / 2)] * dk
|
|
282
|
+
|
|
283
|
+
# define reciprocal plane in angles
|
|
284
|
+
phi = np.arctan2(t_yv, t_xv)
|
|
285
|
+
theta = np.arctan2(np.sqrt(t_xv ** 2 + t_yv ** 2), 1 / wavelength)
|
|
286
|
+
|
|
287
|
+
mask = theta < aperture_size * wavelength
|
|
288
|
+
|
|
289
|
+
# calculate chi
|
|
290
|
+
chi = make_chi(theta, phi, ab)
|
|
291
|
+
|
|
292
|
+
extent = [-nx / 2 * dk, nx / 2 * dk, -nx / 2 * dk, nx / 2 * dk]
|
|
293
|
+
return np.exp(-1j * chi) * mask, extent
|