pyTEMlib 0.2025.12.0__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.
Files changed (92) hide show
  1. build/lib/pyTEMlib/__init__.py +36 -0
  2. build/lib/pyTEMlib/animation.py +667 -0
  3. build/lib/pyTEMlib/atom_tools.py +229 -0
  4. build/lib/pyTEMlib/config_dir.py +43 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +757 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +298 -0
  8. build/lib/pyTEMlib/eds_tools.py +901 -0
  9. build/lib/pyTEMlib/eds_xsections.py +268 -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 +275 -0
  17. build/lib/pyTEMlib/file_tools.py +816 -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 +198 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +278 -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 +308 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +701 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1145 -0
  33. build/lib/pyTEMlib/microscope.py +62 -0
  34. build/lib/pyTEMlib/probe_tools.py +928 -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 +431 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +36 -0
  42. pyTEMlib/animation.py +667 -0
  43. pyTEMlib/atom_tools.py +229 -0
  44. pyTEMlib/config_dir.py +43 -0
  45. pyTEMlib/crystal_tools.py +1219 -0
  46. pyTEMlib/data/k_factors_Bruker_15keV.json +293 -0
  47. pyTEMlib/data/k_factors_Thermo_200keV.json +494 -0
  48. pyTEMlib/data/xrays_X_section_100kV.json +19334 -0
  49. pyTEMlib/data/xrays_X_section_200kV.json +18711 -0
  50. pyTEMlib/data/xrays_X_section_300kV.json +18347 -0
  51. pyTEMlib/data/xrays_X_section_60kV.json +20202 -0
  52. pyTEMlib/diffraction_plot.py +757 -0
  53. pyTEMlib/dynamic_scattering.py +298 -0
  54. pyTEMlib/eds_tools.py +901 -0
  55. pyTEMlib/eds_xsections.py +268 -0
  56. pyTEMlib/eels_tools/__init__.py +44 -0
  57. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  58. pyTEMlib/eels_tools/eels_database.py +134 -0
  59. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  60. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  61. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  62. pyTEMlib/file_reader.py +275 -0
  63. pyTEMlib/file_tools.py +816 -0
  64. pyTEMlib/get_bote_salvat.py +69 -0
  65. pyTEMlib/graph_tools.py +1153 -0
  66. pyTEMlib/graph_viz.py +599 -0
  67. pyTEMlib/image/__init__.py +37 -0
  68. pyTEMlib/image/image_atoms.py +270 -0
  69. pyTEMlib/image/image_clean.py +198 -0
  70. pyTEMlib/image/image_distortion.py +299 -0
  71. pyTEMlib/image/image_fft.py +278 -0
  72. pyTEMlib/image/image_graph.py +926 -0
  73. pyTEMlib/image/image_registration.py +316 -0
  74. pyTEMlib/image/image_utilities.py +308 -0
  75. pyTEMlib/image/image_window.py +421 -0
  76. pyTEMlib/image_tools.py +701 -0
  77. pyTEMlib/interactive_image.py +1 -0
  78. pyTEMlib/kinematic_scattering.py +1145 -0
  79. pyTEMlib/microscope.py +62 -0
  80. pyTEMlib/probe_tools.py +928 -0
  81. pyTEMlib/sidpy_tools.py +153 -0
  82. pyTEMlib/simulation_tools.py +104 -0
  83. pyTEMlib/test.py +437 -0
  84. pyTEMlib/utilities.py +431 -0
  85. pyTEMlib/version.py +5 -0
  86. pyTEMlib/xrpa_x_sections.py +20976 -0
  87. pytemlib-0.2025.12.0.dist-info/METADATA +100 -0
  88. pytemlib-0.2025.12.0.dist-info/RECORD +92 -0
  89. pytemlib-0.2025.12.0.dist-info/WHEEL +5 -0
  90. pytemlib-0.2025.12.0.dist-info/entry_points.txt +2 -0
  91. pytemlib-0.2025.12.0.dist-info/licenses/LICENSE +21 -0
  92. pytemlib-0.2025.12.0.dist-info/top_level.txt +5 -0
