pyTEMlib 0.2020.11.0__py3-none-any.whl → 0.2024.8.4__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.

Files changed (59) hide show
  1. pyTEMlib/__init__.py +11 -11
  2. pyTEMlib/animation.py +631 -0
  3. pyTEMlib/atom_tools.py +240 -222
  4. pyTEMlib/config_dir.py +57 -29
  5. pyTEMlib/core_loss_widget.py +658 -0
  6. pyTEMlib/crystal_tools.py +1255 -0
  7. pyTEMlib/diffraction_plot.py +756 -0
  8. pyTEMlib/dynamic_scattering.py +293 -0
  9. pyTEMlib/eds_tools.py +609 -0
  10. pyTEMlib/eels_dialog.py +749 -486
  11. pyTEMlib/{interactive_eels.py → eels_dialog_utilities.py} +1199 -1524
  12. pyTEMlib/eels_tools.py +2031 -1731
  13. pyTEMlib/file_tools.py +1276 -491
  14. pyTEMlib/file_tools_qt.py +193 -0
  15. pyTEMlib/graph_tools.py +1166 -450
  16. pyTEMlib/graph_viz.py +449 -0
  17. pyTEMlib/image_dialog.py +158 -0
  18. pyTEMlib/image_dlg.py +146 -0
  19. pyTEMlib/image_tools.py +1399 -956
  20. pyTEMlib/info_widget.py +933 -0
  21. pyTEMlib/interactive_image.py +1 -0
  22. pyTEMlib/kinematic_scattering.py +1196 -0
  23. pyTEMlib/low_loss_widget.py +176 -0
  24. pyTEMlib/microscope.py +61 -78
  25. pyTEMlib/peak_dialog.py +1047 -350
  26. pyTEMlib/peak_dlg.py +286 -248
  27. pyTEMlib/probe_tools.py +653 -202
  28. pyTEMlib/sidpy_tools.py +153 -129
  29. pyTEMlib/simulation_tools.py +104 -87
  30. pyTEMlib/version.py +6 -3
  31. pyTEMlib/xrpa_x_sections.py +20972 -0
  32. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/LICENSE +21 -21
  33. pyTEMlib-0.2024.8.4.dist-info/METADATA +93 -0
  34. pyTEMlib-0.2024.8.4.dist-info/RECORD +37 -0
  35. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/WHEEL +6 -5
  36. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/entry_points.txt +0 -1
  37. pyTEMlib/KinsCat.py +0 -2685
  38. pyTEMlib/__version__.py +0 -2
  39. pyTEMlib/data/TEMlibrc +0 -68
  40. pyTEMlib/data/edges_db.csv +0 -189
  41. pyTEMlib/data/edges_db.pkl +0 -0
  42. pyTEMlib/data/fparam.txt +0 -103
  43. pyTEMlib/data/microscopes.csv +0 -7
  44. pyTEMlib/data/microscopes.xml +0 -167
  45. pyTEMlib/data/path.txt +0 -1
  46. pyTEMlib/defaults_parser.py +0 -86
  47. pyTEMlib/dm3_reader.py +0 -609
  48. pyTEMlib/edges_db.py +0 -76
  49. pyTEMlib/eels_dlg.py +0 -240
  50. pyTEMlib/hdf_utils.py +0 -481
  51. pyTEMlib/image_tools1.py +0 -2194
  52. pyTEMlib/info_dialog.py +0 -227
  53. pyTEMlib/info_dlg.py +0 -205
  54. pyTEMlib/nion_reader.py +0 -293
  55. pyTEMlib/nsi_reader.py +0 -165
  56. pyTEMlib/structure_tools.py +0 -316
  57. pyTEMlib-0.2020.11.0.dist-info/METADATA +0 -20
  58. pyTEMlib-0.2020.11.0.dist-info/RECORD +0 -42
  59. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/top_level.txt +0 -0
