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.

Files changed (94) hide show
  1. build/lib/pyTEMlib/__init__.py +33 -0
  2. build/lib/pyTEMlib/animation.py +640 -0
  3. build/lib/pyTEMlib/atom_tools.py +238 -0
  4. build/lib/pyTEMlib/config_dir.py +31 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +756 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +293 -0
  8. build/lib/pyTEMlib/eds_tools.py +826 -0
  9. build/lib/pyTEMlib/eds_xsections.py +432 -0
  10. build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
  11. build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  12. build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
  13. build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  14. build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  15. build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  16. build/lib/pyTEMlib/file_reader.py +274 -0
  17. build/lib/pyTEMlib/file_tools.py +811 -0
  18. build/lib/pyTEMlib/get_bote_salvat.py +69 -0
  19. build/lib/pyTEMlib/graph_tools.py +1153 -0
  20. build/lib/pyTEMlib/graph_viz.py +599 -0
  21. build/lib/pyTEMlib/image/__init__.py +37 -0
  22. build/lib/pyTEMlib/image/image_atoms.py +270 -0
  23. build/lib/pyTEMlib/image/image_clean.py +197 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +277 -0
  26. build/lib/pyTEMlib/image/image_graph.py +926 -0
  27. build/lib/pyTEMlib/image/image_registration.py +316 -0
  28. build/lib/pyTEMlib/image/image_utilities.py +309 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +699 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
  33. build/lib/pyTEMlib/microscope.py +61 -0
  34. build/lib/pyTEMlib/probe_tools.py +906 -0
  35. build/lib/pyTEMlib/sidpy_tools.py +153 -0
  36. build/lib/pyTEMlib/simulation_tools.py +104 -0
  37. build/lib/pyTEMlib/test.py +437 -0
  38. build/lib/pyTEMlib/utilities.py +314 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +25 -3
  42. pyTEMlib/animation.py +31 -22
  43. pyTEMlib/atom_tools.py +29 -34
  44. pyTEMlib/config_dir.py +2 -28
  45. pyTEMlib/crystal_tools.py +129 -165
  46. pyTEMlib/eds_tools.py +559 -342
  47. pyTEMlib/eds_xsections.py +432 -0
  48. pyTEMlib/eels_tools/__init__.py +44 -0
  49. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  50. pyTEMlib/eels_tools/eels_database.py +134 -0
  51. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  52. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  53. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  54. pyTEMlib/file_reader.py +274 -0
  55. pyTEMlib/file_tools.py +260 -1130
  56. pyTEMlib/get_bote_salvat.py +69 -0
  57. pyTEMlib/graph_tools.py +101 -174
  58. pyTEMlib/graph_viz.py +150 -0
  59. pyTEMlib/image/__init__.py +37 -0
  60. pyTEMlib/image/image_atoms.py +270 -0
  61. pyTEMlib/image/image_clean.py +197 -0
  62. pyTEMlib/image/image_distortion.py +299 -0
  63. pyTEMlib/image/image_fft.py +277 -0
  64. pyTEMlib/image/image_graph.py +926 -0
  65. pyTEMlib/image/image_registration.py +316 -0
  66. pyTEMlib/image/image_utilities.py +309 -0
  67. pyTEMlib/image/image_window.py +421 -0
  68. pyTEMlib/image_tools.py +154 -928
  69. pyTEMlib/kinematic_scattering.py +1 -1
  70. pyTEMlib/probe_tools.py +1 -1
  71. pyTEMlib/test.py +437 -0
  72. pyTEMlib/utilities.py +314 -0
  73. pyTEMlib/version.py +2 -3
  74. pyTEMlib/xrpa_x_sections.py +14 -10
  75. {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
  76. pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
  77. {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
  78. pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
  79. pyTEMlib/core_loss_widget.py +0 -721
  80. pyTEMlib/eels_dialog.py +0 -754
  81. pyTEMlib/eels_dialog_utilities.py +0 -1199
  82. pyTEMlib/eels_tools.py +0 -2359
  83. pyTEMlib/file_tools_qt.py +0 -193
  84. pyTEMlib/image_dialog.py +0 -158
  85. pyTEMlib/image_dlg.py +0 -146
  86. pyTEMlib/info_widget.py +0 -1086
  87. pyTEMlib/info_widget3.py +0 -1120
  88. pyTEMlib/low_loss_widget.py +0 -479
  89. pyTEMlib/peak_dialog.py +0 -1129
  90. pyTEMlib/peak_dlg.py +0 -286
  91. pytemlib-0.2025.4.2.dist-info/RECORD +0 -38
  92. pytemlib-0.2025.4.2.dist-info/top_level.txt +0 -1
  93. {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
  94. {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,33 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ PyTEMlib
4
+ --------
5
+ A Python package for analyzing and processing transmission electron microscopy (TEM) data.
6
+ This package provides tools for data quantification through a model-based approach,
7
+ including functionalities for imaging, spectra analysis, and data visualization.
8
+
9
+ The package is part of the pycrosccopy ecosystem and the dataformat is based on sidpy,
10
+ the fileformat is based on pyNSDI.
11
+ Created on Sat Jan 19 10:07:35 2019
12
+ Update on Sun Jul 20 2025
13
+
14
+ @author: gduscher
15
+ """
16
+ from .version import __version__
17
+
18
+ from . import file_tools
19
+ from . import image_tools
20
+ from .image import image_atoms as atom_tools
21
+ from . import graph_tools
22
+ from . import probe_tools
23
+ from . import eels_tools
24
+ from . import eds_tools
25
+ from . import crystal_tools
26
+ from . import kinematic_scattering
27
+ from . import dynamic_scattering
28
+ from .config_dir import config_path
29
+
30
+ __all__ = ['__version__', 'file_tools', 'image_tools', 'atom_tools',
31
+ 'graph_tools', 'probe_tools', 'eels_tools', 'eds_tools',
32
+ 'crystal_tools', 'kinematic_scattering', 'dynamic_scattering', 'config_path']
33
+ __author__ = 'Gerd Duscher'
@@ -0,0 +1,640 @@
1
+ """Figures and Animations for TEM in jupyter notebooks
2
+ part of MSE 672 course at UTK
3
+
4
+ Author: Gerd Duscher
5
+ revision: 01/11/2021
6
+ 03/17/2021 added Aberration Animation
7
+ """
8
+
9
+ import numpy as np
10
+ import matplotlib.pyplot as plt
11
+ from matplotlib import patches
12
+
13
+ from ipywidgets import widgets
14
+ from IPython.display import display
15
+
16
+ import pyTEMlib.kinematic_scattering as ks
17
+
18
+
19
+ def geometric_ray_diagram(focal_length=1., magnification=False):
20
+ """ Sketch of geometric ray diagram od one lens
21
+
22
+ Parameters
23
+ ----------
24
+ focal_length: float
25
+ focal length of lens
26
+ magnification: boolean
27
+ draw magnification on the side
28
+
29
+ Returns
30
+ -------
31
+ matplotlib figure
32
+ """
33
+
34
+ f = focal_length
35
+
36
+ u = 1.5
37
+ v = 1 / (1 / f - 1 / u)
38
+ m = v / u
39
+ if magnification:
40
+ line_strong = .5
41
+ else:
42
+ line_strong = 2
43
+
44
+ x = 0.4
45
+
46
+ fig, ax = plt.subplots()
47
+
48
+ # add an ellipse
49
+ ellipse = patches.Ellipse((0.0, 0.0), 3.4, 0.3, alpha=0.3, color='blue')
50
+ ax.add_patch(ellipse)
51
+ ax.plot([1.5, -1.5], [0, 0], '--', color='black')
52
+ ax.plot([0, 0], [u, -v], '--', color='black')
53
+ single_prop = {"arrowstyle": '->', "shrinkA": 0, "shrinkB": 0}
54
+ double_prop = {"arrowstyle": "<->", "shrinkA": 0, "shrinkB": 0}
55
+
56
+ if magnification:
57
+ ax.annotate("", xy=(-x, u), xytext=(x, u), arrowprops=single_prop)
58
+ ax.annotate("", xy=(x * m, -v), xytext=(-x * m, -v), arrowprops=single_prop)
59
+
60
+ else:
61
+ ax.annotate("", xy=(-x, u), xytext=(0, u), arrowprops=single_prop)
62
+ ax.annotate("", xy=(x * m, -v), xytext=(0, -v), arrowprops=single_prop)
63
+
64
+ ax.text(x + 0.1, u, 'object plane', va='center')
65
+ ax.plot([1, -1], [-f, -f], '--', color='black')
66
+ ax.text(1.1, -f, 'back focal\n plane', va='center')
67
+ ax.text(x * m + 0.1, -v, 'image plane', va='center')
68
+
69
+ ax.annotate("", xy=(-.9, 0), xytext=(-.9, -f), arrowprops=double_prop)
70
+ ax.text(-1, -f / 2, 'f')
71
+ if magnification:
72
+ ax.annotate("", xy=(-1.8, 0), xytext=(-1.8, -v), arrowprops=double_prop)
73
+ ax.text(-1.7, -v / 2, 'v')
74
+ ax.annotate("", xy=(-1.8, 0), xytext=(-1.8, u), arrowprops=double_prop)
75
+ ax.text(-1.7, u / 2, 'u')
76
+
77
+ ax.plot([-x, x * m], [u, -v], color='black', linewidth=line_strong)
78
+ ax.plot([-x, -x], [u, 0], color='black', linewidth=line_strong)
79
+ ax.plot([-x, x * m], [0, -v], color='black', linewidth=line_strong)
80
+
81
+ ax.plot([-x, -2 * x], [u, 0], color='black', linewidth=0.5)
82
+ ax.plot([-2 * x, x * m], [0, -v], color='black', linewidth=0.5)
83
+ if magnification:
84
+ ax.plot([x, -x * m], [u, -v], color='black', linewidth=0.5)
85
+ ax.plot([x, x], [u, 0], color='black', linewidth=0.5)
86
+ ax.plot([x, -x * m], [0, -v], color='black', linewidth=0.5)
87
+
88
+ ax.plot([x, 2 * x], [u, 0], color='black', linewidth=0.5)
89
+ ax.plot([2 * x, -x * m], [0, -v], color='black', linewidth=0.5)
90
+ else:
91
+ ax.plot([-x, x * m], [u, 0], color='black', linewidth=0.5)
92
+ ax.plot([x * m, x * m], [0, -v], color='black', linewidth=0.5)
93
+
94
+ ax.set_xlim(-2, 3)
95
+ ax.set_ylim(-3.5, 2)
96
+ ax.set_aspect('equal')
97
+
98
+
99
+ # ----------------------------------------------------------------
100
+ # Modified from Michael Fairchild :simply draws a thin-lens at the provided location parameters:
101
+ # - z: location along the optical axis (in mm)
102
+ # - f: focal length (in mm, can be negative if div. lens)
103
+ # - diam: lens diameter in mm
104
+ # - lens_labels: label to identify the lens on the drawing
105
+ # ----------------------------------------------------------------
106
+ def add_lens(z, f, diam, lens_labels):
107
+ """add lens to propagate beam plot"""
108
+ ww, tw, rad = diam / 10.0, diam / 3.0, diam / 2.0
109
+ plt.plot([z, z], [-rad, rad], 'k', linewidth=2)
110
+ plt.plot([z, z + tw], [-rad, -rad + np.sign(f) * ww], 'k', linewidth=2)
111
+ plt.plot([z, z - tw], [-rad, -rad + np.sign(f) * ww], 'k', linewidth=2)
112
+ plt.plot([z, z + tw], [rad, rad - np.sign(f) * ww], 'k', linewidth=2)
113
+ plt.plot([z, z - tw], [rad, rad - np.sign(f) * ww], 'k', linewidth=2)
114
+ plt.plot([z + f, z + f], [-ww, ww], 'k', linewidth=2)
115
+ plt.plot([z - f, z - f], [-ww, ww], 'k', linewidth=2)
116
+ plt.text(z, rad + 5.0, lens_labels, fontsize=12)
117
+ plt.text(z, rad + 2.0, 'f=' + str(int(f)), fontsize=10)
118
+
119
+
120
+ def add_aperture(z, diam, radius, lens_labels):
121
+ """add aperture to propagate beam plot"""
122
+
123
+ # ww, tw, rad = diam / 10.0, diam / 3.0, diam / 2.0
124
+ rad = diam / 2
125
+ radius = radius / 2
126
+ plt.plot([z, z], [-rad, -radius], 'k', linewidth=2)
127
+ plt.plot([z, z], [rad, radius], 'k', linewidth=2)
128
+ plt.text(z, -rad - 2.0, lens_labels, fontsize=12)
129
+
130
+
131
+ def propagate_beam(source_position, numerical_aperture, number_of_rays,
132
+ lens_positions, focal_lengths,
133
+ lens_labels='', color='b'):
134
+ """geometrical propagation of light rays from given source
135
+
136
+ Parameters
137
+ ----------
138
+ source_position: list
139
+ location of the source (z0, x0) along and off axis (in mm)
140
+ numerical_aperture: float
141
+ numerical aperture of the beam (in degrees)
142
+ number_of_rays: int
143
+ number of rays to trace
144
+ lens_positions: numpy array
145
+ array with the location of the lenses
146
+ focal_lengths: numpy array
147
+ array with the focal length of lenses
148
+ lens_labels: list of string
149
+ label for the nature of lenses
150
+ color: str
151
+ color of the rays on plot
152
+ """
153
+
154
+ plt.figure()
155
+ z_max = 1600.
156
+
157
+ # aperture (maximum angle) in radians
158
+ apa = numerical_aperture * np.pi / 180.0
159
+
160
+ for i in range(np.size(lens_positions)):
161
+ add_lens(lens_positions[i], focal_lengths[i], 25, lens_labels[i])
162
+
163
+ add_aperture(840, 25, 7, 'CA')
164
+
165
+ # position of source is z0,x0
166
+ z0 = source_position[0]
167
+ if np.size(source_position) == 2:
168
+ x0 = source_position[1]
169
+ else:
170
+ x0 = 0.0
171
+
172
+ # list of lens positions
173
+ zl1, ff1 = lens_positions[(z0 < lens_positions)], focal_lengths[(z0 < lens_positions)]
174
+ nl = np.size(zl1) # number of lenses
175
+
176
+ zz, xx, tani = np.zeros(nl + 2), np.zeros(nl + 2), np.zeros(nl + 2)
177
+ tan0 = np.tan(apa / 2.0) - np.tan(apa) * np.arange(number_of_rays) / (number_of_rays - 1)
178
+
179
+ for i in range(number_of_rays):
180
+ tani[0] = tan0[i] # initial incidence angle
181
+ zz[0], xx[0] = z0, x0
182
+ for j in range(nl):
183
+ zz[j + 1] = zl1[j]
184
+ xx[j + 1] = xx[j] + (zz[j + 1] - zz[j]) * tani[j]
185
+ tani[j + 1] = tani[j] - xx[j + 1] / ff1[j]
186
+
187
+ zz[nl + 1] = z_max
188
+ xx[nl + 1] = xx[nl] + (zz[nl + 1] - zz[nl]) * tani[nl]
189
+ plt.plot(zz, xx, color)
190
+ plt.axis([-20, z_max, -20, 20])
191
+
192
+
193
+ def deficient_holz_line(exact_bragg=False, shift=False, laue_zone=1, color='black'):
194
+ """
195
+ Ewald sphere construction to explain Laue Circle and deficient HOLZ lines
196
+
197
+ Parameters:
198
+ exact_bragg: boolean
199
+ whether to tilt into exact Bragg condition or along zone axis
200
+ shift: boolean
201
+ whether to shift exact Bragg-condition onto zone axis origin
202
+ laue_zone: int
203
+ first or second Laue zone only
204
+ color: string
205
+ color of wave vectors and Ewald sphere
206
+ """
207
+
208
+ k_0 = [0, 1 / ks.get_wavelength(600)]
209
+
210
+ d = 5. # lattice parameter in nm
211
+
212
+ if laue_zone == 0:
213
+ s_g = 1 / d + 0.06
214
+ else:
215
+ s_g = .1
216
+
217
+ g = np.linspace(-5, 6, 12) * 1 / d
218
+ g_d = np.array([5. / d + laue_zone * 1 / d / 2, laue_zone * 1 / d])
219
+ g_sg = g_d.copy()
220
+ g_sg[1] = g_d[1] + s_g # point on Ewald sphere
221
+
222
+ # reciprocal lattice
223
+ plt.scatter(g[:-1], [0] * 11, color='red')
224
+ plt.scatter(g - 1 / d / 2, [1 / d] * 12, color='blue')
225
+
226
+ shift_x = shift_y = 0.
227
+ d_theta = d_theta1 = d_theta2 = 0
228
+
229
+ if exact_bragg:
230
+
231
+ d_theta1 = np.arctan((1 / d * laue_zone + s_g) / g_d[0])
232
+ d_theta2 = np.arctan((1 / d * laue_zone) / g_d[0])
233
+ d_theta = -(d_theta1 - d_theta2)
234
+ s_g = 0
235
+ s = np.sin(d_theta)
236
+ c = np.cos(d_theta)
237
+ k_0 = [-s * k_0[1], c * k_0[1]]
238
+ if shift:
239
+ shift_x = -k_0[0]
240
+ shift_y = np.linalg.norm(k_0) - k_0[1]
241
+ d_theta = np.degrees(d_theta)
242
+
243
+ k_0[0] += shift_x
244
+ k_0[1] += shift_y
245
+
246
+ # Ewald Sphere
247
+ ewald_sphere = patches.Circle((k_0[0], k_0[1]), radius=np.linalg.norm(k_0),
248
+ clip_on=False, zorder=10, linewidth=1,
249
+ edgecolor=color, fill=False)
250
+ plt.gca().add_artist(ewald_sphere)
251
+
252
+ plt.gca().arrow(g[-1] + .1 / d / 4, 1 / d / 2, 0, 1 / d / 2, head_width=0.03,
253
+ head_length=0.04, fc='k', ec='k', length_includes_head=True)
254
+ plt.gca().arrow(g[-1] + .1 / d / 4, 1 / d / 2, 0, -1 / d / 2, head_width=0.03,
255
+ head_length=0.04, fc='k', ec='k', length_includes_head=True)
256
+ plt.gca().annotate("$|g_{HOLZ}|$", xytext=(g[-1] + .1 / d / 3, 1 / d / 3),
257
+ xy=(g[-1] + 1 / d / 3, 1 / d / 3))
258
+ plt.scatter(k_0[0], k_0[1])
259
+ plt.gca().arrow(k_0[0], k_0[1], -k_0[0] + shift_x, -k_0[1] + shift_y, head_width=0.03,
260
+ head_length=0.04, fc=color, ec=color, length_includes_head=True)
261
+ plt.gca().annotate("K$_0$", xytext=(k_0[0] / 2, k_0[1] / 3), xy=(k_0[0] / 2, k_0[1] / 2))
262
+
263
+ # K_d Bragg of HOLZ reflection
264
+ plt.gca().arrow(k_0[0], k_0[1], -k_0[0] + g_d[0] + shift_x, -k_0[1] + g_d[1] + s_g + shift_y,
265
+ head_width=0.03, head_length=0.04, fc=color, ec=color,
266
+ length_includes_head=True)
267
+ plt.gca().annotate("K$_d$", xytext=(k_0[0] + (g_d[0] - k_0[0]) / 2, k_0[1] / 2),
268
+ xy=(6.5 / d / 2, k_0[1] / 2))
269
+
270
+ # s_g excitation Error of HOLZ reflection
271
+ if s_g > 0:
272
+ plt.gca().arrow(g_d[0], g_d[1], 0, s_g, head_width=0.03, head_length=0.04, fc='k',
273
+ ec='k', length_includes_head=True)
274
+ plt.gca().annotate("s$_g$", xytext=(g_d[0] * 1.01, g_d[1] + s_g / 3),
275
+ xy=(g_d[0] * 1.01, g_d[1] + s_g / 3))
276
+
277
+ # Bragg angle
278
+ g_sg = g_d
279
+ g_sg[1] = g_d[1] + s_g
280
+ plt.plot([0 + shift_x, g_sg[0] + shift_x], [0 + shift_y, g_d[1] + shift_y], color=color, linewidth=1, alpha=0.5,
281
+ linestyle='--')
282
+ plt.plot([k_0[0], g_sg[0] / 2 + shift_x], [k_0[1], g_sg[1] / 2 + shift_y], color=color, linewidth=1, alpha=0.5,
283
+ linestyle='--')
284
+ # d_theta = np.degrees(np.arctan(k_0[0]/k_0[1]))
285
+ bragg_angle = patches.Arc((k_0[0], k_0[1]), width=k_0[1], height=k_0[1], theta1=-90 + d_theta,
286
+ theta2=-90 + d_theta + np.degrees(np.arcsin(np.linalg.norm(g_sg / 2) / k_0[1])), fc=color,
287
+ ec=color)
288
+
289
+ plt.gca().annotate(r"$\theta $", xytext=(k_0[0] / 1.3, k_0[1] / 1.5), xy=(k_0[0] / 2 + g_d[0] / 4, k_0[1] / 2))
290
+ plt.gca().add_patch(bragg_angle)
291
+
292
+ # deviation/tilt angle
293
+ if np.abs(d_theta) > 0:
294
+ if shift:
295
+ deviation_angle = patches.Arc((k_0[0], k_0[1]), width=k_0[1] * 1.5, height=k_0[1] * 1.5,
296
+ theta1=-90 + d_theta,
297
+ theta2=-90,
298
+ fc=color, ec=color, linewidth=3)
299
+ plt.gca().annotate(r"$d \theta $", xytext=(k_0[0] - .13, k_0[1] / 3.7),
300
+ xy=(k_0[0] + g_d[0] / 4, k_0[1] / 2))
301
+ plt.gca().arrow(shift_x, -.2, 0, .2, head_width=0.05, head_length=0.06, fc=color, ec='black',
302
+ length_includes_head=True, linewidth=3)
303
+ plt.gca().annotate("deficient line", xytext=(shift_x * 2, -.2), xy=(shift_x, 0))
304
+ else:
305
+ deviation_angle = patches.Arc((0, 0), width=k_0[1], height=k_0[1],
306
+ theta1=np.degrees(d_theta2),
307
+ theta2=np.degrees(d_theta1),
308
+ fc=color, ec=color, linewidth=3)
309
+ plt.gca().annotate(r"$d \theta $", xytext=(g_d[0] * .8, 1 / d / 3), xy=(g_d[0], 1 / d))
310
+
311
+ plt.gca().add_patch(deviation_angle)
312
+ plt.gca().set_aspect('equal')
313
+ plt.gca().set_ylim(-.5, 2.2)
314
+ plt.gca().set_xlim(-1.1, 1.6)
315
+
316
+
317
+ def deficient_kikuchi_line(s_g=0., color_b='black'):
318
+ """Draw the deficient Kikuchi line in the plot."""
319
+ k_len = 1 / ks.get_wavelength(20)
320
+ d = 2 # lattice parameter in nm
321
+
322
+ g = np.linspace(-2, 2, 5) * 1 / d
323
+ g_d = np.array([1 / d, 0])
324
+
325
+ # reciprocal lattice
326
+ plt.scatter(g, [0] * 5, color='blue')
327
+
328
+ alpha = -np.arctan(s_g / g_d[0])
329
+ theta = -np.arcsin(g_d[0] / 2 / k_len)
330
+
331
+ k_0 = np.array([-np.sin(theta - alpha) * k_len, np.cos(theta - alpha) * k_len])
332
+ k_d = np.array([-np.sin(-theta - alpha) * k_len, np.cos(-theta - alpha) * k_len])
333
+ k_i = np.array([-np.sin(theta - alpha) * 1., np.cos(theta - alpha) * 1.])
334
+ k_i_t = np.array([-np.sin(-alpha), np.cos(-alpha)])
335
+
336
+ kk_e = np.array([-np.sin(-theta) * k_len, np.cos(-theta) * k_len])
337
+ kk_d = np.array([-np.sin(theta) * k_len, np.cos(theta) * k_len])
338
+
339
+ # Ewald Sphere
340
+ ewald_sphere = patches.Circle((k_0[0], k_0[1]), radius=np.linalg.norm(k_0), clip_on=False, zorder=10, linewidth=1,
341
+ edgecolor=color_b, fill=False)
342
+ plt.gca().add_artist(ewald_sphere)
343
+
344
+ # K_0
345
+ plt.plot([k_0[0], k_0[0]], [k_0[1], k_0[1] + .4], color='gray', linestyle='-', alpha=0.3)
346
+
347
+ plt.gca().arrow(k_0[0] + k_i[0], k_0[1] + k_i[1], -k_i[0], -k_i[1], head_width=0.01, head_length=0.015, fc=color_b,
348
+ ec=color_b, length_includes_head=True)
349
+ plt.plot([k_0[0] + k_i_t[0], k_0[0] - k_i_t[0]], [k_0[1] + k_i_t[1], k_0[1] - k_i_t[1]], color='black',
350
+ linestyle='--', alpha=0.5)
351
+ plt.scatter(k_0[0], k_0[1], color='black')
352
+ plt.gca().arrow(k_0[0], k_0[1], -k_0[0], -k_0[1], head_width=0.01, head_length=0.015, fc=color_b,
353
+ ec=color_b, length_includes_head=True)
354
+ plt.gca().annotate("K$_0$", xytext=(-k_0[0] / 2, 0), xy=(k_0[0] / 2, 0))
355
+
356
+ plt.gca().arrow(k_0[0], k_0[1], -k_d[0], -k_d[1], head_width=0.01, head_length=0.015, fc=color_b,
357
+ ec=color_b, length_includes_head=True)
358
+ # K_e excess line
359
+ plt.gca().arrow(k_0[0], k_0[1], -kk_e[0], -kk_e[1], head_width=0.01, head_length=0.015, fc='red',
360
+ ec='red', length_includes_head=True)
361
+ plt.gca().annotate("excess", xytext=(k_0[0] - kk_e[0], -1), xy=(-kk_e[0] + k_0[0], 0))
362
+ plt.plot([k_0[0] - kk_e[0], k_0[0] - kk_e[0]], [-.1, .1], color='red')
363
+
364
+ # k_d deficient line
365
+ plt.gca().arrow(k_0[0], k_0[1], -kk_d[0], -kk_d[1], head_width=0.01, head_length=0.015, fc='blue',
366
+ ec='blue', length_includes_head=True)
367
+ plt.plot([k_0[0] - kk_d[0], k_0[0] - kk_d[0]], [-.1, .1], color='blue')
368
+ plt.gca().annotate("deficient", xytext=(k_0[0] - kk_d[0], -1), xy=(k_0[0] - kk_d[0], 0))
369
+
370
+ # s_g excitation Error of HOLZ reflection
371
+ plt.gca().arrow(g_d[0], g_d[1], 0, s_g, head_width=0.01, head_length=0.015, fc='k',
372
+ ec='k', length_includes_head=True)
373
+ plt.gca().annotate("s$_g$", xytext=(g_d[0] * 1.01, g_d[1] + s_g / 3), xy=(g_d[0] * 1.01, g_d[1] + s_g / 3))
374
+
375
+ theta = np.degrees(theta)
376
+ alpha = np.degrees(alpha)
377
+
378
+ bragg_angle = patches.Arc((k_0[0], k_0[1]), width=.55, height=.55,
379
+ theta1=90 + theta - alpha, theta2=90 - alpha, fc='black', ec='black')
380
+ if alpha > 0:
381
+ deviation_angle = patches.Arc((k_0[0], k_0[1]), width=.6, height=.6,
382
+ theta1=90 - alpha, theta2=90, fc='black', ec='red')
383
+ else:
384
+ deviation_angle = patches.Arc((k_0[0], k_0[1]), width=.6, height=.6,
385
+ theta1=90, theta2=90 - alpha, fc='black', ec='red')
386
+
387
+ plt.gca().annotate(r"$\theta$", xytext=(k_0[0] + k_i_t[0] / 20, k_0[1] + .2), xy=(k_0[0] + k_i_t[0], k_0[1] + .2))
388
+ plt.gca().annotate(r"$\alpha$", xytext=(k_0[0] + k_i_t[0] / 10, k_0[1] + .3), xy=(k_0[0] + k_i_t[0], k_0[1] + .3),
389
+ color='red')
390
+ plt.gca().add_patch(bragg_angle)
391
+ plt.gca().add_patch(deviation_angle)
392
+
393
+ plt.gca().set_aspect('equal')
394
+ plt.gca().set_xlabel(r'angle (1/$\AA$)')
395
+ plt.gca().set_ylim(-.1, k_0[1] * 2.2)
396
+ plt.gca().set_xlim(-.2, 1.03)
397
+
398
+
399
+ class InteractiveAberration():
400
+ """
401
+ ### Interactive explanation of aberrations
402
+
403
+ """
404
+
405
+ def __init__(self, horizontal=True):
406
+
407
+ box_layout = widgets.Layout(display='flex',
408
+ flex_flow='row',
409
+ align_items='stretch',
410
+ width='100%')
411
+
412
+ self.words = ['ideal rays', 'aberrated rays', 'aberrated wavefront', 'aberration function']
413
+
414
+ self.buttons = [widgets.ToggleButton(value=False, description=word, disabled=False) for word in self.words]
415
+ box = widgets.Box(children=self.buttons, layout=box_layout)
416
+ display(box)
417
+
418
+ # Button(description='edge_quantification')
419
+ for button in self.buttons:
420
+ button.observe(self.on_button_clicked, 'value') # on_click(self.on_button_clicked)
421
+
422
+ self.figure = plt.figure()
423
+ self.ax = plt.gca()
424
+ self.horizontal = horizontal
425
+ self.ax.set_aspect('equal')
426
+ self.analysis = []
427
+ self.update()
428
+ # self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
429
+
430
+ def on_button_clicked(self, b):
431
+ """Handle button click events to update the analysis options"""
432
+ # print(b['owner'].description)
433
+ selection = b['owner'].description
434
+ if selection in self.analysis:
435
+ self.analysis.remove(selection)
436
+ else:
437
+ self.analysis.append(selection)
438
+ self.update()
439
+
440
+ def update(self):
441
+ """Update the plot based on the selected analysis options"""
442
+ ax = self.ax
443
+ ax.clear()
444
+ selection = self.analysis
445
+ ax.plot([0, 15], [0, 0], color='black')
446
+ ax.plot([9, 9], [-.3, .3], color='black')
447
+ lens = patches.Ellipse((2, 0),
448
+ width=.4,
449
+ height=7,
450
+ facecolor='gray')
451
+ ax.add_patch(lens)
452
+ ax.set_ylim(-6.5, 6.5)
453
+ ax.set_aspect('equal')
454
+
455
+ if self.words[0] in selection:
456
+ color = 'gray'
457
+ ax.plot([0, 2], [1, 1], color=color)
458
+ ax.plot([0, 2], [-1, -1], color=color)
459
+ ax.plot([2, 9], [1, 0], color=color)
460
+ ax.plot([2, 9], [-1, 0], color=color)
461
+
462
+ gauss = patches.Ellipse((9, 0),
463
+ width=12,
464
+ height=12,
465
+ fill=False)
466
+ ax.add_patch(gauss)
467
+
468
+ if self.words[1] in selection:
469
+ color = 'blue'
470
+ ax.plot([0, 2], [2, 2], color=color)
471
+ ax.plot([0, 2], [-2, -2], color=color)
472
+ ax.plot([2, 7], [2, 0], color=color)
473
+ ax.plot([2, 7], [-2, 0], color=color)
474
+ gauss2 = patches.Ellipse((7, 0),
475
+ width=8,
476
+ height=8,
477
+ fill=False,
478
+ color=color, linestyle='--')
479
+ plt.gca().add_patch(gauss2)
480
+
481
+ if self.words[2] in selection:
482
+ color = 'red'
483
+ ax.plot([0, 2], [2, 2], color=color)
484
+ ax.plot([0, 2], [-2, -2], color=color)
485
+ ax.plot([2, 7], [2, 0], color=color)
486
+ ax.plot([2, 7], [-2, 0], color=color)
487
+ ax.plot([0, 2], [1, 1], color=color)
488
+ ax.plot([0, 2], [-1, -1], color=color)
489
+ ax.plot([2, 9], [1, 0], color=color)
490
+ ax.plot([2, 9], [-1, 0], color=color)
491
+ gauss3 = patches.Ellipse((9, 0),
492
+ width=12,
493
+ height=9.7,
494
+ fill=False,
495
+ color=color)
496
+ plt.gca().add_patch(gauss3)
497
+
498
+ if self.words[3] in selection:
499
+ color = 'green'
500
+ x = np.arange(100) / 100 - 6
501
+ x2 = np.arange(100) / 100 * 1.5 - 6
502
+ b = 4.8
503
+ a = 6
504
+ y = np.sqrt(a ** 2 - x ** 2)
505
+ y2 = b / a * np.sqrt(a ** 2 - x2 ** 2)
506
+
507
+ x = np.append(x[::-1], x[1:])
508
+ y = np.append(y[::-1], -y[1:])
509
+ x2 = np.append(x2[::-1], x2[1:])
510
+ y2 = np.append(y2[::-1], -y2[1:])
511
+
512
+ dif = y2 - y
513
+
514
+ x = np.append(x[::-1], x2)
515
+ y = np.append(y[::-1], y2)
516
+ aberration = patches.Polygon(np.array([x + 9, y]).T,
517
+ fill=True,
518
+ color=color, alpha=.5)
519
+
520
+ aberration2 = patches.Polygon(np.array(
521
+ [np.append(np.abs(dif), [0, 0]) * 2 + 2.5, np.append(np.linspace(-3.3, 3.3, len(dif)), [3.3, -3.3])]).T,
522
+ fill=True,
523
+ color=color, alpha=.9)
524
+
525
+ plt.gca().add_patch(aberration)
526
+ plt.gca().add_patch(aberration2)
527
+
528
+
529
+ class InteractiveRonchigramMagnification():
530
+ """
531
+ ### Interactive explanation of magnification
532
+
533
+ """
534
+
535
+ def __init__(self, horizontal=True):
536
+
537
+ box_layout = widgets.Layout(display='flex',
538
+ flex_flow='row',
539
+ align_items='stretch',
540
+ width='100%')
541
+
542
+ self.words = ['ideal rays', 'radial circle rays', 'axial circle rays', 'over-focused rays']
543
+
544
+ self.buttons = [widgets.ToggleButton(value=False, description=word, disabled=False) for word in self.words]
545
+ box = widgets.Box(children=self.buttons, layout=box_layout)
546
+ display(box)
547
+
548
+ # Button(description='edge_quantification')
549
+ for button in self.buttons:
550
+ button.observe(self.on_button_clicked, 'value') # on_click(self.on_button_clicked)
551
+
552
+ self.figure = plt.figure()
553
+ self.ax = plt.gca()
554
+ self.horizontal = horizontal
555
+ self.ax.set_aspect('equal')
556
+ self.analysis = []
557
+ self.update()
558
+ # self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
559
+
560
+ def on_button_clicked(self, b):
561
+ """Handle button click events"""
562
+ # print(b['owner'].description)
563
+ selection = b['owner'].description
564
+ if selection in self.analysis:
565
+ self.analysis.remove(selection)
566
+ else:
567
+ self.analysis.append(selection)
568
+ self.update()
569
+
570
+ def update(self):
571
+ """Update the plot based on the selected analysis options"""
572
+ ax = self.ax
573
+ ax.clear()
574
+ selection = self.analysis
575
+ ax.plot([0, 24], [0, 0], color='black')
576
+ ax.plot([14, 14], [-.3, .3], color='black')
577
+ ax.text(14, 1, s='f')
578
+ lens = patches.Ellipse((4, 0),
579
+ width=.8,
580
+ height=14,
581
+ facecolor='gray')
582
+ ax.add_patch(lens)
583
+ ax.text(4, 8, s='lens')
584
+ sample = patches.Rectangle((10, -2),
585
+ width=.2,
586
+ height=4,
587
+ facecolor='gray')
588
+
589
+ ax.add_patch(sample)
590
+ ax.text(9, 3, s='sample')
591
+ ax.set_ylim(-10, 10)
592
+ ax.set_aspect('equal')
593
+
594
+ if self.words[0] in selection:
595
+ color = 'gray'
596
+ ax.plot([0, 4], [1, 1], color=color)
597
+ ax.plot([0, 4], [-1, -1], color=color)
598
+ ax.plot([4, 24], [1, -1], color=color)
599
+ ax.plot([4, 24], [-1, 1], color=color)
600
+
601
+ circle1 = patches.Ellipse((24, 0), width=.2, height=2, fill=False, color=color)
602
+ ax.add_patch(circle1)
603
+
604
+ if self.words[1] in selection:
605
+ color = 'red'
606
+ ax.plot([0, 4], [3, 3], color=color)
607
+ ax.plot([0, 4], [-3, -3], color=color)
608
+ ax.plot([4, 24], [3, -4], color=color)
609
+ ax.plot([4, 24], [-3, 4], color=color)
610
+ ax.plot([0, 4], [2.5, 2.5], color=color)
611
+ ax.plot([0, 4], [-2.50, -2.5], color=color)
612
+ ax.plot([4, 24], [2.5, -2.8], color=color)
613
+ ax.plot([4, 24], [-2.5, 2.8], color=color)
614
+
615
+ circle2 = patches.Ellipse((24, 0), width=.9, height=8, fill=False, color=color)
616
+ ax.add_patch(circle2)
617
+ circle3 = patches.Ellipse((24, 0), width=.6, height=5.6, fill=False, color=color)
618
+ ax.add_patch(circle3)
619
+ circle3 = patches.Ellipse((24, 0), width=.7, height=7.3, fill=False, color=color, linewidth=5, alpha=.5)
620
+ ax.add_patch(circle3)
621
+
622
+ if self.words[2] in selection:
623
+ color = 'orange'
624
+ ax.plot([0, 4], [4, 4], color=color)
625
+ ax.plot([0, 4], [-4, -4], color=color)
626
+ ax.plot([4, 24], [4, -9.25], color=color)
627
+ ax.plot([4, 24], [-4, 9.25], color=color)
628
+
629
+ circle4 = patches.Ellipse((24, 0), width=2, height=18.5, fill=False, color=color)
630
+ plt.gca().add_patch(circle4)
631
+
632
+ if self.words[3] in selection:
633
+ color = 'green'
634
+ ax.plot([0, 4], [5, 5], color=color, linestyle='--')
635
+ ax.plot([0, 4], [-5, -5], color=color, linestyle='--')
636
+ ax.plot([4, 24], [5, -13], color=color, linestyle='--')
637
+ ax.plot([4, 24], [-5, 13], color=color, linestyle='--')
638
+
639
+ circle6 = patches.Ellipse((24, 0), width=4, height=26, fill=False, color=color, linestyle='--')
640
+ plt.gca().add_patch(circle6)