pyTEMlib/animation.py ADDED
@@ -0,0 +1,667 @@
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
+ _, 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],
281
+ [0 + shift_y, g_d[1] + shift_y],
282
+ color=color, linewidth=1, alpha=0.5, linestyle='--')
283
+ plt.plot([k_0[0], g_sg[0] / 2 + shift_x],
284
+ [k_0[1], g_sg[1] / 2 + shift_y],
285
+ color=color, linewidth=1, alpha=0.5, linestyle='--')
286
+ # d_theta = np.degrees(np.arctan(k_0[0]/k_0[1]))
287
+ bragg_angle = patches.Arc((k_0[0], k_0[1]), width=k_0[1],
288
+ height=k_0[1], theta1=-90 + d_theta,
289
+ theta2=(-90 + d_theta +
290
+ np.degrees(np.arcsin(np.linalg.norm(g_sg / 2)
291
+ / k_0[1]))),
292
+ fc=color, ec=color)
293
+
294
+ plt.gca().annotate(r"$\theta $", xytext=(k_0[0] / 1.3, k_0[1] / 1.5),
295
+ xy=(k_0[0] / 2 + g_d[0] / 4, k_0[1] / 2))
296
+ plt.gca().add_patch(bragg_angle)
297
+
298
+ # deviation/tilt angle
299
+ if np.abs(d_theta) > 0:
300
+ if shift:
301
+ deviation_angle = patches.Arc((k_0[0], k_0[1]),
302
+ width=k_0[1] * 1.5,
303
+ height=k_0[1] * 1.5,
304
+ theta1=-90 + d_theta,
305
+ theta2=-90,
306
+ fc=color, ec=color, linewidth=3)
307
+ plt.gca().annotate(r"$d \theta $", xytext=(k_0[0] - .13, k_0[1] / 3.7),
308
+ xy=(k_0[0] + g_d[0] / 4, k_0[1] / 2))
309
+ plt.gca().arrow(shift_x, -.2, 0, .2, head_width=0.05, head_length=0.06,
310
+ fc=color, ec='black',
311
+ length_includes_head=True, linewidth=3)
312
+ plt.gca().annotate("deficient line", xytext=(shift_x * 2, -.2), xy=(shift_x, 0))
313
+ else:
314
+ deviation_angle = patches.Arc((0, 0), width=k_0[1], height=k_0[1],
315
+ theta1=np.degrees(d_theta2),
316
+ theta2=np.degrees(d_theta1),
317
+ fc=color, ec=color, linewidth=3)
318
+ plt.gca().annotate(r"$d \theta $", xytext=(g_d[0] * .8, 1 / d / 3), xy=(g_d[0], 1 / d))
319
+
320
+ plt.gca().add_patch(deviation_angle)
321
+ plt.gca().set_aspect('equal')
322
+ plt.gca().set_ylim(-.5, 2.2)
323
+ plt.gca().set_xlim(-1.1, 1.6)
324
+
325
+
326
+ def deficient_kikuchi_line(s_g=0., color_b='black'):
327
+ """Draw the deficient Kikuchi line in the plot."""
328
+ k_len = 1 / ks.get_wavelength(20)
329
+ d = 2 # lattice parameter in nm
330
+
331
+ g = np.linspace(-2, 2, 5) * 1 / d
332
+ g_d = np.array([1 / d, 0])
333
+
334
+ # reciprocal lattice
335
+ plt.scatter(g, [0] * 5, color='blue')
336
+
337
+ alpha = -np.arctan(s_g / g_d[0])
338
+ theta = -np.arcsin(g_d[0] / 2 / k_len)
339
+
340
+ k_0 = np.array([-np.sin(theta - alpha) * k_len, np.cos(theta - alpha) * k_len])
341
+ k_d = np.array([-np.sin(-theta - alpha) * k_len, np.cos(-theta - alpha) * k_len])
342
+ k_i = np.array([-np.sin(theta - alpha) * 1., np.cos(theta - alpha) * 1.])
343
+ k_i_t = np.array([-np.sin(-alpha), np.cos(-alpha)])
344
+
345
+ kk_e = np.array([-np.sin(-theta) * k_len, np.cos(-theta) * k_len])
346
+ kk_d = np.array([-np.sin(theta) * k_len, np.cos(theta) * k_len])
347
+
348
+ # Ewald Sphere
349
+ ewald_sphere = patches.Circle((k_0[0], k_0[1]), radius=np.linalg.norm(k_0),
350
+ clip_on=False, zorder=10, linewidth=1,
351
+ edgecolor=color_b, fill=False)
352
+ plt.gca().add_artist(ewald_sphere)
353
+
354
+ # K_0
355
+ plt.plot([k_0[0], k_0[0]], [k_0[1], k_0[1] + .4],
356
+ color='gray', linestyle='-', alpha=0.3)
357
+
358
+ plt.gca().arrow(k_0[0] + k_i[0], k_0[1] + k_i[1], -k_i[0], -k_i[1],
359
+ head_width=0.01, head_length=0.015, fc=color_b,
360
+ ec=color_b, length_includes_head=True)
361
+ plt.plot([k_0[0] + k_i_t[0], k_0[0] - k_i_t[0]],
362
+ [k_0[1] + k_i_t[1], k_0[1] - k_i_t[1]],
363
+ color='black', linestyle='--', alpha=0.5)
364
+ plt.scatter(k_0[0], k_0[1], color='black')
365
+ plt.gca().arrow(k_0[0], k_0[1], -k_0[0], -k_0[1],
366
+ head_width=0.01, head_length=0.015, fc=color_b,
367
+ ec=color_b, length_includes_head=True)
368
+ plt.gca().annotate("K$_0$", xytext=(-k_0[0] / 2, 0),xy=(k_0[0] / 2, 0))
369
+
370
+ plt.gca().arrow(k_0[0], k_0[1], -k_d[0], -k_d[1],
371
+ head_width=0.01, head_length=0.015, fc=color_b,
372
+ ec=color_b, length_includes_head=True)
373
+ # K_e excess line
374
+ plt.gca().arrow(k_0[0], k_0[1], -kk_e[0], -kk_e[1],
375
+ head_width=0.01, head_length=0.015, fc='red',
376
+ ec='red', length_includes_head=True)
377
+ plt.gca().annotate("excess", xytext=(k_0[0] - kk_e[0], -1),
378
+ xy=(-kk_e[0] + k_0[0], 0))
379
+ plt.plot([k_0[0] - kk_e[0], k_0[0] - kk_e[0]], [-.1, .1], color='red')
380
+
381
+ # k_d deficient line
382
+ plt.gca().arrow(k_0[0], k_0[1], -kk_d[0], -kk_d[1],
383
+ head_width=0.01, head_length=0.015, fc='blue',
384
+ ec='blue', length_includes_head=True)
385
+ plt.plot([k_0[0] - kk_d[0], k_0[0] - kk_d[0]], [-.1, .1], color='blue')
386
+ plt.gca().annotate("deficient", xytext=(k_0[0] - kk_d[0], -1), xy=(k_0[0] - kk_d[0], 0))
387
+
388
+ # s_g excitation Error of HOLZ reflection
389
+ plt.gca().arrow(g_d[0], g_d[1], 0, s_g, head_width=0.01, head_length=0.015, fc='k',
390
+ ec='k', length_includes_head=True)
391
+ plt.gca().annotate("s$_g$", xytext=(g_d[0] * 1.01, g_d[1] + s_g / 3),
392
+ xy=(g_d[0] * 1.01, g_d[1] + s_g / 3))
393
+
394
+ theta = np.degrees(theta)
395
+ alpha = np.degrees(alpha)
396
+
397
+ bragg_angle = patches.Arc((k_0[0], k_0[1]), width=.55, height=.55,
398
+ theta1=90 + theta - alpha, theta2=90 - alpha,
399
+ fc='black', ec='black')
400
+ if alpha > 0:
401
+ deviation_angle = patches.Arc((k_0[0], k_0[1]), width=.6, height=.6,
402
+ theta1=90 - alpha, theta2=90, fc='black', ec='red')
403
+ else:
404
+ deviation_angle = patches.Arc((k_0[0], k_0[1]), width=.6, height=.6,
405
+ theta1=90, theta2=90 - alpha, fc='black', ec='red')
406
+
407
+ plt.gca().annotate(r"$\theta$", xytext=(k_0[0] + k_i_t[0] / 20, k_0[1] + .2),
408
+ xy=(k_0[0] + k_i_t[0], k_0[1] + .2))
409
+ plt.gca().annotate(r"$\alpha$", xytext=(k_0[0] + k_i_t[0] / 10, k_0[1] + .3),
410
+ xy=(k_0[0] + k_i_t[0], k_0[1] + .3),
411
+ color='red')
412
+ plt.gca().add_patch(bragg_angle)
413
+ plt.gca().add_patch(deviation_angle)
414
+
415
+ plt.gca().set_aspect('equal')
416
+ plt.gca().set_xlabel(r'angle (1/$\AA$)')
417
+ plt.gca().set_ylim(-.1, k_0[1] * 2.2)
418
+ plt.gca().set_xlim(-.2, 1.03)
419
+
420
+
421
+ class InteractiveAberration():
422
+ """
423
+ ### Interactive explanation of aberrations
424
+
425
+ """
426
+
427
+ def __init__(self, horizontal=True):
428
+
429
+ box_layout = widgets.Layout(display='flex',
430
+ flex_flow='row',
431
+ align_items='stretch',
432
+ width='100%')
433
+
434
+ self.words = ['ideal rays', 'aberrated rays',
435
+ 'aberrated wavefront', 'aberration function']
436
+
437
+ self.buttons = [widgets.ToggleButton(value=False, description=word,
438
+ disabled=False) for word in self.words]
439
+ box = widgets.Box(children=self.buttons, layout=box_layout)
440
+ display(box)
441
+
442
+ # Button(description='edge_quantification')
443
+ for button in self.buttons:
444
+ button.observe(self.on_button_clicked, 'value')
445
+
446
+ self.figure = plt.figure()
447
+ self.ax = plt.gca()
448
+ self.horizontal = horizontal
449
+ self.ax.set_aspect('equal')
450
+ self.analysis = []
451
+ self.update()
452
+ # self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
453
+
454
+ def on_button_clicked(self, b):
455
+ """Handle button click events to update the analysis options"""
456
+ # print(b['owner'].description)
457
+ selection = b['owner'].description
458
+ if selection in self.analysis:
459
+ self.analysis.remove(selection)
460
+ else:
461
+ self.analysis.append(selection)
462
+ self.update()
463
+
464
+ def update(self):
465
+ """Update the plot based on the selected analysis options"""
466
+ ax = self.ax
467
+ ax.clear()
468
+ selection = self.analysis
469
+ ax.plot([0, 15], [0, 0], color='black')
470
+ ax.plot([9, 9], [-.3, .3], color='black')
471
+ lens = patches.Ellipse((2, 0),
472
+ width=.4,
473
+ height=7,
474
+ facecolor='gray')
475
+ ax.add_patch(lens)
476
+ ax.set_ylim(-6.5, 6.5)
477
+ ax.set_aspect('equal')
478
+
479
+ if self.words[0] in selection:
480
+ color = 'gray'
481
+ ax.plot([0, 2], [1, 1], color=color)
482
+ ax.plot([0, 2], [-1, -1], color=color)
483
+ ax.plot([2, 9], [1, 0], color=color)
484
+ ax.plot([2, 9], [-1, 0], color=color)
485
+
486
+ gauss = patches.Ellipse((9, 0),
487
+ width=12,
488
+ height=12,
489
+ fill=False)
490
+ ax.add_patch(gauss)
491
+
492
+ if self.words[1] in selection:
493
+ color = 'blue'
494
+ ax.plot([0, 2], [2, 2], color=color)
495
+ ax.plot([0, 2], [-2, -2], color=color)
496
+ ax.plot([2, 7], [2, 0], color=color)
497
+ ax.plot([2, 7], [-2, 0], color=color)
498
+ gauss2 = patches.Ellipse((7, 0),
499
+ width=8,
500
+ height=8,
501
+ fill=False,
502
+ color=color, linestyle='--')
503
+ plt.gca().add_patch(gauss2)
504
+
505
+ if self.words[2] in selection:
506
+ color = 'red'
507
+ ax.plot([0, 2], [2, 2], color=color)
508
+ ax.plot([0, 2], [-2, -2], color=color)
509
+ ax.plot([2, 7], [2, 0], color=color)
510
+ ax.plot([2, 7], [-2, 0], color=color)
511
+ ax.plot([0, 2], [1, 1], color=color)
512
+ ax.plot([0, 2], [-1, -1], color=color)
513
+ ax.plot([2, 9], [1, 0], color=color)
514
+ ax.plot([2, 9], [-1, 0], color=color)
515
+ gauss3 = patches.Ellipse((9, 0),
516
+ width=12,
517
+ height=9.7,
518
+ fill=False,
519
+ color=color)
520
+ plt.gca().add_patch(gauss3)
521
+
522
+ if self.words[3] in selection:
523
+ color = 'green'
524
+ x = np.arange(100) / 100 - 6
525
+ x2 = np.arange(100) / 100 * 1.5 - 6
526
+ b = 4.8
527
+ a = 6
528
+ y = np.sqrt(a ** 2 - x ** 2)
529
+ y2 = b / a * np.sqrt(a ** 2 - x2 ** 2)
530
+
531
+ x = np.append(x[::-1], x[1:])
532
+ y = np.append(y[::-1], -y[1:])
533
+ x2 = np.append(x2[::-1], x2[1:])
534
+ y2 = np.append(y2[::-1], -y2[1:])
535
+
536
+ dif = y2 - y
537
+
538
+ x = np.append(x[::-1], x2)
539
+ y = np.append(y[::-1], y2)
540
+ aberration = patches.Polygon(np.array([x + 9, y]).T,
541
+ fill=True,
542
+ color=color, alpha=.5)
543
+
544
+ aberration2 = patches.Polygon(np.array(
545
+ [np.append(np.abs(dif), [0, 0]) * 2 + 2.5,
546
+ np.append(np.linspace(-3.3, 3.3, len(dif)), [3.3, -3.3])]).T,
547
+ fill=True, color=color, alpha=.9)
548
+ plt.gca().add_patch(aberration)
549
+ plt.gca().add_patch(aberration2)
550
+
551
+
552
+ class InteractiveRonchigramMagnification():
553
+ """
554
+ ### Interactive explanation of magnification
555
+
556
+ """
557
+
558
+ def __init__(self, horizontal=True):
559
+
560
+ box_layout = widgets.Layout(display='flex',
561
+ flex_flow='row',
562
+ align_items='stretch',
563
+ width='100%')
564
+
565
+ self.words = ['ideal rays', 'radial circle rays',
566
+ 'axial circle rays', 'over-focused rays']
567
+
568
+ self.buttons = [widgets.ToggleButton(value=False, description=word,
569
+ disabled=False) for word in self.words]
570
+ box = widgets.Box(children=self.buttons, layout=box_layout)
571
+ display(box)
572
+
573
+ # Button(description='edge_quantification')
574
+ for button in self.buttons:
575
+ button.observe(self.on_button_clicked, 'value') # on_click(self.on_button_clicked)
576
+
577
+ self.figure = plt.figure()
578
+ self.ax = plt.gca()
579
+ self.horizontal = horizontal
580
+ self.ax.set_aspect('equal')
581
+ self.analysis = []
582
+ self.update()
583
+ # self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
584
+
585
+ def on_button_clicked(self, b):
586
+ """Handle button click events"""
587
+ # print(b['owner'].description)
588
+ selection = b['owner'].description
589
+ if selection in self.analysis:
590
+ self.analysis.remove(selection)
591
+ else:
592
+ self.analysis.append(selection)
593
+ self.update()
594
+
595
+ def update(self):
596
+ """Update the plot based on the selected analysis options"""
597
+ ax = self.ax
598
+ ax.clear()
599
+ selection = self.analysis
600
+ ax.plot([0, 24], [0, 0], color='black')
601
+ ax.plot([14, 14], [-.3, .3], color='black')
602
+ ax.text(14, 1, s='f')
603
+ lens = patches.Ellipse((4, 0),
604
+ width=.8,
605
+ height=14,
606
+ facecolor='gray')
607
+ ax.add_patch(lens)
608
+ ax.text(4, 8, s='lens')
609
+ sample = patches.Rectangle((10, -2),
610
+ width=.2,
611
+ height=4,
612
+ facecolor='gray')
613
+
614
+ ax.add_patch(sample)
615
+ ax.text(9, 3, s='sample')
616
+ ax.set_ylim(-10, 10)
617
+ ax.set_aspect('equal')
618
+
619
+ if self.words[0] in selection:
620
+ color = 'gray'
621
+ ax.plot([0, 4], [1, 1], color=color)
622
+ ax.plot([0, 4], [-1, -1], color=color)
623
+ ax.plot([4, 24], [1, -1], color=color)
624
+ ax.plot([4, 24], [-1, 1], color=color)
625
+
626
+ circle1 = patches.Ellipse((24, 0), width=.2, height=2, fill=False, color=color)
627
+ ax.add_patch(circle1)
628
+
629
+ if self.words[1] in selection:
630
+ color = 'red'
631
+ ax.plot([0, 4], [3, 3], color=color)
632
+ ax.plot([0, 4], [-3, -3], color=color)
633
+ ax.plot([4, 24], [3, -4], color=color)
634
+ ax.plot([4, 24], [-3, 4], color=color)
635
+ ax.plot([0, 4], [2.5, 2.5], color=color)
636
+ ax.plot([0, 4], [-2.50, -2.5], color=color)
637
+ ax.plot([4, 24], [2.5, -2.8], color=color)
638
+ ax.plot([4, 24], [-2.5, 2.8], color=color)
639
+
640
+ circle2 = patches.Ellipse((24, 0), width=.9, height=8, fill=False, color=color)
641
+ ax.add_patch(circle2)
642
+ circle3 = patches.Ellipse((24, 0), width=.6, height=5.6, fill=False, color=color)
643
+ ax.add_patch(circle3)
644
+ circle3 = patches.Ellipse((24, 0), width=.7, height=7.3,
645
+ fill=False, color=color, linewidth=5, alpha=.5)
646
+ ax.add_patch(circle3)
647
+
648
+ if self.words[2] in selection:
649
+ color = 'orange'
650
+ ax.plot([0, 4], [4, 4], color=color)
651
+ ax.plot([0, 4], [-4, -4], color=color)
652
+ ax.plot([4, 24], [4, -9.25], color=color)
653
+ ax.plot([4, 24], [-4, 9.25], color=color)
654
+
655
+ circle4 = patches.Ellipse((24, 0), width=2, height=18.5, fill=False, color=color)
656
+ plt.gca().add_patch(circle4)
657
+
658
+ if self.words[3] in selection:
659
+ color = 'green'
660
+ ax.plot([0, 4], [5, 5], color=color, linestyle='--')
661
+ ax.plot([0, 4], [-5, -5], color=color, linestyle='--')
662
+ ax.plot([4, 24], [5, -13], color=color, linestyle='--')
663
+ ax.plot([4, 24], [-5, 13], color=color, linestyle='--')
664
+
665
+ circle6 = patches.Ellipse((24, 0), width=4, height=26,
666
+ fill=False, color=color, linestyle='--')
667
+ plt.gca().add_patch(circle6)