pyTEMlib/probe_tools.py CHANGED
@@ -1,202 +1,653 @@
1
- import numpy as np
2
-
3
-
4
- def make_gauss(size_x, size_y, width=1.0, x0=0.0, y0=0.0, intensity=1.0):
5
- size_x = size_x/2
6
- size_y = size_y/2
7
- x, y = np.mgrid[-size_x:size_x, -size_y:size_y]
8
- g = np.exp(-((x-x0)**2 + (y-y0)**2) / 2.0 / width**2)
9
- probe = g / g.sum() * intensity
10
-
11
- return probe
12
-
13
-
14
- def make_lorentz(size_x, size_y, gamma=1.0, x0=0., y0=0., intensity=1.):
15
- size_x = np.floor(size_x / 2)
16
- size_y = np.floor(size_y / 2)
17
- x, y = np.mgrid[-size_x:size_x, -size_y:size_y]
18
- g = gamma / (2*np.pi) / np.power(((x-x0)**2 + (y-y0)**2 + gamma**2), 1.5)
19
- probe = g / g.sum() * intensity
20
- return probe
21
-
22
-
23
- def zero_loss_peak_weight():
24
- # US100 zero_loss peak for Cc of aberrations
25
- x = np.linspace(-0.5, 0.9, 29)
26
- y = [0.0143, 0.0193, 0.0281, 0.0440, 0.0768, 0.1447, 0.2785, 0.4955, 0.7442, 0.9380, 1.0000, 0.9483, 0.8596,
27
- 0.7620, 0.6539, 0.5515, 0.4478, 0.3500, 0.2683, 0.1979, 0.1410, 0.1021, 0.0752, 0.0545, 0.0401, 0.0300,
28
- 0.0229, 0.0176, 0.0139]
29
- return x, y
30
-
31
-
32
- # ## Aberration Function for Probe calculations
33
- def make_chi1(phi, theta, wl, ab, c1_include):
34
- """
35
- ###
36
- # Aberration function chi without defocus
37
- ###
38
- """
39
- t0 = np.power(theta, 1) / 1 * (float(ab['C01a']) * np.cos(1 * phi) + float(ab['C01b']) * np.sin(1 * phi))
40
-
41
- if c1_include == 1: # First and second terms
42
- t1 = np.power(theta, 2) / 2 * (ab['C10'] + ab['C12a'] * np.cos(2 * phi) + ab['C12b'] * np.sin(2 * phi))
43
- elif c1_include == 2: # Second terms only
44
- t1 = np.power(theta, 2) / 2 * (ab['C12a'] * np.cos(2 * phi) + ab['C12b'] * np.sin(2 * phi))
45
- else: # none for zero
46
- t1 = t0 * 0.
47
- t2 = np.power(theta, 3) / 3 * (ab['C21a'] * np.cos(1 * phi) + ab['C21b'] * np.sin(1 * phi)
48
- + ab['C23a'] * np.cos(3 * phi) + ab['C23b'] * np.sin(3 * phi))
49
-
50
- t3 = np.power(theta, 4) / 4 * (ab['C30']
51
- + ab['C32a'] * np.cos(2 * phi)
52
- + ab['C32b'] * np.sin(2 * phi)
53
- + ab['C34a'] * np.cos(4 * phi)
54
- + ab['C34b'] * np.sin(4 * phi))
55
-
56
- t4 = np.power(theta, 5) / 5 * (ab['C41a'] * np.cos(1 * phi)
57
- + ab['C41b'] * np.sin(1 * phi)
58
- + ab['C43a'] * np.cos(3 * phi)
59
- + ab['C43b'] * np.sin(3 * phi)
60
- + ab['C45a'] * np.cos(5 * phi)
61
- + ab['C45b'] * np.sin(5 * phi))
62
-
63
- t5 = np.power(theta, 6) / 6 * (ab['C50']
64
- + ab['C52a'] * np.cos(2 * phi)
65
- + ab['C52b'] * np.sin(2 * phi)
66
- + ab['C54a'] * np.cos(4 * phi)
67
- + ab['C54b'] * np.sin(4 * phi)
68
- + ab['C56a'] * np.cos(6 * phi)
69
- + ab['C56b'] * np.sin(6 * phi))
70
-
71
- chi = t0 + t1 + t2 + t3 + t4 + t5
72
- if 'C70' in ab:
73
- chi += np.power(theta, 8) / 8 * (ab['C70'])
74
- return chi * 2 * np.pi / wl # np.power(theta,6)/6*( ab['C50'] )
75
-
76
-
77
- def probe2(ab, size_x, size_y, tags, verbose=False):
78
- """
79
- **********************************************
80
- * This function creates a incident STEM probe
81
- * at position (0,0)
82
- * with parameters given in ab dictionary
83
- *
84
- * The following Aberration functions are being used:
85
- * 1) ddf = Cc*de/E but not + Cc2*(de/E)^2,
86
- * Cc, Cc2 = chrom. Aber. (1st, 2nd order) [1]
87
- * 2) chi(qx,qy) = (2*pi/lambda)*{0.5*C1*(qx^2+qy^2)+
88
- * 0.5*C12a*(qx^2-qy^2)+
89
- * C12b*qx*qy+
90
- * C21a/3*qx*(qx^2+qy^2)+
91
- * ...
92
- * +0.5*C3*(qx^2+qy^2)^2
93
- * +0.125*C5*(qx^2+qy^2)^3
94
- * ... (need to finish)
95
- *
96
- *
97
- * qx = acos(kx/K), qy = acos(ky/K)
98
- *
99
- * References:
100
- * [1] J. Zach, M. Haider,
101
- * "Correction of spherical and Chromatic Aberration
102
- * in a low Voltage SEM", Optik 98 (3), 112-118 (1995)
103
- * [2] O.L. Krivanek, N. Delby, A.R. Lupini,
104
- * "Towards sub-Angstroem Electron Beams",
105
- * Ultramicroscopy 78, 1-11 (1999)
106
- *
107
- *********************************************'''
108
- ####
109
- # Internally reciprocal lattice vectors in 1/nm or rad.
110
- # All calculations of chi in angles.
111
- # All aberration coefficients in nm
112
- """
113
-
114
- if 'fov' not in ab:
115
- if 'fov' not in tags:
116
- print(' need field of view in tags ')
117
- else:
118
- ab['fov'] = tags['fov']
119
-
120
- if 'convAngle' not in ab:
121
- ab['convAngle'] = 30 # in mrad
122
-
123
- ap_angle = ab['convAngle'] / 1000.0 # in rad
124
-
125
- e0 = ab['EHT'] = float(ab['EHT']) # acceleration voltage in ev
126
-
127
- # defocus = ab['C10']
128
-
129
- if 'C01a' not in ab:
130
- ab['C01a'] = 0.
131
- if 'C01b' not in ab:
132
- ab['C01b'] = 0.
133
-
134
- if 'C50' not in ab:
135
- ab['C50'] = 0.
136
- if 'C70' not in ab:
137
- ab['C70'] = 0.
138
-
139
- if 'Cc' not in ab:
140
- ab['Cc'] = 1.3e6 # Cc in nm
141
-
142
- def get_wl():
143
- h = 6.626 * 10 ** -34
144
- m0 = 9.109 * 10 ** -31
145
- ev = 1.602 * 10 ** -19 * e0
146
- c = 2.998 * 10 ** 8
147
- return h / np.sqrt(2 * m0 * ev * (1 + ev / (2 * m0 * c ** 2))) * 10 ** 9
148
-
149
- wl = get_wl()
150
- if verbose:
151
- print('Acceleration voltage {0:}kV => wavelength {1:.2f}pm'.format(int(e0 / 1000), wl * 1000))
152
- ab['wavelength'] = wl
153
-
154
- # Reciprocal plane in 1/nm
155
- dk = 1 / ab['fov']
156
- kx = np.array(dk * (-size_x / 2. + np.arange(size_x)))
157
- ky = np.array(dk * (-size_y / 2. + np.arange(size_y)))
158
- t_xv, t_yv = np.meshgrid(kx, ky)
159
-
160
- # define reciprocal plane in angles
161
- phi = np.arctan2(t_xv, t_yv)
162
- theta = np.arctan2(np.sqrt(t_xv ** 2 + t_yv ** 2), 1 / wl)
163
-
164
- # calculate chi but omit defocus
165
- chi = np.fft.ifftshift(make_chi1(phi, theta, wl, ab, 2))
166
- probe = np.zeros((size_x, size_y))
167
-
168
- # Aperture function
169
- mask = theta >= ap_angle
170
-
171
- # Calculate probe with Cc
172
-
173
- for i in range(len(ab['zeroLoss'])):
174
- df = ab['C10'] + ab['Cc'] * ab['zeroEnergy'][i] / e0
175
- if verbose:
176
- print('defocus due to Cc: {0:.2f} nm with weight {1:.2f}'.format(df, ab['zeroLoss'][i]))
177
- # Add defocus
178
- chi2 = chi + np.power(theta, 2) / 2 * df
179
- # Calculate exponent of - i * chi
180
- chi_t = np.fft.ifftshift(np.vectorize(complex)(np.cos(chi2), -np.sin(chi2)))
181
- # Apply aperture function
182
- chi_t[mask] = 0.
183
- # inverse fft of aberration function
184
- i2 = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(chi_t)))
185
- # add intensities
186
- probe = probe + np.real(i2 * np.conjugate(i2)).T * ab['zeroLoss'][i]
187
-
188
- ab0 = {}
189
- for key in ab:
190
- ab0[key] = 0.
191
- # chiIA = np.fft.fftshift(make_chi1(phi, theta, wl, ab0, 0)) # np.ones(chi2.shape)*2*np.pi/wl
192
- chi_i = np.ones((size_y, size_x))
193
- chi_i[mask] = 0.
194
- i2 = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(chi_i)))
195
- ideal = np.real(i2 * np.conjugate(i2))
196
-
197
- probe_f = np.fft.fft2(probe, probe.shape) + 1e-12
198
- ideal_f = np.fft.fft2(ideal, probe.shape)
199
- fourier_space_division = ideal_f / probe_f
200
- probe_r = (np.fft.ifft2(fourier_space_division, probe.shape))
201
-
202
- return probe / sum(ab['zeroLoss']), np.real(probe_r)
1
+ """Functions to calculate electron probe"""
2
+ import numpy as np
3
+ import pyTEMlib.image_tools
4
+ import scipy.ndimage as ndimage
5
+
6
+
7
+ def make_gauss(size_x, size_y, width=1.0, x0=0.0, y0=0.0, intensity=1.0):
8
+ """Make a Gaussian shaped probe """
9
+ size_x = size_x / 2
10
+ size_y = size_y / 2
11
+ x, y = np.mgrid[-size_x:size_x, -size_y:size_y]
12
+ g = np.exp(-((x - x0) ** 2 + (y - y0) ** 2) / 2.0 / width ** 2)
13
+ probe = g / g.sum() * intensity
14
+
15
+ return probe
16
+
17
+
18
+ def make_lorentz(size_x, size_y, gamma=1.0, x0=0., y0=0., intensity=1.):
19
+ """Make a Lorentzian shaped probe """
20
+
21
+ size_x = np.floor(size_x / 2)
22
+ size_y = np.floor(size_y / 2)
23
+ x, y = np.mgrid[-size_x:size_x, -size_y:size_y]
24
+ g = gamma / (2 * np.pi) / np.power(((x - x0) ** 2 + (y - y0) ** 2 + gamma ** 2), 1.5)
25
+ probe = g / g.sum() * intensity
26
+ return probe
27
+
28
+
29
+ def zero_loss_peak_weight():
30
+ # US100 zero_loss peak for Cc of aberrations
31
+ x = np.linspace(-0.5, 0.9, 29)
32
+ y = [0.0143, 0.0193, 0.0281, 0.0440, 0.0768, 0.1447, 0.2785, 0.4955, 0.7442, 0.9380, 1.0000, 0.9483, 0.8596,
33
+ 0.7620, 0.6539, 0.5515, 0.4478, 0.3500, 0.2683, 0.1979, 0.1410, 0.1021, 0.0752, 0.0545, 0.0401, 0.0300,
34
+ 0.0229, 0.0176, 0.0139]
35
+ return x, y
36
+
37
+
38
+ def make_chi(phi, theta, aberrations):
39
+ maximum_aberration_order = 5
40
+ chi = np.zeros(theta.shape)
41
+ for n in range(maximum_aberration_order + 1): # First Sum up to fifth order
42
+ term_first_sum = np.power(theta, n + 1) / (n + 1) # term in first sum
43
+
44
+ second_sum = np.zeros(theta.shape) # second Sum initialized with zeros
45
+ for m in range((n + 1) % 2, n + 2, 2):
46
+ if m > 0:
47
+ if f'C{n}{m}a' not in aberrations: # Set non existent aberrations coefficient to zero
48
+ aberrations[f'C{n}{m}a'] = 0.
49
+ if f'C{n}{m}b' not in aberrations:
50
+ aberrations[f'C{n}{m}b'] = 0.
51
+
52
+ # term in second sum
53
+ second_sum = second_sum + aberrations[f'C{n}{m}a'] * np.cos(m * phi) + aberrations[
54
+ f'C{n}{m}b'] * np.sin(m * phi)
55
+ else:
56
+ if f'C{n}{m}' not in aberrations: # Set non existent aberrations coefficient to zero
57
+ aberrations[f'C{n}{m}'] = 0.
58
+
59
+ # term in second sum
60
+ second_sum = second_sum + aberrations[f'C{n}{m}']
61
+ chi = chi + term_first_sum * second_sum * 2 * np.pi / aberrations['wavelength']
62
+
63
+ return chi
64
+
65
+
66
+ def get_chi(ab, size_x, size_y, verbose=False):
67
+ """ Get aberration function chi without defocus spread
68
+
69
+ # Internally reciprocal lattice vectors in 1/nm or rad.
70
+ # All calculations of chi in angles.
71
+ # All aberration coefficients in nm
72
+ """
73
+ aperture_angle = ab['convergence_angle'] / 1000.0 # in rad
74
+
75
+ wavelength = pyTEMlib.image_tools.get_wavelength(ab['acceleration_voltage'])
76
+ if verbose:
77
+ print(f"Acceleration voltage {ab['acceleration_voltage'] / 1000:}kV => wavelength {wavelength * 1000.:.2f}pm")
78
+
79
+ ab['wavelength'] = wavelength
80
+
81
+ # Reciprocal plane in 1/nm
82
+ dk = 1 / ab['FOV']
83
+ k_x = np.array(dk * (-size_x / 2. + np.arange(size_x)))
84
+ k_y = np.array(dk * (-size_y / 2. + np.arange(size_y)))
85
+ t_x_v, t_y_v = np.meshgrid(k_x, k_y)
86
+
87
+ # define reciprocal plane in angles
88
+ phi = np.arctan2(t_x_v, t_y_v)
89
+ theta = np.arctan2(np.sqrt(t_x_v ** 2 + t_y_v ** 2), 1 / wavelength)
90
+
91
+ # calculate chi
92
+ chi = make_chi(phi, theta, ab)
93
+
94
+ # Aperture function
95
+ mask = theta >= aperture_angle
96
+
97
+ aperture = np.ones((size_x, size_y), dtype=float)
98
+ aperture[mask] = 0.
99
+
100
+ return chi, aperture
101
+
102
+
103
+ def print_aberrations(ab):
104
+ from IPython.display import HTML, display
105
+ output = '<html><body>'
106
+ output += f"Aberrations [nm] for acceleration voltage: {ab['acceleration_voltage'] / 1e3:.0f} kV"
107
+ output += '<table>'
108
+ output += f"<tr><td> C10 </td><td> {ab['C10']:.1f} </tr>"
109
+ output += f"<tr><td> C12a </td><td> {ab['C12a']:20.1f} <td> C12b </td><td> {ab['C12b']:20.1f} </tr>"
110
+ output += f"<tr><td> C21a </td><td> {ab['C21a']:.1f} <td> C21b </td><td> {ab['C21b']:.1f} "
111
+ output += f" <td> C23a </td><td> {ab['C23a']:.1f} <td> C23b </td><td> {ab['C23b']:.1f} </tr>"
112
+ output += f"<tr><td> C30 </td><td> {ab['C30']:.1f} </tr>"
113
+ output += f"<tr><td> C32a </td><td> {ab['C32a']:20.1f} <td> C32b </td><td> {ab['C32b']:20.1f} "
114
+ output += f"<td> C34a </td><td> {ab['C34a']:20.1f} <td> C34b </td><td> {ab['C34b']:20.1f} </tr>"
115
+ output += f"<tr><td> C41a </td><td> {ab['C41a']:.3g} <td> C41b </td><td> {ab['C41b']:.3g} "
116
+ output += f" <td> C43a </td><td> {ab['C43a']:.3g} <td> C43b </td><td> {ab['C41b']:.3g} "
117
+ output += f" <td> C45a </td><td> {ab['C45a']:.3g} <td> C45b </td><td> {ab['C45b']:.3g} </tr>"
118
+ output += f"<tr><td> C50 </td><td> {ab['C50']:.3g} </tr>"
119
+ output += f"<tr><td> C52a </td><td> {ab['C52a']:20.1f} <td> C52b </td><td> {ab['C52b']:20.1f} "
120
+ output += f"<td> C54a </td><td> {ab['C54a']:20.1f} <td> C54b </td><td> {ab['C54b']:20.1f} "
121
+ output += f"<td> C56a </td><td> {ab['C56a']:20.1f} <td> C56b </td><td> {ab['C56b']:20.1f} </tr>"
122
+ output += f"<tr><td> Cc </td><td> {ab['Cc']:.3g} </tr>"
123
+
124
+ output += '</table></body></html>'
125
+
126
+ display(HTML(output))
127
+
128
+
129
+ def get_ronchigram(size, ab, scale='mrad'):
130
+ """ Get Ronchigram
131
+
132
+ """
133
+ size_x = size_y = size
134
+ chi, A_k = get_chi(ab, size_x, size_y)
135
+
136
+ v_noise = np.random.rand(size_x, size_y)
137
+ smoothing = 5
138
+ phi_r = ndimage.gaussian_filter(v_noise, sigma=(smoothing, smoothing), order=0)
139
+
140
+ sigma = 6 # 6 for carbon and thin
141
+
142
+ q_r = np.exp(-1j * sigma * phi_r)
143
+ # q_r = 1-phi_r * sigma
144
+
145
+ T_k = A_k * (np.exp(-1j * chi))
146
+ t_r = (np.fft.ifft2(np.fft.fftshift(T_k)))
147
+
148
+ psi_k = np.fft.fftshift(np.fft.fft2(q_r * t_r))
149
+
150
+ ronchigram = np.absolute(psi_k * np.conjugate(psi_k))
151
+
152
+ fov_reciprocal = 1 / ab['FOV'] * size_x / 2
153
+ if scale == '1/nm':
154
+ extent = [-fov_reciprocal, fov_reciprocal, -fov_reciprocal, fov_reciprocal]
155
+ ylabel = 'reciprocal distance [1/nm]'
156
+ else:
157
+ fov_mrad = fov_reciprocal * ab['wavelength'] * 1000
158
+ extent = [-fov_mrad, fov_mrad, -fov_mrad, fov_mrad]
159
+ ylabel = 'reciprocal distance [mrad]'
160
+
161
+ ab['ronchi_extent'] = extent
162
+ ab['ronchi_label'] = ylabel
163
+ return ronchigram
164
+
165
+
166
+ def get_chi_2(ab, u, v):
167
+ chi1 = ab['C10'] * (u ** 2 + v ** 2) / 2 \
168
+ + ab['C12a'] * (u ** 2 - v ** 2) / 2 \
169
+ - ab['C12b'] * u * v
170
+
171
+ chi2 = ab['C21a'] * (u ** 3 + u * v ** 2) / 3 \
172
+ - ab['C21b'] * (u ** 2 * v + v ** 3) / 3 \
173
+ + ab['C23a'] * (u ** 3 - 3 * u * v ** 2) / 3 \
174
+ - ab['C23b'] * (3 * u ** 2 * v - v ** 3) / 3
175
+
176
+ chi3 = ab['C30'] * (u ** 4 + 2 * u ** 2 * v ** 2 + v ** 4) / 4 \
177
+ + ab['C32a'] * (u ** 4 - v ** 4) / 4 \
178
+ - ab['C32b'] * (u ** 3 * v + u * v ** 3) / 2 \
179
+ + ab['C34a'] * (u ** 4 - 6 * u ** 2 * v ** 2 + v ** 4) / 4 \
180
+ - ab['C34b'] * (4 * u ** 3 * v - 4 * u * v ** 3) / 4
181
+
182
+ chi4 = ab['C41a'] * (u ** 5 + 2 * u ** 3 * v ** 2 + u * v ** 4) / 5 \
183
+ - ab['C41b'] * (u ** 4 * v + 2 * u ** 2 * v ** 3 + v ** 5) / 5 \
184
+ + ab['C43a'] * (u ** 5 - 2 * u ** 3 * v ** 2 - 3 * u * v ** 4) / 5 \
185
+ - ab['C43b'] * (3 * u ** 4 * v + 2 * u ** 2 * v ** 3 - v ** 5) / 5 \
186
+ + ab['C45a'] * (u ** 5 - 10 * u ** 3 * v ** 2 + 5 * u * v ** 4) / 5 \
187
+ - ab['C45b'] * (5 * u ** 4 * v - 10 * u ** 2 * v ** 3 + v ** 5) / 5
188
+
189
+ chi5 = ab['C50'] * (u ** 6 + 3 * u ** 4 * v ** 2 + 3 * u ** 2 * v ** 4 + v ** 6) / 6 \
190
+ + ab['C52a'] * (u ** 6 + u ** 4 * v ** 2 - u ** 2 * v ** 4 - v ** 6) / 6 \
191
+ - ab['C52b'] * (2 * u ** 5 * v + 4 * u ** 3 * v ** 3 + 2 * u * v ** 5) / 6 \
192
+ + ab['C54a'] * (u ** 6 - 5 * u ** 4 * v ** 2 - 5 * u ** 2 * v ** 4 + v ** 6) / 6 \
193
+ - ab['C54b'] * (4 * u ** 5 * v - 4 * u * v ** 5) / 6 \
194
+ + ab['C56a'] * (u ** 6 - 15 * u ** 4 * v ** 2 + 15 * u ** 2 * v ** 4 - v ** 6) / 6 \
195
+ - ab['C56b'] * (6 * u ** 5 * v - 20 * u ** 3 * v ** 3 + 6 * u * v ** 5) / 6
196
+
197
+ chi = chi1 + chi2 + chi3 + chi4 + chi5
198
+ return chi * 2 * np.pi / ab['wavelength']
199
+
200
+
201
+ def get_d2chidu2(ab, u, v):
202
+ d2chi1du2 = ab['C10'] + ab['C12a']
203
+
204
+ d2chi2du2 = ab['C21a'] * 2 * u \
205
+ - ab['C21b'] * 2 / 3 * v \
206
+ + ab['C23a'] * 2 * u \
207
+ - ab['C23b'] * 2 * v
208
+
209
+ d2chi3du2 = ab['C30'] * (3 * u ** 2 + v ** 2) \
210
+ + ab['C32a'] * 3 * u ** 2 \
211
+ - ab['C32b'] * 3 * u * v \
212
+ + ab['C34a'] * (3 * u ** 2 - 3 * v ** 2) \
213
+ - ab['C34b'] * 6 * u * v
214
+
215
+ d2chi4du2 = ab['C41a'] * 4 / 5 * (5 * u ** 3 + 3 * u * v ** 2) \
216
+ - ab['C41b'] * 4 / 5 * (3 * u ** 2 * v + v ** 3) \
217
+ + ab['C43a'] * 4 / 5 * (5 * u ** 3 - 3 * u * v ** 2) \
218
+ - ab['C43b'] * 4 / 5 * (9 * u ** 2 * v + v ** 3) \
219
+ + ab['C45a'] * 4 * (u ** 3 - 3 * u * v ** 2) \
220
+ - ab['C45b'] * 4 * (3 * u ** 2 * v - v ** 3)
221
+
222
+ d2chi5du2 = ab['C50'] * (5 * u ** 4 + 6 * u ** 2 * v ** 2 + v ** 4) \
223
+ + ab['C52a'] * (15 * u ** 4 + 6 * u ** 2 * v ** 2 - v ** 4) / 3 \
224
+ - ab['C52b'] * (20 * u ** 3 * v + 12 * u * v ** 3) / 3 \
225
+ + ab['C54a'] * 5 / 3 * (3 * u ** 4 - 6 * u ** 2 * v ** 2 - v ** 4) \
226
+ - ab['C54b'] * 5 / 3 * (8 * u ** 3 * v) \
227
+ + ab['C56a'] * 5 * (u ** 4 - 6 * u ** 2 * v ** 2 + v ** 4) \
228
+ - ab['C56b'] * 20 * (u ** 3 * v - u * v ** 3)
229
+
230
+ d2chidu2 = d2chi1du2 + d2chi2du2 + d2chi3du2 + d2chi4du2 + d2chi5du2
231
+ return d2chidu2
232
+
233
+
234
+ def get_d2chidudv(ab, u, v):
235
+ d2chi1dudv = -ab['C12b']
236
+
237
+ d2chi2dudv = ab['C21a'] * 2 / 3 * v \
238
+ - ab['C21b'] * 2 / 3 * u \
239
+ - ab['C23a'] * 2 * v \
240
+ - ab['C23b'] * 2 * u
241
+
242
+ d2chi3dudv = ab['C30'] * 2 * u * v \
243
+ + ab['C32a'] * 0 \
244
+ - ab['C32b'] * 3 / 2 * (u ** 2 + v ** 2) \
245
+ - ab['C34a'] * 6 * u * v \
246
+ - ab['C34b'] * 3 * (u ** 2 - v ** 2)
247
+
248
+ d2chi4dudv = ab['C41a'] * 4 / 5 * (3 * u ** 2 * v + v ** 3) \
249
+ - ab['C41b'] * 4 / 5 * (u ** 3 + 3 * u * v ** 2) \
250
+ - ab['C43a'] * 12 / 5 * (u ** 2 * v + v ** 3) \
251
+ - ab['C43b'] * 12 / 5 * (u ** 3 + u * v ** 2) \
252
+ - ab['C45a'] * 4 * (3 * u ** 2 * v - v ** 3) \
253
+ - ab['C45b'] * 4 * (u ** 3 - 3 * u * v ** 2)
254
+
255
+ d2chi5dudv = ab['C50'] * 4 * u * v * (u ** 2 + v ** 2) \
256
+ + ab['C52a'] * 4 / 3 * (u ** 3 * v - u * v ** 3) \
257
+ - ab['C52b'] * (5 * u ** 4 + 18 * u ** 2 * v ** 2 + 5 * v ** 4) / 3 \
258
+ - ab['C54a'] * 20 / 3 * (u ** 3 * v + u * v ** 3) \
259
+ - ab['C54b'] * 10 / 3 * (u ** 4 - v ** 4) \
260
+ - ab['C56a'] * 20 * (u ** 3 * v - u * v ** 3) \
261
+ - ab['C56b'] * 5 * (u ** 4 - 6 * u ** 2 * v ** 2 + v ** 4)
262
+
263
+ d2chidudv = d2chi1dudv + d2chi2dudv + d2chi3dudv + d2chi4dudv + d2chi5dudv
264
+ return d2chidudv
265
+
266
+
267
+ def get_d2chidv2(ab, u, v):
268
+ d2chi1dv2 = ab['C10'] - ab['C12a']
269
+
270
+ d2chi2dv2 = ab['C21a'] * 2 / 3 * u \
271
+ - ab['C21b'] * 2 * v \
272
+ - ab['C23a'] * 2 * u \
273
+ + ab['C23b'] * 2 * v
274
+
275
+ d2chi3dv2 = ab['C30'] * (u ** 2 + 3 * v ** 2) \
276
+ - ab['C32a'] * 3 * v ** 2 \
277
+ - ab['C32b'] * 3 * v * u \
278
+ - ab['C34a'] * 3 * (u ** 2 - v ** 2) \
279
+ + ab['C34b'] * 6 * u * v
280
+
281
+ d2chi4dv2 = ab['C41a'] * 4 / 5 * (u ** 3 + 3 * u * v ** 2) \
282
+ - ab['C41b'] * 4 / 5 * (3 * u ** 2 * v + 5 * v ** 3) \
283
+ - ab['C43a'] * 4 / 5 * (u ** 3 + 9 * u * v ** 2) \
284
+ - ab['C43b'] * 4 / 5 * (3 * u ** 2 * v - 5 * v ** 3) \
285
+ - ab['C45a'] * 4 * (u ** 3 - 3 * u * v ** 2) \
286
+ + ab['C45b'] * 4 * (3 * u ** 2 * v - v ** 3)
287
+
288
+ d2chi5dv2 = ab['C50'] * (u ** 4 + 6 * u ** 2 * v ** 2 + 5 * v ** 4) \
289
+ + ab['C52a'] * (u ** 4 - 6 * u ** 2 * v ** 2 - 15 * v ** 4) / 3 \
290
+ - ab['C52b'] * (12 * u ** 3 * v + 20 * u * v ** 3) / 3 \
291
+ - ab['C54a'] * 5 / 3 * (u ** 4 + 6 * u ** 2 * v ** 2 - 3 * v ** 4) \
292
+ + ab['C54b'] * 40 / 3 * u * v ** 3 \
293
+ - ab['C56a'] * 5 * (u ** 4 - 6 * u ** 2 * v ** 2 + v ** 4) \
294
+ + ab['C56b'] * 20 * (u ** 3 * v - u * v ** 3)
295
+
296
+ d2chidv2 = d2chi1dv2 + d2chi2dv2 + d2chi3dv2 + d2chi4dv2 + d2chi5dv2
297
+ return d2chidv2
298
+
299
+
300
+ def get_source_energy_spread():
301
+ x = np.linspace(-0.5, .9, 29)
302
+ y = [0.0143, 0.0193, 0.0281, 0.0440, 0.0768, 0.1447, 0.2785, 0.4955, 0.7442, 0.9380, 1.0000, 0.9483, 0.8596, 0.7620,
303
+ 0.6539, 0.5515, 0.4478, 0.3500, 0.2683, 0.1979, 0.1410, 0.1021, 0.0752, 0.0545, 0.0401, 0.0300, 0.0229, 0.0176,
304
+ 0.0139]
305
+
306
+ return x, y
307
+
308
+
309
+ def get_target_aberrations(TEM_name, acceleration_voltage):
310
+ ab = {}
311
+ if TEM_name == 'NionUS200':
312
+ if int(acceleration_voltage) == 200000:
313
+ print(f' **** Using Target Values at {acceleration_voltage / 1000}kV for Aberrations of {TEM_name}****')
314
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': -335., 'C21b': 283., 'C23a': -34., 'C23b': 220.,
315
+ 'C30': -8080.,
316
+ 'C32a': 18800., 'C32b': -2260., 'C34a': 949., 'C34b': 949., 'C41a': 54883., 'C41b': -464102.,
317
+ 'C43a': 77240.5,
318
+ 'C43b': -540842., 'C45a': -79844.4, 'C45b': -76980.8, 'C50': 9546970., 'C52a': -2494290.,
319
+ 'C52b': 2999910.,
320
+ 'C54a': -2020140., 'C54b': -2019630., 'C56a': -535079., 'C56b': 1851850.}
321
+ ab['source_size'] = 0.051
322
+ ab['acceleration_voltage'] = acceleration_voltage
323
+ ab['convergence_angle'] = 30
324
+
325
+ ab['Cc'] = 1.3e6 # // Cc in nm
326
+
327
+ if int(acceleration_voltage) == 100000:
328
+ print(f' **** Using Target Values at {acceleration_voltage / 1000}kV for Aberrations of {TEM_name}****')
329
+
330
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 157., 'C21b': 169, 'C23a': -173., 'C23b': 48.7, 'C30': 201.,
331
+ 'C32a': 1090., 'C32b': 6840., 'C34a': 1010., 'C34b': 79.9, 'C41a': -210696., 'C41b': -262313.,
332
+ 'C43a': 348450., 'C43b': -9.7888e4, 'C45a': 6.80247e4, 'C45b': -3.14637e1, 'C50': -193896.,
333
+ 'C52a': -1178950, 'C52b': -7414340, 'C54a': -1753890, 'C54b': -1753890, 'C56a': -631786,
334
+ 'C56b': -165705}
335
+ ab['source_size'] = 0.051
336
+ ab['acceleration_voltage'] = acceleration_voltage
337
+ ab['convergence_angle'] = 30
338
+ ab['Cc'] = 1.3e6
339
+
340
+ if int(acceleration_voltage) == 60000:
341
+ print(f' **** Using Target Values at {acceleration_voltage / 1000}kV for Aberrations of {TEM_name}****')
342
+
343
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 11.5, 'C21b': 113, 'C23a': -136., 'C23b': 18.2, 'C30': 134.,
344
+ 'C32a': 1080., 'C32b': 773., 'C34a': 1190., 'C34b': -593., 'C41a': -179174., 'C41b': -350378.,
345
+ 'C43a': 528598, 'C43b': -257349., 'C45a': 63853.4, 'C45b': 1367.98, 'C50': 239021., 'C52a': 1569280.,
346
+ 'C52b': -6229310., 'C54a': -3167620., 'C54b': -449198., 'C56a': -907315., 'C56b': -16281.9}
347
+ ab['source_size'] = 0.081
348
+ ab['acceleration_voltage'] = acceleration_voltage
349
+ ab['convergence_angle'] = 30
350
+ ab['Cc'] = 1.3e6 # // Cc in nm
351
+
352
+ ab['origin'] = 'target aberrations'
353
+ ab['TEM_name'] = TEM_name
354
+ ab['wavelength'] = pyTEMlib.image_tools.get_wavelength(ab['acceleration_voltage'])
355
+
356
+ if TEM_name == 'NionUS100':
357
+ if int(acceleration_voltage) == 100000:
358
+ print(f' **** Using Target Values at {acceleration_voltage / 1000}kV for Aberrations of {TEM_name}****')
359
+
360
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 157., 'C21b': 169, 'C23a': -173., 'C23b': 48.7, 'C30': 201.,
361
+ 'C32a': 1090., 'C32b': 6840., 'C34a': 1010., 'C34b': 79.9, 'C41a': -210696., 'C41b': -262313.,
362
+ 'C43a': 348450., 'C43b': -9.7888e4, 'C45a': 6.80247e4, 'C45b': -3.14637e1, 'C50': -193896.,
363
+ 'C52a': -1178950, 'C52b': -7414340, 'C54a': -1753890, 'C54b': -1753890, 'C56a': -631786,
364
+ 'C56b': -165705}
365
+ ab['source_size'] = 0.051
366
+ ab['acceleration_voltage'] = acceleration_voltage
367
+ ab['convergence_angle'] = 30
368
+ ab['Cc'] = 1.3e6 # // Cc in nm
369
+
370
+ if int(acceleration_voltage) == 60000:
371
+ print(f' **** Using Target Values at {acceleration_voltage / 1000}kV for Aberrations of {TEM_name}****')
372
+
373
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 11.5, 'C21b': 113, 'C23a': -136., 'C23b': 18.2, 'C30': 134.,
374
+ 'C32a': 1080., 'C32b': 773., 'C34a': 1190., 'C34b': -593., 'C41a': -179174., 'C41b': -350378.,
375
+ 'C43a': 528598, 'C43b': -257349., 'C45a': 63853.4, 'C45b': 1367.98, 'C50': 239021., 'C52a': 1569280.,
376
+ 'C52b': -6229310., 'C54a': -3167620., 'C54b': -449198., 'C56a': -907315., 'C56b': -16281.9}
377
+ ab['source_size'] = 0.081
378
+ ab['acceleration_voltage'] = acceleration_voltage
379
+ ab['convergence_angle'] = 30
380
+ ab['Cc'] = 1.3e6 # // Cc in nm
381
+
382
+ ab['origin'] = 'target aberrations'
383
+ ab['TEM_name'] = TEM_name
384
+ ab['wavelength'] = pyTEMlib.image_tools.get_wavelength(ab['acceleration_voltage'])
385
+
386
+ if TEM_name == 'ZeissMC200':
387
+ ab = {'C10': 0, 'C12a': 0, 'C12b': 0, 'C21a': 0, 'C21b': 0, 'C23a': 0, 'C23b': 0, 'C30': 0.,
388
+ 'C32a': 0., 'C32b': -0., 'C34a': 0., 'C34b': 0., 'C41a': 0., 'C41b': -0., 'C43a': 0.,
389
+ 'C43b': -0., 'C45a': -0., 'C45b': -0., 'C50': 0., 'C52a': -0., 'C52b': 0.,
390
+ 'C54a': -0., 'C54b': -0., 'C56a': -0., 'C56b': 0.}
391
+ ab['C30'] = 2.2 * 1e6
392
+
393
+ ab['Cc'] = 2.0 * 1e6
394
+
395
+ ab['source_size'] = 0.2
396
+ ab['acceleration_voltage'] = acceleration_voltage
397
+ ab['convergence_angle'] = 10
398
+
399
+ ab['origin'] = 'target aberrations'
400
+ ab['TEM_name'] = TEM_name
401
+
402
+ ab['wavelength'] = pyTEMlib.image_tools.get_wavelength(ab['acceleration_voltage'])
403
+ return ab
404
+
405
+
406
+ def get_ronchigram_2(size, ab, scale='mrad', threshold=3):
407
+ aperture_angle = ab['convergence_angle'] / 1000.0 # in rad
408
+
409
+ wavelength = pyTEMlib.image_tools.get_wavelength(ab['acceleration_voltage'])
410
+ # if verbose:
411
+ # print(f"Acceleration voltage {ab['acceleration_voltage']/1000:}kV => wavelength {wavelength*1000.:.2f}pm")
412
+
413
+ ab['wavelength'] = wavelength
414
+
415
+ size_x = size_y = size
416
+
417
+ # Reciprocal plane in 1/nm
418
+ dk = ab['reciprocal_FOV'] / size
419
+ k_x = np.array(dk * (-size_x / 2. + np.arange(size_x)))
420
+ k_y = np.array(dk * (-size_y / 2. + np.arange(size_y)))
421
+ t_x_v, t_y_v = np.meshgrid(k_x, k_y)
422
+
423
+ chi = get_chi_2(ab, t_x_v, t_y_v) # , verbose= True)
424
+ # define reciprocal plane in angles
425
+ phi = np.arctan2(t_x_v, t_y_v)
426
+ theta = np.arctan2(np.sqrt(t_x_v ** 2 + t_y_v ** 2), 1 / wavelength)
427
+
428
+ # Aperture function
429
+ mask = theta >= aperture_angle
430
+
431
+ aperture = np.ones((size_x, size_y), dtype=float)
432
+ aperture[mask] = 0.
433
+
434
+ v_noise = np.random.rand(size_x, size_y)
435
+ smoothing = 5
436
+ phi_r = ndimage.gaussian_filter(v_noise, sigma=(smoothing, smoothing), order=0)
437
+
438
+ sigma = 6 # 6 for carbon and thin
439
+
440
+ q_r = np.exp(-1j * sigma * phi_r)
441
+ # q_r = 1-phi_r * sigma
442
+
443
+ T_k = aperture * (np.exp(-1j * chi))
444
+ t_r = np.fft.ifft2(np.fft.fftshift(T_k))
445
+
446
+ Psi_k = np.fft.fftshift(np.fft.fft2(q_r * t_r))
447
+
448
+ ronchigram = I_k = np.absolute(Psi_k * np.conjugate(Psi_k))
449
+
450
+ fov_reciprocal = ab['reciprocal_FOV']
451
+ if scale == '1/nm':
452
+ extent = [-fov_reciprocal, fov_reciprocal, -fov_reciprocal, fov_reciprocal]
453
+ ylabel = 'reciprocal distance [1/nm]'
454
+ else:
455
+ fov_mrad = fov_reciprocal * ab['wavelength'] * 1000
456
+ extent = [-fov_mrad, fov_mrad, -fov_mrad, fov_mrad]
457
+ ylabel = 'reciprocal distance [mrad]'
458
+
459
+ ab['ronchi_extent'] = extent
460
+ ab['ronchi_label'] = ylabel
461
+
462
+ h = np.zeros([chi.shape[0], chi.shape[1], 2, 2])
463
+ h[:, :, 0, 0] = get_d2chidu2(ab, t_x_v, t_y_v)
464
+ h[:, :, 0, 1] = get_d2chidudv(ab, t_x_v, t_y_v)
465
+ h[:, :, 1, 0] = get_d2chidudv(ab, t_x_v, t_y_v)
466
+ h[:, :, 1, 1] = get_d2chidv2(ab, t_x_v, t_y_v)
467
+
468
+ # get Eigenvalues
469
+ _, s, _ = np.linalg.svd(h)
470
+
471
+ # get smallest Eigenvalue per pixel
472
+ infinite_magnification = np.min(s, axis=2)
473
+
474
+ # set all values below a threshold value to one, otherwise 0
475
+ infinite_magnification[infinite_magnification <= threshold] = 1
476
+ infinite_magnification[infinite_magnification > threshold] = 0
477
+
478
+ return ronchigram, infinite_magnification
479
+
480
+
481
+ # ## Aberration Function for Probe calculations
482
+ def make_chi1(phi, theta, wavelength, ab, c1_include):
483
+ """
484
+ # ##
485
+ # Aberration function chi without defocus
486
+ # ##
487
+ """
488
+ t0 = np.power(theta, 1) / 1 * (float(ab['C01a']) * np.cos(1 * phi) + float(ab['C01b']) * np.sin(1 * phi))
489
+
490
+ if c1_include == 1: # First and second terms
491
+ t1 = np.power(theta, 2) / 2 * (ab['C10'] + ab['C12a'] * np.cos(2 * phi) + ab['C12b'] * np.sin(2 * phi))
492
+ elif c1_include == 2: # Second terms only
493
+ t1 = np.power(theta, 2) / 2 * (ab['C12a'] * np.cos(2 * phi) + ab['C12b'] * np.sin(2 * phi))
494
+ else: # none for zero
495
+ t1 = t0 * 0.
496
+
497
+ t2 = np.power(theta, 3) / 3 * (ab['C21a'] * np.cos(1 * phi) + ab['C21b'] * np.sin(1 * phi)
498
+ + ab['C23a'] * np.cos(3 * phi) + ab['C23b'] * np.sin(3 * phi))
499
+
500
+ t3 = np.power(theta, 4) / 4 * (ab['C30']
501
+ + ab['C32a'] * np.cos(2 * phi)
502
+ + ab['C32b'] * np.sin(2 * phi)
503
+ + ab['C34a'] * np.cos(4 * phi)
504
+ + ab['C34b'] * np.sin(4 * phi))
505
+
506
+ t4 = np.power(theta, 5) / 5 * (ab['C41a'] * np.cos(1 * phi)
507
+ + ab['C41b'] * np.sin(1 * phi)
508
+ + ab['C43a'] * np.cos(3 * phi)
509
+ + ab['C43b'] * np.sin(3 * phi)
510
+ + ab['C45a'] * np.cos(5 * phi)
511
+ + ab['C45b'] * np.sin(5 * phi))
512
+
513
+ t5 = np.power(theta, 6) / 6 * (ab['C50']
514
+ + ab['C52a'] * np.cos(2 * phi)
515
+ + ab['C52b'] * np.sin(2 * phi)
516
+ + ab['C54a'] * np.cos(4 * phi)
517
+ + ab['C54b'] * np.sin(4 * phi)
518
+ + ab['C56a'] * np.cos(6 * phi)
519
+ + ab['C56b'] * np.sin(6 * phi))
520
+
521
+ chi = t0 + t1 + t2 + t3 + t4 + t5
522
+ if 'C70' in ab:
523
+ chi += np.power(theta, 8) / 8 * (ab['C70'])
524
+
525
+ return chi * 2 * np.pi / wavelength # np.power(theta,6)/6*( ab['C50'] )
526
+
527
+
528
+ def probe2(ab, size_x, size_y, tags, verbose=False):
529
+ """
530
+
531
+ * This function creates an incident STEM probe
532
+ * at position (0,0)
533
+ * with parameters given in ab dictionary
534
+ *
535
+ * The following Aberration functions are being used:
536
+ * 1) ddf = Cc*de/E but not + Cc2*(de/E)^2,
537
+ * Cc, Cc2 = chrom. Aber. (1st, 2nd order) [1]
538
+ * 2) chi(qx,qy) = (2*pi/lambda)*{0.5*C1*(qx^2+qy^2)+
539
+ * 0.5*C12a*(qx^2-qy^2)+
540
+ * C12b*qx*qy+
541
+ * C21a/3*qx*(qx^2+qy^2)+
542
+ * ...
543
+ * +0.5*C3*(qx^2+qy^2)^2
544
+ * +0.125*C5*(qx^2+qy^2)^3
545
+ * ... (need to finish)
546
+ *
547
+ *
548
+ * qx = acos(k_x/K), qy = acos(k_y/K)
549
+ *
550
+ * References:
551
+ * [1] J. Zach, M. Haider,
552
+ * "Correction of spherical and Chromatic Aberration
553
+ * in a low Voltage SEM", Optik 98 (3), 112-118 (1995)
554
+ * [2] O.L. Krivanek, N. Delby, A.R. Lupini,
555
+ * "Towards sub-Angstrom Electron Beams",
556
+ * Ultramicroscopy 78, 1-11 (1999)
557
+ *
558
+
559
+
560
+ # Internally reciprocal lattice vectors in 1/nm or rad.
561
+ # All calculations of chi in angles.
562
+ # All aberration coefficients in nm
563
+ """
564
+
565
+ if 'fov' not in ab:
566
+ if 'fov' not in tags:
567
+ print(' need field of view in tags ')
568
+ else:
569
+ ab['fov'] = tags['fov']
570
+
571
+ if 'convAngle' not in ab:
572
+ ab['convAngle'] = 30 # in mrad
573
+
574
+ ap_angle = ab['convAngle'] / 1000.0 # in rad
575
+
576
+ e0 = ab['EHT'] = float(ab['EHT']) # acceleration voltage in ev
577
+
578
+ # defocus = ab['C10']
579
+
580
+ if 'C01a' not in ab:
581
+ ab['C01a'] = 0.
582
+ if 'C01b' not in ab:
583
+ ab['C01b'] = 0.
584
+
585
+ if 'C50' not in ab:
586
+ ab['C50'] = 0.
587
+ if 'C70' not in ab:
588
+ ab['C70'] = 0.
589
+
590
+ if 'Cc' not in ab:
591
+ ab['Cc'] = 1.3e6 # Cc in nm
592
+
593
+ def get_wl():
594
+ h = 6.626 * 10 ** -34
595
+ m0 = 9.109 * 10 ** -31
596
+ ev = 1.602 * 10 ** -19 * e0
597
+ c = 2.998 * 10 ** 8
598
+ return h / np.sqrt(2 * m0 * ev * (1 + ev / (2 * m0 * c ** 2))) * 10 ** 9
599
+
600
+ wavelength = get_wl()
601
+ if verbose:
602
+ print('Acceleration voltage {0:}kV => wavelength {1:.2f}pm'.format(int(e0 / 1000), wavelength * 1000))
603
+ ab['wavelength'] = wavelength
604
+
605
+ # Reciprocal plane in 1/nm
606
+ dk = 1 / ab['fov']
607
+ k_x = np.array(dk * (-size_x / 2. + np.arange(size_x)))
608
+ k_y = np.array(dk * (-size_y / 2. + np.arange(size_y)))
609
+ t_xv, t_yv = np.meshgrid(k_x, k_y)
610
+
611
+ # define reciprocal plane in angles
612
+ phi = np.arctan2(t_xv, t_yv)
613
+ theta = np.arctan2(np.sqrt(t_xv ** 2 + t_yv ** 2), 1 / wavelength)
614
+
615
+ # calculate chi but omit defocus
616
+ chi = np.fft.ifftshift(make_chi1(phi, theta, wavelength, ab, 2))
617
+ probe = np.zeros((size_x, size_y))
618
+
619
+ # Aperture function
620
+ mask = theta >= ap_angle
621
+
622
+ # Calculate probe with Cc
623
+
624
+ for i in range(len(ab['zeroLoss'])):
625
+ df = ab['C10'] + ab['Cc'] * ab['zeroEnergy'][i] / e0
626
+ if verbose:
627
+ print('defocus due to Cc: {0:.2f} nm with weight {1:.2f}'.format(df, ab['zeroLoss'][i]))
628
+ # Add defocus
629
+ chi2 = chi + np.power(theta, 2) / 2 * df
630
+ # Calculate exponent of - i * chi
631
+ chi_t = np.fft.ifftshift(np.vectorize(complex)(np.cos(chi2), -np.sin(chi2)))
632
+ # Apply aperture function
633
+ chi_t[mask] = 0.
634
+ # inverse fft of aberration function
635
+ i2 = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(chi_t)))
636
+ # add intensities
637
+ probe = probe + np.real(i2 * np.conjugate(i2)).T * ab['zeroLoss'][i]
638
+
639
+ ab0 = {}
640
+ for key in ab:
641
+ ab0[key] = 0.
642
+ # chiIA = np.fft.fftshift(make_chi1(phi, theta, wavelength, ab0, 0)) # np.ones(chi2.shape)*2*np.pi/wavelength
643
+ chi_i = np.ones((size_y, size_x))
644
+ chi_i[mask] = 0.
645
+ i2 = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(chi_i)))
646
+ ideal = np.real(i2 * np.conjugate(i2))
647
+
648
+ probe_f = np.fft.fft2(probe, probe.shape) + 1e-12
649
+ ideal_f = np.fft.fft2(ideal, probe.shape)
650
+ fourier_space_division = ideal_f / probe_f
651
+ probe_r = (np.fft.ifft2(fourier_space_division, probe.shape))
652
+
653
+ return probe / sum(ab['zeroLoss']), np.real(probe_r)