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,756 @@
1
+ import matplotlib.pyplot as plt
2
+
3
+ import matplotlib.patches as patches
4
+ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar
5
+ from matplotlib.patches import Circle # , Ellipse, Rectangle
6
+ from matplotlib.collections import PatchCollection
7
+ from matplotlib.lines import Line2D
8
+
9
+ from scipy.interpolate import interp1d
10
+ from scipy.ndimage import map_coordinates, geometric_transform
11
+
12
+ import ase
13
+ import numpy as np
14
+ import sidpy
15
+
16
+ # ##################################
17
+ # Plot Reciprocal Unit Cell in 2D #
18
+ # ##################################
19
+
20
+
21
+ def plot_reciprocal_unit_cell_2D(atoms):
22
+ """Plot # unit cell in reciprocal space in 2D"""
23
+
24
+ reciprocal_unit_cell = atoms.get_reciprocal_cell()
25
+
26
+ # ignore y direction
27
+
28
+ x = [reciprocal_unit_cell[0, 0], reciprocal_unit_cell[0, 0], reciprocal_unit_cell[1, 0], reciprocal_unit_cell[1, 0]]
29
+ z = [reciprocal_unit_cell[0, 2], reciprocal_unit_cell[2, 2], reciprocal_unit_cell[2, 2], reciprocal_unit_cell[0, 2]]
30
+
31
+ # Plot 2D
32
+ fig = plt.figure()
33
+ ax = plt.gca() # current axis
34
+
35
+ ax.scatter(x, z, c='red', s=80)
36
+ ax.add_patch(
37
+ patches.Rectangle(
38
+ (0, 0), # (x,y)
39
+ reciprocal_unit_cell[0, 0], # width
40
+ reciprocal_unit_cell[2, 2], # height
41
+ fill=False # remove background
42
+ )
43
+ )
44
+ ax.add_patch(
45
+ patches.FancyArrow(0, 0, reciprocal_unit_cell[0, 0], 0, width=0.02,
46
+ color='black',
47
+ head_width=0.08, # Default: 3 * width
48
+ head_length=0.1, # Default: 1.5 * head_width
49
+ length_includes_head=True # Default: False
50
+ )
51
+ )
52
+ ax.add_patch(
53
+ patches.FancyArrow(0, 0, 0, reciprocal_unit_cell[2, 2], width=0.02,
54
+ color='black',
55
+ head_width=0.08, # Default: 3 * width
56
+ head_length=0.1, # Default: 1.5 * head_width
57
+ length_includes_head=True # Default: False
58
+ )
59
+ )
60
+
61
+ plt.xlabel('x 1/nm')
62
+ plt.ylabel('z 1/nm')
63
+ ax.axis('equal')
64
+ # plt.title('Unit Cell in Reciprocal Space of {0}'.format(tags['crystal']) )
65
+ # texfig.savefig("recip_unit_cell")
66
+ # fig.savefig('recip_unit_cell.jpg', dpi=90, bbox_inches='tight')
67
+ plt.show()
68
+ return fig
69
+
70
+
71
+ # ####################
72
+ # Plot SAED Pattern #
73
+ # ####################
74
+ def plotSAED_parameter(gray=False):
75
+
76
+ tags = {'convergence_angle_A-1': 0,
77
+ 'background': 'white', # 'white' 'grey'
78
+ 'color_map': 'plasma', # ,'cubehelix' #'Greys'#'plasma'
79
+ 'color_reflections': 'intensity'}
80
+
81
+ if gray:
82
+ tags['color_map'] = 'gray'
83
+ tags['background'] = '#303030' # 'darkgray'
84
+ tags['color_reflections'] = 'intensity'
85
+ tags['plot_HOLZ'] = 0
86
+ tags['plot_HOLZ_excess'] = 0
87
+ tags['plot_Kikuchi'] = 1
88
+ tags['plot_reflections'] = 1
89
+
90
+ tags['color_Kikuchi'] = 'green'
91
+
92
+ tags['linewidth_HOLZ'] = -1 # -1: linewidth according to intensity (structure factor F^2
93
+ tags['linewidth_Kikuchi'] = -1 # -1: linewidth according to intensity (structure factor F^2
94
+
95
+ tags['label_HOLZ'] = 0
96
+ tags['label_Kikuchi'] = 0
97
+ tags['label_reflections'] = 0
98
+
99
+ tags['label_color'] = 'white'
100
+ tags['label_size'] = 10
101
+
102
+ tags['color_Laue_Zones'] = ['red', 'blue', 'green', 'blue', 'green'] # , 'green', 'red'] #for OLZ give a sequence
103
+ tags['color_zero'] = 'red' # 'None' #'white'
104
+ tags['color_ring_zero'] = 'None' # 'Red' #'white' #, 'None'
105
+ tags['width_ring_zero'] = .2
106
+
107
+ # plotDiffPattern(tags,True)
108
+ tags['plot_rotation'] = 0. # degree
109
+ tags['plot_shift_x'] = -0.0
110
+ tags['plot_shift_y'] = 0.0
111
+
112
+ return tags
113
+
114
+
115
+ ########################
116
+ # Plot Kikuchi Pattern #
117
+ ########################
118
+ def plotKikuchi(grey=False):
119
+ tags = {'background': 'black', # 'white' 'grey'
120
+ 'color_map': 'plasma', # ,'cubehelix'#'Greys'#'plasma'
121
+ 'color_reflections': 'intensity',
122
+ 'plot_HOLZ': 0,
123
+ 'plot_HOLZ_excess': 0,
124
+ 'plot_Kikuchi': 1,
125
+ 'plot_reflections': 1,
126
+ 'label_HOLZ': 0,
127
+ 'label_Kikuchi': 0,
128
+ 'label_reflections': 0,
129
+ 'label_color': 'white',
130
+ 'label_size': 10,
131
+ 'color_Kikuchi': 'green',
132
+ 'linewidth_HOLZ': -1, # -1: linewidth according to intensity (structure factor F^2
133
+ 'linewidth_Kikuchi': -1, # -1: linewidth according to intensity (structure factor F^2
134
+ 'color_Laue_Zones': ['red', 'blue', 'green', 'blue', 'green'], # , 'green', 'red'] #for OLZ give a sequence
135
+ 'color_zero': 'white', # 'None' #'white'
136
+ 'color_ring_zero': 'None', # 'Red' #'white' #, 'None'
137
+ 'width_ring_zero': 2}
138
+
139
+ if grey:
140
+ tags['color_map'] = 'gray'
141
+ tags['background'] = '#303030' # 'darkgray'
142
+ tags['color_reflections'] = 'intensity'
143
+
144
+ return tags
145
+
146
+ # plotDiffPattern(tags,True)
147
+
148
+
149
+ ########################
150
+ # Plot HOLZ Pattern #
151
+ ########################
152
+
153
+ def plotHOLZ_parameter(grey=False):
154
+ tags = {'background': 'gray', 'color_map': 'plasma', 'color_reflections': 'intensity', 'plot_HOLZ': 1,
155
+ 'plot_HOLZ_excess': 1, 'plot_Kikuchi': 1, 'plot_reflections': 1, 'label_HOLZ': 0, 'label_Kikuchi': 0,
156
+ 'label_reflections': 0, 'label_color': 'white', 'label_size': 12, 'color_Kikuchi': 'green',
157
+ 'linewidth_HOLZ': 1, 'linewidth_Kikuchi': -1,
158
+ 'color_Laue_Zones': ['red', 'blue', 'lightblue', 'green', 'red'], 'color_zero': 'None',
159
+ 'color_ring_zero': 'Red', 'width_ring_zero': 2, 'plot_rotation': 0., 'plot_shift_x': -0.0,
160
+ 'plot_shift_y': 0.0} # 'white' 'grey'
161
+
162
+ # plotDiffPattern(holz,True)
163
+ return tags
164
+
165
+
166
+ ########################
167
+ # Plot CBED Pattern #
168
+ ########################
169
+
170
+ def plotCBED_parameter():
171
+ tags = {'background': 'black', 'color_map': 'plasma', 'color_reflections': 'intensity', 'plot_HOLZ': 1,
172
+ 'plot_HOLZ_excess': 1, 'plot_Kikuchi': 1, 'plot_reflections': 1, 'label_HOLZ': 0, 'label_Kikuchi': 0,
173
+ 'label_reflections': 0, 'label_color': 'white', 'label_size': 10, 'color_Kikuchi': 'green',
174
+ 'linewidth_HOLZ': -1, 'linewidth_Kikuchi': -1, 'color_Laue_Zones': ['red', 'blue', 'green'],
175
+ 'color_zero': 'white', 'color_ring_zero': 'Red', 'width_ring_zero': 2} # 'white' 'grey'
176
+
177
+ # plotDiffPattern(tags,True)
178
+ return tags
179
+
180
+ ########################
181
+ # Plot HOLZ Pattern #
182
+ ########################
183
+
184
+
185
+ def circles(x, y, s, c='b', vmin=None, vmax=None, **kwargs):
186
+ """
187
+ Make a scatter plot of circles.
188
+ Similar to plt.scatter, but the size of circles are in data scale.
189
+ Parameters
190
+ ----------
191
+ x, y : scalar or array_like, shape (n, )
192
+ Input data
193
+ s : scalar or array_like, shape (n, )
194
+ Radius of circles.
195
+ c : color or sequence of color, optional, default : 'b'
196
+ `c` can be a single color format string, or a sequence of color
197
+ specifications of length `N`, or a sequence of `N` numbers to be
198
+ mapped to colors using the `cmap` and `norm` specified via kwargs.
199
+ Note that `c` should not be a single numeric RGB or RGBA sequence
200
+ because that is indistinguishable from an array of values
201
+ to be colormapped. (If you insist, use `color` instead.)
202
+ `c` can be a 2-D array in which the rows are RGB or RGBA, however.
203
+ vmin, vmax : scalar, optional, default: None
204
+ `vmin` and `vmax` are used in conjunction with `norm` to normalize
205
+ luminance data. If either are `None`, the min and max of the
206
+ color array is used.
207
+ kwargs : `~matplotlib.collections.Collection` properties
208
+ Eg. alpha, edgecolor(ec), facecolor(fc), linewidth(lw), linestyle(ls),
209
+ norm, cmap, transform, etc.
210
+ Returns
211
+ -------
212
+ paths : `~matplotlib.collections.PathCollection`
213
+ Examples
214
+ --------
215
+ a = np.arange(11)
216
+ circles(a, a, s=a*0.2, c=a, alpha=0.5, ec='none')
217
+ plt.colorbar()
218
+ License
219
+ --------
220
+ This code is under [The BSD 3-Clause License]
221
+ (http://opensource.org/licenses/BSD-3-Clause)
222
+ """
223
+
224
+ if np.isscalar(c):
225
+ kwargs.setdefault('color', c)
226
+ c = None
227
+
228
+ if 'fc' in kwargs:
229
+ kwargs.setdefault('facecolor', kwargs.pop('fc'))
230
+ if 'ec' in kwargs:
231
+ kwargs.setdefault('edgecolor', kwargs.pop('ec'))
232
+ if 'ls' in kwargs:
233
+ kwargs.setdefault('linestyle', kwargs.pop('ls'))
234
+ if 'lw' in kwargs:
235
+ kwargs.setdefault('linewidth', kwargs.pop('lw'))
236
+ # You can set `facecolor` with an array for each patch,
237
+ # while you can only set `facecolors` with a value for all.
238
+
239
+ zipped = np.broadcast(x, y, s)
240
+ patches = [Circle((x_, y_), s_, picker=True)
241
+ for x_, y_, s_ in zipped]
242
+ collection = PatchCollection(patches, **kwargs)
243
+ if c is not None:
244
+ c = np.broadcast_to(c, zipped.shape).ravel()
245
+ collection.set_array(c)
246
+ collection.set_clim(vmin, vmax)
247
+
248
+ ax = plt.gca()
249
+ ax.add_collection(collection)
250
+ ax.autoscale_view()
251
+ plt.draw_if_interactive()
252
+ if c is not None:
253
+ plt.sci(collection)
254
+ return collection
255
+
256
+
257
+ def cartesian2polar(x, y, grid, r, t, order=3):
258
+ R, T = np.meshgrid(r, t)
259
+
260
+ new_x = R * np.cos(T)
261
+ new_y = R * np.sin(T)
262
+
263
+ ix = interp1d(x, np.arange(len(x)))
264
+ iy = interp1d(y, np.arange(len(y)))
265
+
266
+ new_ix = ix(new_x.ravel())
267
+ new_iy = iy(new_y.ravel())
268
+
269
+ return map_coordinates(grid, np.array([new_ix, new_iy]),
270
+ order=order).reshape(new_x.shape)
271
+
272
+
273
+ def warp(diff, center):
274
+ """
275
+ Define original polar grid
276
+
277
+ Parameter:
278
+ ----------
279
+ diff: sidpy object or numpy ndarray of
280
+ diffraction pattern
281
+ center: list or numpy array of length 2
282
+ coordinates of center in pixel
283
+
284
+ Return:
285
+ ------
286
+ numpy array of diffraction pattern in polar coordinates
287
+
288
+ """
289
+ nx = diff.shape[0]
290
+ ny = diff.shape[1]
291
+
292
+ x = np.linspace(1, nx, nx, endpoint=True) - center[0]
293
+ y = np.linspace(1, ny, ny, endpoint=True) - center[1]
294
+ z = diff
295
+
296
+ # Define new polar grid
297
+ nr = int(min([center[0], center[1], diff.shape[0] - center[0], diff.shape[1] - center[1]]) - 1)
298
+ print(nr)
299
+ nt = 360 * 3
300
+
301
+ r = np.linspace(1, nr, nr)
302
+ t = np.linspace(0., np.pi, nt, endpoint=False)
303
+ return cartesian2polar(x, y, z, r, t, order=3).T
304
+
305
+
306
+ def topolar(img, order=1):
307
+ """
308
+ Transform img to its polar coordinate representation.
309
+
310
+ order: int, default 1
311
+ Specify the spline interpolation order.
312
+ High orders may be slow for large images.
313
+ """
314
+ # max_radius is the length of the diagonal
315
+ # from a corner to the mid-point of img.
316
+ max_radius = 0.5 * np.linalg.norm(img.shape)
317
+
318
+ def transform(coords):
319
+ # Put coord[1] in the interval, [-pi, pi]
320
+ theta = 2 * np.pi * coords[1] / (img.shape[1] - 1.)
321
+
322
+ # Then map it to the interval [0, max_radius].
323
+ # radius = float(img.shape[0]-coords[0]) / img.shape[0] * max_radius
324
+ radius = max_radius * coords[0] / img.shape[0]
325
+
326
+ i = 0.5 * img.shape[0] - radius * np.sin(theta)
327
+ j = radius * np.cos(theta) + 0.5 * img.shape[1]
328
+ return i, j
329
+
330
+ polar = geometric_transform(img, transform, order=order)
331
+
332
+ rads = max_radius * np.linspace(0, 1, img.shape[0])
333
+ angs = np.linspace(0, 2 * np.pi, img.shape[1])
334
+
335
+ return polar, (rads, angs)
336
+
337
+
338
+ def plot_ring_pattern(atoms, diffraction_pattern=None, grey=False):
339
+ """
340
+ Plot of ring diffraction pattern with matplotlib
341
+
342
+ Parameters
343
+ ----------
344
+ atoms: dictionary or sidpy.Dataset
345
+ information stored as dictionary either directly or in metadata attribute of sidpy.Dataset
346
+ grey: bool
347
+ plotting in greyscale if True
348
+
349
+ Returns
350
+ -------
351
+ fig: matplotlib figure
352
+ reference to matplotlib figure
353
+ """
354
+
355
+ if isinstance(atoms, dict):
356
+ tags = atoms
357
+ elif isinstance(atoms, ase.Atoms):
358
+ if 'diffraction' in atoms.info:
359
+ tags = atoms.info['diffraction']
360
+ plot_diffraction_pattern = True
361
+ else:
362
+ raise TypeError('Diffraction information must be in metadata')
363
+ else:
364
+ raise TypeError('Diffraction info must be in sidpy Dataset or dictionary form')
365
+ if diffraction_pattern is not None:
366
+ if not(diffraction_pattern, sidpy.Dataset):
367
+ print('diffraction_pattern must be a sidpy.Dataset \n -> Ignoring this variable')
368
+ diffraction_pattern = None
369
+ d = tags['Ring_Pattern']['allowed']['g norm']
370
+ label = tags['Ring_Pattern']['allowed']['label']
371
+ if 'label_color' not in tags:
372
+ tags['label_color'] = 'navy'
373
+ if 'profile color' not in tags:
374
+ tags['profile color'] = 'navy'
375
+ if 'ring color' not in tags:
376
+ tags['ring color'] = 'red'
377
+ if 'label_size' not in tags:
378
+ tags['label_size'] = 10
379
+ if 'profile height' not in tags:
380
+ tags['profile height'] = 5
381
+ if 'plot_scalebar' not in tags:
382
+ tags['plot_scalebar'] = False
383
+
384
+ fg, ax = plt.subplots(1, 1)
385
+
386
+ # ###
387
+ # plot arcs of the rings
388
+ # ###
389
+ for i in range(len(d)):
390
+ pac = patches.Arc((0, 0), d[i] * 2, d[i] * 2, angle=0, theta1=45, theta2=360, color=tags['ring color'])
391
+ ax.add_patch(pac)
392
+
393
+ ####
394
+ # show image in background
395
+ ####
396
+ if plot_diffraction_pattern is not None:
397
+ plt.imshow(diffraction_pattern, extent=diffraction_pattern.get_extent(), cmap='gray')
398
+
399
+ ax.set_aspect("equal")
400
+
401
+ # fg.canvas.draw()
402
+
403
+ if tags['plot_scalebar']:
404
+ def f(axis):
405
+ l = axis.get_majorticklocs()
406
+ return len(l) > 1 and (l[1] - l[0])
407
+
408
+ sizex = f(ax.xaxis)
409
+ labelx = str(sizex) + ' 1/nm'
410
+ scalebar = AnchoredSizeBar(ax.transData, sizex, labelx, loc=3,
411
+ pad=0.5, color='white', frameon=False)
412
+ # size_vertical=.2, fill_bar = True) # will be implemented in matplotlib 2.1
413
+
414
+ ax.add_artist(scalebar)
415
+ ax.axis('off')
416
+
417
+ # ####
418
+ # plot profile
419
+ # ####
420
+
421
+ y = tags['Ring_Pattern']['profile_y']
422
+ y = y / y.max() * tags['profile height']
423
+ x = tags['Ring_Pattern']['profile_x']
424
+ ax.plot(x, y, c=tags['profile color'])
425
+
426
+ ax.plot([0, x[-1]], [0, 0], c=tags['profile color'])
427
+
428
+ if 'experimental profile_y' in tags:
429
+ yy = tags['experimental profile_y']
430
+ yy = yy / yy.max() * tags['profile height']
431
+ xx = tags['experimental profile_x']
432
+ ax.plot(xx, yy, c=tags['experimental profile color'])
433
+
434
+ if 'plot_image_FOV' in tags:
435
+ max_d = tags['plot_image_FOV'] / 2 + tags['plot_shift_x']
436
+ else:
437
+ max_d = d.max()
438
+ for i in range(len(d)):
439
+ if d[i] < max_d:
440
+ plt.text(d[i] - .2, -.5, label[i], fontsize=tags['label_size'], color=tags['label_color'], rotation=90)
441
+
442
+ if 'plot_FOV' in tags:
443
+ l = -tags['plot_FOV'] / 2
444
+ r = tags['plot_FOV'] / 2
445
+ t = -tags['plot_FOV'] / 2
446
+ b = tags['plot_FOV'] / 2
447
+ plt.xlim(l, r)
448
+ plt.ylim(t, b)
449
+
450
+ fg.show()
451
+ return fg
452
+
453
+
454
+ def plot_diffraction_pattern(atoms, diffraction_pattern=None, grey=False):
455
+ """
456
+ Plot of spot diffraction pattern with matplotlib
457
+ Plot of spot diffraction pattern with matplotlib
458
+
459
+ Parameters
460
+ ----------
461
+ atoms: dictionary or ase.Atoms object
462
+ information stored as dictionary either directly or in info attribute of ase.Atoms object
463
+ diffraction_pattern: None or sidpy.Dataset
464
+ diffraction pattern in background
465
+ grey: bool
466
+ plotting in greyscale if True
467
+
468
+ Returns
469
+ -------
470
+ fig: matplotlib figure
471
+ reference to matplotlib figure
472
+ """
473
+
474
+ if isinstance(atoms, dict):
475
+ tags_out = atoms
476
+
477
+ elif isinstance(atoms, ase.Atoms):
478
+ if 'diffraction' in atoms.info:
479
+ tags_out = atoms.info['diffraction']
480
+ plot_diffraction_pattern = True
481
+ else:
482
+ raise TypeError('Diffraction information must be in info dictionary of ase.Atoms object')
483
+ else:
484
+ raise TypeError('Diffraction info must be in ase.Atoms object or dictionary form')
485
+
486
+ if 'output' not in atoms.info:
487
+ return
488
+
489
+ # Get information from dictionary
490
+ HOLZ = tags_out['HOLZ']
491
+ ZOLZ = tags_out['allowed']['ZOLZ']
492
+ # Kikuchi = tags_out['Kikuchi']
493
+
494
+ Laue_Zone = tags_out['allowed']['Laue_Zone']
495
+
496
+ label = tags_out['allowed']['label']
497
+ hkl_label = tags_out['allowed']['hkl']
498
+
499
+ angle = np.radians(atoms.info['output']['plot_rotation']) # mrad
500
+ c = np.cos(angle)
501
+ s = np.sin(angle)
502
+ r_mat = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
503
+
504
+ # HOLZ and Kikuchi lines coordinates in Hough space
505
+ LC = tags_out['Laue_circle']
506
+ gd = np.dot(tags_out['HOLZ']['g_deficient'] + LC, r_mat)
507
+ ge = np.dot(tags_out['HOLZ']['g_excess'], r_mat)
508
+ points = np.dot(tags_out['allowed']['g'] + LC, r_mat)
509
+
510
+ theta = tags_out['HOLZ']['theta'] + angle
511
+
512
+ if 'thickness' not in tags_out:
513
+ tags_out['thickness'] = 0.
514
+ if tags_out['thickness'] > 0.1:
515
+ intensity = np.real(tags_out['allowed']['Ig'])
516
+ else:
517
+ intensity = tags_out['allowed']['intensities']
518
+
519
+ radius = atoms.info['experimental']['convergence_angle_A-1']
520
+
521
+ if radius < 0.1:
522
+ radiusI = 2
523
+ else:
524
+ radiusI = radius
525
+ # Beginning and ends of HOLZ lines
526
+ max_length = radiusI * 1.3
527
+ h_xp = gd[:, 0] + max_length * np.cos(np.pi - theta)
528
+ h_yp = gd[:, 1] + max_length * np.sin(np.pi - theta)
529
+ h_xm = gd[:, 0] - max_length * np.cos(np.pi - theta)
530
+ h_ym = gd[:, 1] - max_length * np.sin(np.pi - theta)
531
+
532
+ # Beginning and ends of excess HOLZ lines
533
+ max_length = radiusI * .8
534
+ e_xp = ge[:, 0] + max_length * np.cos(np.pi - theta)
535
+ e_yp = ge[:, 1] + max_length * np.sin(np.pi - theta)
536
+ e_xm = ge[:, 0] - max_length * np.cos(np.pi - theta)
537
+ e_ym = ge[:, 1] - max_length * np.sin(np.pi - theta)
538
+
539
+ # Beginning and ends of Kikuchi lines
540
+ if 'max_length' not in tags_out['Kikuchi']:
541
+ tags_out['Kikuchi']['max_length'] = 20
542
+ max_length = tags_out['Kikuchi']['max_length']
543
+
544
+ gd = tags_out['Kikuchi']['g_deficient']
545
+ theta = tags_out['Kikuchi']['theta']
546
+ k_xp = gd[:, 0] + max_length * np.cos(np.pi - theta)
547
+ k_yp = gd[:, 1] + max_length * np.sin(np.pi - theta)
548
+ k_xm = gd[:, 0] - max_length * np.cos(np.pi - theta)
549
+ k_ym = gd[:, 1] - max_length * np.sin(np.pi - theta)
550
+
551
+ if atoms.info['output']['linewidth_Kikuchi'] < 0:
552
+ if len(intensity[ZOLZ]) > 0:
553
+ intensity_kikuchi = intensity * 4. / intensity[ZOLZ].max()
554
+ else:
555
+ intensity_kikuchi = intensity
556
+ else:
557
+ intensity_kikuchi = np.ones(len(intensity)) * atoms.info['output']['linewidth_Kikuchi']
558
+
559
+ if atoms.info['output']['linewidth_HOLZ'] < 0:
560
+ intensity_holz = np.log(intensity + 1)
561
+
562
+ if tags_out['HOLZ']['HOLZ'].any():
563
+ pass # intensity_holz = intensity/intensity[tags_out['HOLZ']['HOLZ']].max()*4.
564
+ else:
565
+ intensity_holz = np.ones(len(intensity)) * atoms.info['output']['linewidth_HOLZ']
566
+
567
+ # #######
568
+ # Plot #
569
+ # #######
570
+ # cms = mpl.cm
571
+ # cm = cms.plasma#jet#, cms.gray, cms.autumn]
572
+ cm = plt.get_cmap(atoms.info['output']['color_map'])
573
+
574
+ # fig = plt.figure()
575
+ fig = plt.figure()
576
+
577
+ ax = plt.gca()
578
+ if 'background' not in atoms.info['output']:
579
+ atoms.info['output']['background'] = None
580
+ if atoms.info['output']['background'] is not None:
581
+ ax.set_facecolor(atoms.info['output']['background'])
582
+
583
+ if diffraction_pattern is not None:
584
+ plt.imshow(diffraction_pattern, extent=diffraction_pattern.get_extent([0, 1]), cmap='gray')
585
+
586
+ ix = np.argsort((points ** 2).sum(axis=1))
587
+ p = points[ix]
588
+ inten = intensity[ix]
589
+ reflection = hkl_label[ix]
590
+ laue_color = []
591
+
592
+ labelP = ''
593
+ lineLabel = []
594
+
595
+ def onpick(event):
596
+ if isinstance(event.artist, Line2D):
597
+ thisline = event.artist
598
+ ind = ax.lines.index(thisline)
599
+ print(ind, len(points), ind - len(points))
600
+ # ind = ind- len(points)
601
+ h, k, l = lineLabel[ind]
602
+
603
+ if Laue_Zone[ind] > 0:
604
+ labelP = 'Laue Zone %1d; HOLZ line: [%1d,%1d,%1d]' % (Laue_Zone[ind], h, k, l)
605
+ else:
606
+ labelP = 'Kikuchi line: [%1d,%1d,%1d]' % (h, k, l)
607
+ # print(labelP)
608
+
609
+ elif isinstance(event.artist, Circle):
610
+ print('Circle')
611
+
612
+ else:
613
+ ind = event.ind[0]
614
+ h, k, l = reflection[ind]
615
+
616
+ print('Reflection: [%1d,%1d,%1d]' % (h, k, l))
617
+
618
+ for i in range(int(Laue_Zone.max()) + 1):
619
+ if i < len(atoms.info['output']['color_Laue_Zones']):
620
+ laue_color.append(atoms.info['output']['color_Laue_Zones'][i])
621
+ else:
622
+ laue_color.append(atoms.info['output']['color_Laue_Zones'][-1])
623
+
624
+ if 'plot_labels' not in atoms.info['output']:
625
+ atoms.info['output']['plot_labels'] = True
626
+ if atoms.info['output']['plot_reflections']:
627
+ if radius < 0.01:
628
+ if atoms.info['output']['color_reflections'] == 'intensity':
629
+ for i in range(len(points)):
630
+ ax.scatter(points[i, 0], points[i, 1], c=np.log(intensity[i] + 1), cmap=cm, s=100)
631
+
632
+ if atoms.info['output']['plot_labels']:
633
+ plt.text(points[i, 0], points[i, 1], label[i], fontsize=10)
634
+ else:
635
+ for i in range(len(Laue_Zone)):
636
+ color = laue_color[int(Laue_Zone[i])]
637
+ ax.scatter(points[i, 0], points[i, 1], c=color, cmap=cm, s=100)
638
+ if atoms.info['output']['plot_labels']:
639
+ plt.text(points[i, 0], points[i, 1], label[i], fontsize=8)
640
+
641
+ ax.scatter(LC[0], LC[1], c=atoms.info['output']['color_zero'], s=100)
642
+ radius = .2
643
+ else:
644
+ ix = np.argsort((points ** 2).sum(axis=1))
645
+ p = points[ix]
646
+ inten = intensity[ix]
647
+ if atoms.info['output']['color_reflections'] == 'intensity':
648
+ circles(p[:, 0], p[:, 1], s=radius, c=np.log(inten + 1), cmap=cm, alpha=0.9, edgecolor=None, picker=5)
649
+ else:
650
+ for i in range(len(Laue_Zone)):
651
+ color = laue_color[int(Laue_Zone[i])]
652
+ circles(p[i, 0], p[i, 1], s=radius, c=color, cmap=cm, alpha=0.9, edgecolor='', picker=5) #
653
+ plt.text(points[i, 0], points[i, 1], label[i], fontsize=8)
654
+
655
+ if 'plot_dynamically_allowed' not in atoms.info['output']:
656
+ atoms.info['output']['plot_dynamically_allowed'] = False
657
+ if 'plot_forbidden' not in atoms.info['output']:
658
+ atoms.info['output']['plot_forbidden'] = False
659
+
660
+ if atoms.info['output']['plot_dynamically_allowed']:
661
+ if 'dynamically_allowed' not in atoms.info['diffraction']['forbidden']:
662
+ print('To plot dynamically allowed reflections you must run the get_dynamically_allowed function of '
663
+ 'kinematic_scattering library first!')
664
+ else:
665
+ points = atoms.info['diffraction']['forbidden']['g']
666
+ dynamically_allowed = atoms.info['diffraction']['forbidden']['dynamically_allowed']
667
+ dyn_allowed = atoms.info['diffraction']['forbidden']['g'][dynamically_allowed, :]
668
+ dyn_label = atoms.info['diffraction']['forbidden']['hkl'][dynamically_allowed, :]
669
+
670
+ color = laue_color[0]
671
+ ax.scatter(dyn_allowed[:, 0], dyn_allowed[:, 1], c='blue', alpha=0.4, s=70)
672
+ if atoms.info['output']['plot_labels']:
673
+ for i in range(len(dyn_allowed)):
674
+ plt.text(dyn_allowed[i, 0], dyn_allowed[i, 1], dyn_label[i], fontsize=8)
675
+ if atoms.info['output']['plot_forbidden']:
676
+ forbidden_g = atoms.info['diffraction']['forbidden']['g'][np.logical_not(dynamically_allowed), :]
677
+ forbidden_hkl = atoms.info['diffraction']['forbidden']['hkl'][np.logical_not(dynamically_allowed), :]
678
+ ax.scatter(forbidden_g[:, 0], forbidden_g[:, 1], c='orange', alpha=0.4, s=70)
679
+ if atoms.info['output']['plot_labels']:
680
+ for i in range(len(forbidden_g)):
681
+ plt.text(forbidden_g[i, 0], forbidden_g[i, 1], forbidden_hkl[i], fontsize=8)
682
+ elif atoms.info['output']['plot_forbidden']:
683
+ forbidden_g = atoms.info['diffraction']['forbidden']['g']
684
+ forbidden_hkl = atoms.info['diffraction']['forbidden']['hkl']
685
+ ax.scatter(forbidden_g[:, 0], forbidden_g[:, 1], c='orange', alpha=0.4, s=70)
686
+ if atoms.info['output']['plot_labels']:
687
+ for i in range(len(forbidden_g)):
688
+ plt.text(forbidden_g[i, 0], forbidden_g[i, 1], forbidden_hkl[i], fontsize=8)
689
+
690
+ k = 0
691
+ if atoms.info['output']['plot_HOLZ']:
692
+ for i in range(len(h_xp)):
693
+ if tags_out['HOLZ']['HOLZ'][i]:
694
+ color = laue_color[int(Laue_Zone[i])]
695
+ if atoms.info['output']['plot_HOLZ']:
696
+ # plot HOLZ lines
697
+ line, = plt.plot((h_xp[i], h_xm[i]), (h_yp[i], h_ym[i]), c=color, linewidth=intensity_holz[i],
698
+ picker=5)
699
+ if atoms.info['output']['label_HOLZ']: # Add indices
700
+ plt.text(h_xp[i], h_yp[i], label[i], fontsize=8)
701
+ lineLabel.append(hkl_label[i])
702
+ # print(i, hkl_label[i], intensity_holz[i])
703
+
704
+ if atoms.info['output']['plot_HOLZ_excess']:
705
+ line, = plt.plot((e_xp[i], e_xm[i]), (e_yp[i], e_ym[i]), c=color, linewidth=intensity_holz[i])
706
+ lineLabel.append(hkl_label[i])
707
+
708
+ if atoms.info['output']['label_HOLZ']: # Add indices
709
+ plt.text(e_xp[i], e_yp[i], label[i], fontsize=8)
710
+
711
+ elif atoms.info['output']['label_Kikuchi']: # Add indices
712
+ if ZOLZ[i]:
713
+ plt.text(k_xp[i], k_yp[i], label[i], fontsize=atoms.info['output']['label_size'],
714
+ color=atoms.info['output']['label_color'])
715
+ lineLabel.append(hkl_label[i])
716
+ if atoms.info['output']['plot_Kikuchi']:
717
+ # Beginning and ends of Kikuchi lines
718
+ if atoms.info['output']['label_Kikuchi']:
719
+ label_kikuchi = []
720
+ for i in range(len(label)):
721
+ if ZOLZ[i]:
722
+ label_kikuchi.append(label[i])
723
+ for i in range(len(k_xp)):
724
+ line, = plt.plot((k_xp[i], k_xm[i]), (k_yp[i], k_ym[i]), c=atoms.info['output']['color_Kikuchi'],
725
+ linewidth=2)
726
+ if atoms.info['output']['label_Kikuchi']: # Add indices
727
+ plt.text(k_xp[i], k_yp[i], label[i], fontsize=atoms.info['output']['label_size'],
728
+ color=atoms.info['output']['label_color'])
729
+
730
+ def format_coord(x, y):
731
+ return labelP + 'x=%1.4f, y=%1.4f' % (x, y)
732
+
733
+ ax.format_coord = format_coord
734
+
735
+ if atoms.info['output']['color_ring_zero'] != 'None':
736
+ ring = plt.Circle(LC, radius, color=atoms.info['output']['color_ring_zero'], fill=False, linewidth=2)
737
+ ax.add_artist(ring)
738
+ # print(ring)
739
+ if atoms.info['output']['color_zero'] != 'None':
740
+ circle = plt.Circle(LC, radius, color=atoms.info['output']['color_zero'], linewidth=2)
741
+ ax.add_artist(circle)
742
+
743
+ plt.axis('equal')
744
+ if 'plot_FOV' in tags_out:
745
+ l = -tags_out['plot_FOV'] / 2
746
+ r = tags_out['plot_FOV'] / 2
747
+ t = -tags_out['plot_FOV'] / 2
748
+ b = tags_out['plot_FOV'] / 2
749
+ plt.xlim(l, r)
750
+ plt.ylim(t, b)
751
+
752
+ fig.canvas.mpl_connect('pick_event', onpick)
753
+ # texfig.savefig("HOLZ")
754
+
755
+ # plt.title( tags_out['crystal'])
756
+ plt.show()