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
@@ -0,0 +1,701 @@
1
+ """
2
+ image_tools.py
3
+ by Gerd Duscher, UTK
4
+ part of pyTEMlib
5
+ MIT license except where stated differently
6
+
7
+ This version is build on top of pycroscopy.image package of the pycrocsopy ecosysgtem.
8
+ """
9
+
10
+ import numpy as np
11
+
12
+ import scipy
13
+ import skimage
14
+ import sklearn
15
+ import matplotlib
16
+ import matplotlib.pylab as plt
17
+
18
+ import sidpy
19
+ import pyTEMlib
20
+
21
+ ## import all function of the image package of pycroscopy
22
+ from .image import *
23
+ from .image.image_utilities import pol2cart, cart2pol, xy2polar
24
+
25
+
26
+ def get_atomic_pseudo_potential(fov, atoms, size=512, rotation=0):
27
+ """Big assumption: the atoms are not near the edge of the unit cell
28
+ # If any atoms are close to the edge (ex. [0,0]) then the potential will be clipped
29
+ # before calling the function, shift the atoms to the center of the unit cell
30
+ """
31
+
32
+ pixel_size = fov / size
33
+ max_size = int(size * np.sqrt(2) + 1) # Maximum size to accommodate rotation
34
+
35
+ # Create unit cell potential
36
+ positions = atoms.get_positions()[:, :2]
37
+ atomic_numbers = atoms.get_atomic_numbers()
38
+ unit_cell_size = atoms.cell.cellpar()[:2]
39
+
40
+ unit_cell_potential = np.zeros((max_size, max_size))
41
+ for pos, atomic_number in zip(positions, atomic_numbers):
42
+ x = pos[0] / pixel_size
43
+ y = pos[1] / pixel_size
44
+ atom_width = 0.5 # Angstrom
45
+ # important for images at various fov. Room for improvement with theory
46
+ gauss_width = atom_width/pixel_size
47
+ gauss = pyTEMlib.probe_tools.make_gauss(max_size, max_size,
48
+ width=gauss_width,
49
+ x0=x, y0=y)
50
+ unit_cell_potential += gauss * atomic_number # gauss is already normalized to 1
51
+
52
+ # Create interpolation function for unit cell potential
53
+ x_grid = np.linspace(0, fov * max_size / size, max_size)
54
+ y_grid = np.linspace(0, fov * max_size / size, max_size)
55
+ interpolator = scipy.interpolate.RegularGridInterpolator((x_grid, y_grid),
56
+ unit_cell_potential,
57
+ bounds_error=False,
58
+ fill_value=0)
59
+ # Vectorized computation of the full potential map with max_size
60
+ x_coords, y_coords = np.meshgrid(np.linspace(0, fov, max_size),
61
+ np.linspace(0, fov, max_size),
62
+ indexing="ij")
63
+ xtal_x = x_coords % unit_cell_size[0]
64
+ xtal_y = y_coords % unit_cell_size[1]
65
+ potential_map = interpolator((xtal_x.ravel(), xtal_y.ravel())).reshape(max_size, max_size)
66
+
67
+ # Rotate and crop the potential map
68
+ potential_map = scipy.ndimage.rotate(potential_map, rotation, reshape=False)
69
+ center = potential_map.shape[0] // 2
70
+ potential_map = potential_map[center - size // 2:center + size // 2,
71
+ center - size // 2:center + size // 2]
72
+ potential_map = scipy.ndimage.gaussian_filter(potential_map,3)
73
+
74
+ return potential_map
75
+
76
+ def convolve_probe(ab, potential):
77
+ """ Convolve probe with potential using FFT based convolution"""
78
+ # the pixel sizes should be the exact same as the potential
79
+ final_sizes = potential.shape
80
+
81
+ # Perform FFT-based convolution
82
+ pad_height = pad_width = potential.shape[0] // 2
83
+ potential = np.pad(potential, ((pad_height, pad_height),
84
+ (pad_width, pad_width)), mode='constant')
85
+
86
+ probe, _, _ = pyTEMlib.probe_tools.get_probe(ab, potential.shape[0],
87
+ potential.shape[1],
88
+ scale='mrad', verbose=False)
89
+ convolved = scipy.signal.fftconvolve(potential, probe, mode='same')
90
+
91
+ # Crop to original potential size
92
+ start_row = pad_height
93
+ start_col = pad_width
94
+ end_row = start_row + final_sizes[0]
95
+ end_col = start_col + final_sizes[1]
96
+
97
+ image = convolved[start_row:end_row, start_col:end_col]
98
+ return probe, image
99
+
100
+ def get_wavelength(e0):
101
+ """
102
+ Calculates the relativistic corrected de Broglie wave length of an electron
103
+ # Wavelength in 1/nm
104
+ Parameters
105
+ ----------
106
+ e0: float
107
+ acceleration voltage in volt
108
+
109
+ Returns
110
+ -------
111
+ wave length in 1/nm
112
+ """
113
+ ev = scipy.constants.e * e0
114
+ h = scipy.constants.h
115
+ m = scipy.constants.m_e
116
+ c = scipy.constants.c
117
+ return h / np.sqrt(2 * m * ev * (1 + ev / (2 * m * c**2))) * 10**9
118
+
119
+ def fourier_transform(dset: sidpy.Dataset) -> sidpy.Dataset:
120
+ """
121
+ Reads information into dictionary 'tags', performs 'FFT',
122
+ and provides a smoothed FT and reciprocal
123
+ and intensity limits for visualization.
124
+
125
+ Parameters
126
+ ----------
127
+ dset: sidpy.Dataset
128
+ image
129
+
130
+ Returns
131
+ -------
132
+ fft_dset: sidpy.Dataset
133
+ Fourier transform with correct dimensions
134
+
135
+ Example
136
+ -------
137
+ >>> fft_dataset = fourier_transform(sidpy_dataset)
138
+ >>> fft_dataset.plot()
139
+ """
140
+
141
+ assert isinstance(dset, sidpy.Dataset), 'Expected a sidpy Dataset'
142
+
143
+ selection = []
144
+ image_dims = dset.get_image_dims(return_axis=True)
145
+ if dset.data_type.name == 'IMAGE_STACK':
146
+ stack_dim = dset.get_dimensions_by_type('TEMPORAL')
147
+
148
+ if len(image_dims) != 2:
149
+ raise ValueError('need at least two SPATIAL dimension for an image stack')
150
+
151
+ for i in range(dset.ndim):
152
+ if i in image_dims:
153
+ selection.append(slice(None))
154
+ if len(stack_dim) == 0:
155
+ stack_dim = i
156
+ selection.append(slice(None))
157
+ elif i in stack_dim:
158
+ stack_dim = i
159
+ selection.append(slice(None))
160
+ else:
161
+ selection.append(slice(0, 1))
162
+
163
+ image_stack = np.squeeze(np.array(dset)[selection])
164
+ new_image = np.sum(np.array(image_stack), axis=stack_dim)
165
+ elif dset.data_type.name == 'IMAGE':
166
+ new_image = np.array(dset)
167
+ else:
168
+ return
169
+
170
+ new_image = new_image - new_image.min()
171
+ fft_transform = (np.fft.fftshift(np.fft.fft2(np.array(new_image))))
172
+
173
+ image_dims = dset.get_image_dims(return_axis=True)
174
+
175
+ units_x = '1/' + image_dims[0].units
176
+ units_y = '1/' + image_dims[1].units
177
+ fft_dset = sidpy.Dataset.from_array(fft_transform)
178
+ fft_dset.quantity = dset.quantity
179
+ fft_dset.units = 'a.u.'
180
+ fft_dset.data_type = 'IMAGE'
181
+ fft_dset.source = dset.title
182
+ fft_dset.modality = 'fft'
183
+ axis = np.fft.fftshift(np.fft.fftfreq(new_image.shape[0], d=dset.x[1]-dset.x[0]))
184
+ fft_dset.set_dimension(0, sidpy.Dimension(axis,
185
+ name='u', units=units_x,
186
+ dimension_type='RECIPROCAL',
187
+ quantity='reciprocal_length'))
188
+ axis = np.fft.fftshift(np.fft.fftfreq(new_image.shape[1], d=dset.y[1]-dset.y[0]))
189
+ fft_dset.set_dimension(1, sidpy.Dimension(axis,
190
+ name='v', units=units_y,
191
+ dimension_type='RECIPROCAL',
192
+ quantity='reciprocal_length'))
193
+ return fft_dset
194
+
195
+
196
+ def center_diffractogram(dset, return_plot = True, smoothing = 1,
197
+ min_samples = 10, beamstop_size = 0.1):
198
+ """Find center of diffractogram by fitting a circle to the diffraction ring"""
199
+ mean_radius = 0
200
+ try:
201
+ diff = np.array(dset).T.astype(np.float16)
202
+ diff[diff < 0] = 0
203
+ threshold = skimage.filters.threshold_otsu(diff)
204
+ binary = (diff > threshold).astype(float)
205
+ # Smooth before edge detection
206
+ smoothed_image = scipy.ndimage.gaussian_filter(binary, sigma=smoothing)
207
+ smooth_threshold = skimage.filters.threshold_otsu(smoothed_image)
208
+ smooth_binary = (smoothed_image > smooth_threshold).astype(float)
209
+
210
+ # add a circle to mask the beamstop
211
+ x, y = np.meshgrid(np.arange(dset.shape[0]), np.arange(dset.shape[1]))
212
+ radius = (x - dset.shape[0] / 2) ** 2 + (y - dset.shape[1] / 2) ** 2
213
+ circle = radius < (beamstop_size * dset.shape[0] / 2) ** 2
214
+ smooth_binary[circle] = 1
215
+
216
+ # Find the edges using the Sobel operator
217
+ edges = skimage.filters.sobel(smooth_binary)
218
+ edge_points = np.argwhere(edges)
219
+
220
+ # Use DBSCAN to cluster the edge points
221
+ db = sklearn.cluster.DBSCAN(eps=10, min_samples=min_samples).fit(edge_points)
222
+ labels = db.labels_
223
+ if len(set(labels)) == 1:
224
+ raise ValueError("DBSCAN clustering resulted in only one group, check the parameters.")
225
+
226
+ # Get the largest group of edge points
227
+ unique, counts = np.unique(labels, return_counts=True)
228
+ counts = dict(zip(unique, counts))
229
+ largest_group = max(counts, key=counts.get)
230
+ edge_points = edge_points[labels == largest_group]
231
+
232
+ # Fit a circle to the diffraction ring
233
+ def calc_distance(c, x, y):
234
+ ri = np.sqrt((x - c[0])**2 + (y - c[1])**2)
235
+ return ri - ri.mean()
236
+ x_m = np.mean(edge_points[:, 1])
237
+ y_m = np.mean(edge_points[:, 0])
238
+ center_guess = x_m, y_m
239
+ center, _ = scipy.optimize.leastsq(calc_distance, center_guess,
240
+ args=(edge_points[:, 1], edge_points[:, 0]))
241
+ mean_radius = (np.mean(calc_distance(center, edge_points[:, 1], edge_points[:, 0]))
242
+ + np.sqrt((edge_points[:, 1] - center[0])**2
243
+ + (edge_points[:, 0] - center[1])**2).mean())
244
+ finally:
245
+ if return_plot:
246
+ fig, ax = plt.subplots(1, 5, figsize=(14, 4), sharex=True, sharey=True)
247
+ ax[0].set_title('Diffractogram')
248
+ ax[0].imshow(dset.T, cmap='viridis')
249
+ ax[1].set_title('Otsu Binary Image')
250
+ ax[1].imshow(binary, cmap='gray')
251
+ ax[2].set_title('Smoothed Binary Image')
252
+ ax[2].imshow(smoothed_image, cmap='gray')
253
+
254
+ ax[3].set_title('Smoothed Binary Image')
255
+ ax[3].imshow(smooth_binary, cmap='gray')
256
+ ax[4].set_title('Edge Detection and Fitting')
257
+ ax[4].imshow(edges, cmap='gray')
258
+ ax[4].scatter(center[0], center[1], c='r', s=10)
259
+ circle = plt.Circle(center, mean_radius, color='red', fill=False)
260
+ ax[4].add_artist(circle)
261
+ for axis in ax:
262
+ axis.axis('off')
263
+ fig.tight_layout()
264
+ return center
265
+
266
+
267
+ class ImageWithLineProfile:
268
+ """Image with line profile"""
269
+
270
+ def __init__(self, data, extent, title=''):
271
+ fig, ax = plt.subplots(1, 1)
272
+ self.figure = fig
273
+ self.title = title
274
+ self.line_plot = False
275
+ self.ax = ax
276
+ self.data = data
277
+ self.extent = extent
278
+ self.ax.imshow(data, extent=extent)
279
+ self.ax.set_title(title)
280
+ self.line, = self.ax.plot([0], [0], color='orange') # empty line
281
+ self.end_x = self.line.get_xdata()
282
+ self.end_y = self.line.get_ydata()
283
+ self.x = 0
284
+ self.y = 0
285
+ self.z = 0
286
+ self.start_x = self.end_x
287
+ self.start_y = self.end_y
288
+ self.moved_point = [0, 0]
289
+ self.new_point = [0, 0]
290
+ self.cid = self.line.figure.canvas.mpl_connect('button_press_event', self)
291
+
292
+ def __call__(self, event):
293
+ if event.inaxes != self.line.axes:
294
+ return
295
+ self.start_x = self.end_x
296
+ self.start_y = self.end_y
297
+
298
+ self.line.set_data([self.start_x, event.xdata], [self.start_y, event.ydata])
299
+ self.line.figure.canvas.draw()
300
+
301
+ self.end_x = event.xdata
302
+ self.end_y = event.ydata
303
+
304
+ self.update()
305
+
306
+ def update(self):
307
+ """ Update line profile"""
308
+ if not self.line_plot:
309
+ self.line_plot = True
310
+ self.figure.clear()
311
+ self.ax = self.figure.subplots(2, 1)
312
+ self.ax[0].imshow(self.data, extent=self.extent)
313
+ self.ax[0].set_title(self.title)
314
+
315
+ self.line, = self.ax[0].plot([0], [0], color='orange') # empty line
316
+ self.line_plot, = self.ax[1].plot([], [], color='orange')
317
+ self.ax[1].set_xlabel('distance [nm]')
318
+
319
+ x0 = self.start_x
320
+ x1 = self.end_x
321
+ y0 = self.start_y
322
+ y1 = self.end_y
323
+ length_plot = np.sqrt((x1-x0)**2+(y1-y0)**2)
324
+
325
+ num = length_plot*(self.data.shape[0]/self.extent[1])
326
+ x = np.linspace(x0, x1, num)*(self.data.shape[0]/self.extent[1])
327
+ y = np.linspace(y0, y1, num)*(self.data.shape[0]/self.extent[1])
328
+
329
+ # Extract the values along the line, using cubic interpolation
330
+ zi2 = scipy.ndimage.map_coordinates(self.data.T, np.vstack((x, y)))
331
+
332
+ x_axis = np.linspace(0, length_plot, len(zi2))
333
+ self.x = x_axis
334
+ self.z = zi2
335
+
336
+ self.line_plot.set_xdata(x_axis)
337
+ self.line_plot.set_ydata(zi2)
338
+ self.ax[1].set_xlim(0, x_axis.max())
339
+ self.ax[1].set_ylim(zi2.min(), zi2.max())
340
+ self.ax[1].draw()
341
+
342
+
343
+ class LineSelector(matplotlib.widgets.PolygonSelector):
344
+ """ Line selector with adjustable line width"""
345
+ def __init__(self, ax, onselect, line_width=1, **kwargs):
346
+ super().__init__(ax, onselect, **kwargs)
347
+ bounds = ax.viewLim.get_points()
348
+ np.max(bounds[0])
349
+ self.line_verts = np.array([[np.max(bounds[1])/2, np.max(bounds[0])/5],
350
+ [np.max(bounds[1])/2, np.max(bounds[0])/5+1],
351
+ [np.max(bounds[1])/5, np.max(bounds[0])/2],
352
+ [np.max(bounds[1])/5, np.max(bounds[0])/2]])
353
+ self.verts = self.line_verts
354
+ self.line_width = line_width
355
+
356
+ def set_linewidth(self, line_width=None):
357
+ """ Set the line width of the line selector"""
358
+ if line_width is not None:
359
+ self.line_width = line_width
360
+
361
+ m = -(self.line_verts[0, 1]-self.line_verts[3, 1])/(self.line_verts[0, 0]
362
+ -self.line_verts[3, 0])
363
+ c = 1/np.sqrt(1+m**2)
364
+ s = c*m
365
+ self.line_verts[1] = [self.line_verts[0, 0]+self.line_width*s,
366
+ self.line_verts[0, 1]+self.line_width*c]
367
+ self.line_verts[2] = [self.line_verts[3, 0]+self.line_width*s,
368
+ self.line_verts[3, 1]+self.line_width*c]
369
+ self.verts = self.line_verts.copy()
370
+
371
+ def onmove(self, event):
372
+ super().onmove(event)
373
+ if np.max(np.linalg.norm(self.line_verts-self.verts, axis=1)) > 1:
374
+ self.moved_point = np.argmax(np.linalg.norm(self.line_verts-self.verts, axis=1))
375
+
376
+ self.new_point = self.verts[self.moved_point]
377
+ moved_point = int(np.floor(self.moved_point/2)*3)
378
+ self.moved_point = moved_point
379
+ self.line_verts[moved_point] = self.new_point
380
+ self.set_linewidth()
381
+
382
+ def get_profile(dataset, line, spline_order=-1):
383
+ """
384
+ This function extracts a line profile from a given dataset.
385
+ The line profile is a representation of the data values
386
+ along a specified line in the dataset.
387
+ This function works for both image and spectral image data types.
388
+
389
+ Args:
390
+ dataset (sidpy.Dataset): The input dataset from which to extract the line profile.
391
+ line (list): A list specifying the line along which the profile should be extracted.
392
+ spline_order (int, optional): The order of the spline interpolation to use.
393
+ Default is -1, which means no interpolation.
394
+
395
+ Returns:
396
+ profile_dataset (sidpy.Dataset): A new sidpy.Dataset containing the line profile.
397
+
398
+
399
+ """
400
+ xv, yv = get_line_selection_points(line)
401
+ if dataset.data_type.name == 'IMAGE':
402
+ image_dims = dataset.get_image_dims(return_axis=True)
403
+ xv /= image_dims[0].slope
404
+ yv /= image_dims[1].slope
405
+ profile = scipy.ndimage.map_coordinates(np.array(dataset), [xv, yv])
406
+
407
+ profile_dataset = sidpy.Dataset.from_array(profile.sum(axis=0))
408
+ profile_dataset.data_type='spectrum'
409
+ profile_dataset.units = dataset.units
410
+ profile_dataset.quantity = dataset.quantity
411
+ profile_dataset.set_dimension(0, sidpy.Dimension(np.linspace(xv[0,0], xv[-1,-1],
412
+ profile_dataset.shape[0]),
413
+ name='x', units=dataset.x.units,
414
+ quantity=dataset.x.quantity,
415
+ dimension_type='spatial'))
416
+
417
+ if dataset.data_type.name == 'SPECTRAL_IMAGE':
418
+ spectral_axis = dataset.get_spectral_dims(return_axis=True)[0]
419
+ if spline_order > -1:
420
+ xv, yv, zv = get_line_selection_points_interpolated(line, z_length=dataset.shape[2])
421
+ profile = scipy.ndimage.map_coordinates(np.array(dataset), [xv, yv, zv],
422
+ order=spline_order)
423
+ profile = profile.sum(axis=0)
424
+ profile = np.stack([profile, profile], axis=1)
425
+ start = xv[0, 0, 0]
426
+ else:
427
+ profile = get_line_profile(np.array(dataset), xv, yv, len(spectral_axis))
428
+ start = xv[0, 0]
429
+ print(profile.shape)
430
+ profile_dataset = sidpy.Dataset.from_array(profile)
431
+ profile_dataset.data_type='spectral_image'
432
+ profile_dataset.units = dataset.units
433
+ profile_dataset.quantity = dataset.quantity
434
+ profile_dataset.set_dimension(0, sidpy.Dimension(np.arange(profile_dataset.shape[0])+start,
435
+ name='x', units=dataset.x.units,
436
+ quantity=dataset.x.quantity,
437
+ dimension_type='spatial'))
438
+ profile_dataset.set_dimension(1, sidpy.Dimension([0, 1],
439
+ name='y', units=dataset.x.units,
440
+ quantity=dataset.x.quantity,
441
+ dimension_type='spatial'))
442
+
443
+ profile_dataset.set_dimension(2, spectral_axis)
444
+ return profile_dataset
445
+
446
+
447
+ def get_line_selection_points_interpolated(line, z_length=1):
448
+ """ Get line selection points from line selector with interpolation in z direction"""
449
+ start_point = line.line_verts[3]
450
+ right_point = line.line_verts[0]
451
+ low_point = line.line_verts[2]
452
+
453
+ if start_point[0] > right_point[0]:
454
+ start_point = line.line_verts[0]
455
+ right_point = line.line_verts[3]
456
+ low_point = line.line_verts[1]
457
+ m = (right_point[1] - start_point[1]) / (right_point[0] - start_point[0])
458
+ length_x = int(abs(start_point[0]-right_point[0]))
459
+ length_v = int(np.linalg.norm(start_point-right_point))
460
+
461
+ linewidth = int(abs(start_point[1]-low_point[1]))
462
+ x = np.linspace(0,length_x, length_v)
463
+ y = np.linspace(0,linewidth, line.line_width)
464
+ if z_length > 1:
465
+ z = np.linspace(0, z_length, z_length)
466
+ xv, yv, zv = np.meshgrid(x, y, np.arange(z_length))
467
+ x = np.atleast_2d(x).repeat(z_length, axis=0).T
468
+ y = np.atleast_2d(y).repeat(z_length, axis=0).T
469
+ else:
470
+ xv, yv = np.meshgrid(x, y)
471
+ yv = yv + x*m + start_point[1]
472
+ xv = (xv.swapaxes(0,1) -y*m ).swapaxes(0,1) + start_point[0]
473
+
474
+ if z_length > 1:
475
+ return xv, yv, zv
476
+ else:
477
+ return xv, yv
478
+
479
+
480
+ def get_line_selection_points(line):
481
+ """ Get line selection points from line selector"""
482
+ start_point = line.line_verts[3]
483
+ right_point = line.line_verts[0]
484
+ low_point = line.line_verts[2]
485
+
486
+ if start_point[0] > right_point[0]:
487
+ start_point = line.line_verts[0]
488
+ right_point = line.line_verts[3]
489
+ low_point = line.line_verts[1]
490
+ m = (right_point[1] - start_point[1]) / (right_point[0] - start_point[0])
491
+ length_x = int(abs(start_point[0]-right_point[0]))
492
+ length_v = int(np.linalg.norm(start_point-right_point))
493
+
494
+ linewidth = int(abs(start_point[1]-low_point[1]))
495
+ x = np.linspace(0,length_x, length_v)
496
+ y = np.linspace(0,linewidth, line.line_width)
497
+ xv, yv = np.meshgrid(x, y)
498
+
499
+ yy = yv +x*m+start_point[1]
500
+ xx = (xv.T -y*m ).T + start_point[0]
501
+ return xx, yy
502
+
503
+
504
+ def get_line_profile(data, xv, yv, z_length):
505
+ """ Get line profile from data array"""
506
+ profile = np.zeros([len(xv[0]), 2, z_length])
507
+ for index_x in range(xv.shape[1]):
508
+ for index_y in range(xv.shape[0]):
509
+ x = int(xv[index_y, index_x])
510
+ y = int(yv[index_y, index_x])
511
+ if x< data.shape[0] and x>0 and y < data.shape[1] and y>0:
512
+ profile[index_x, 0] +=data[x, y]
513
+ return profile
514
+
515
+
516
+ def histogram_plot(image_tags):
517
+ """interactive histogram"""
518
+ nbins = 75
519
+ color_map_list = ['gray', 'viridis', 'jet', 'hot']
520
+ if 'minimum_intensity' not in image_tags:
521
+ image_tags['minimum_intensity'] = image_tags['plotimage'].min()
522
+ if 'maximum_intensity' not in image_tags:
523
+ image_tags['maximum_intensity'] = image_tags['plotimage'].max()
524
+ data = image_tags['plotimage']
525
+ vmin = image_tags['minimum_intensity']
526
+ vmax = image_tags['maximum_intensity']
527
+ if 'color_map' not in image_tags:
528
+ image_tags['color_map'] = color_map_list[0]
529
+
530
+ cmap = plt.cm.get_cmap(image_tags['color_map'])
531
+ colors = cmap(np.linspace(0., 1., nbins))
532
+ norm2 = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
533
+ hist, bin_edges = np.histogram(data, np.linspace(vmin, vmax, nbins), density=True)
534
+
535
+ width = bin_edges[1]-bin_edges[0]
536
+
537
+ def onselect(vmin, vmax):
538
+ """on select update histogram"""
539
+ ax1.clear()
540
+ cmap = plt.cm.get_cmap(image_tags['color_map'])
541
+ colors = cmap(np.linspace(0., 1., nbins))
542
+ norm2 = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
543
+ hist2, bin_edges2 = np.histogram(data, np.linspace(vmin, vmax, nbins), density=True)
544
+
545
+ width2 = bin_edges2[1]-bin_edges2[0]
546
+ for i in range(nbins-1):
547
+ histogram[i].xy = (bin_edges2[i], 0)
548
+ histogram[i].set_height(hist2[i])
549
+ histogram[i].set_width(width2)
550
+ histogram[i].set_facecolor(colors[i])
551
+ ax.set_xlim(vmin, vmax)
552
+ ax.set_ylim(0, hist2.max()*1.01)
553
+
554
+ # cb1 = matplotlib.colorbar.ColorbarBase(ax1, cmap=cmap,
555
+ # norm=norm2, orientation='horizontal')
556
+
557
+ image_tags['minimum_intensity'] = vmin
558
+ image_tags['maximum_intensity'] = vmax
559
+
560
+ def onclick(event):
561
+ """on click change color map"""
562
+ # global event2
563
+ # event2 = event
564
+ button_click = 'double' if event.dblclick else 'single'
565
+ print(f"{button_click} click: button={event.button},"
566
+ + f" x={event.x}, y={event.y}, xdata={event.xdata}, ydata={event.ydata}")
567
+ if event.inaxes == ax1:
568
+ if event.button == 3:
569
+ ind = color_map_list.index(image_tags['color_map'])+1
570
+ if ind == len(color_map_list):
571
+ ind = 0
572
+ image_tags['color_map'] = color_map_list[ind] # 'viridis'
573
+ vmin = image_tags['minimum_intensity']
574
+ vmax = image_tags['maximum_intensity']
575
+ else:
576
+ vmax = data.max()
577
+ vmin = data.min()
578
+ onselect(vmin, vmax)
579
+
580
+ fig2 = plt.figure()
581
+
582
+ ax = fig2.add_axes([0., 0.2, 0.9, 0.7])
583
+ ax1 = fig2.add_axes([0., 0.15, 0.9, 0.05])
584
+
585
+ histogram = ax.bar(bin_edges[0:-1], hist, width=width, color=colors,
586
+ edgecolor='black', alpha=0.8)
587
+ onselect(vmin, vmax)
588
+ cb1 = matplotlib.colorbar.ColorbarBase(ax1, cmap=cmap, norm=norm2, orientation='horizontal')
589
+
590
+ rectprops = dict(facecolor='blue', alpha=0.5)
591
+
592
+ span = matplotlib.widgets.SpanSelector(ax, onselect, 'horizontal', props=rectprops)
593
+
594
+ cid = fig2.canvas.mpl_connect('button_press_event', onclick)
595
+ return span
596
+
597
+
598
+ def calculate_ctf(wavelength, cs, defocus, k):
599
+ """ Calculate Contrast Transfer Function
600
+
601
+ everything in nm
602
+
603
+ Parameters
604
+ ----------
605
+ wavelength: float
606
+ deBroglie wavelength of electrons
607
+ cs: float
608
+ spherical aberration coefficient
609
+ defocus: float
610
+ defocus
611
+ k: numpy array
612
+ reciprocal scale
613
+
614
+ Returns
615
+ -------
616
+ ctf: numpy array
617
+ contrast transfer function
618
+
619
+ """
620
+ ctf = np.sin(np.pi*defocus*wavelength*k**2+0.5*np.pi*cs*wavelength**3*k**4)
621
+ return ctf
622
+
623
+
624
+ def calculate_scherzer(wavelength, cs):
625
+ """
626
+ Calculate the Scherzer defocus. Cs is in mm, lambda is in nm
627
+
628
+ # Input and output in nm
629
+ """
630
+
631
+ scherzer = -1.155*(cs*wavelength)**0.5 # in m
632
+ return scherzer
633
+
634
+
635
+ def get_rotation(experiment_spots, crystal_spots):
636
+ """Get rotation by comparing spots in diffractogram to diffraction Bragg spots
637
+
638
+ Parameter
639
+ ---------
640
+ experiment_spots: numpy array (nx2)
641
+ positions (in 1/nm) of spots in diffractogram
642
+ crystal_spots: numpy array (nx2)
643
+ positions (in 1/nm) of Bragg spots according to kinematic scattering theory
644
+ """
645
+ r_experiment, phi_experiment = cart2pol(experiment_spots)
646
+
647
+ # get crystal spots of same length and sort them by angle as well
648
+ r_crystal, phi_crystal, _ = xy2polar(crystal_spots)
649
+ angle_index = np.argmin(np.abs(r_experiment-r_crystal[1]))
650
+ rotation_angle = phi_experiment[angle_index] % (2*np.pi) - phi_crystal[1]
651
+ print(phi_experiment[angle_index])
652
+ st = np.sin(rotation_angle)
653
+ ct = np.cos(rotation_angle)
654
+ rotation_matrix = np.array([[ct, -st], [st, ct]])
655
+
656
+ return rotation_matrix, rotation_angle
657
+
658
+
659
+ def calibrate_image_scale(fft_tags, spots_reference, spots_experiment):
660
+ """depreciated get change of scale from comparison of spots to Bragg angles """
661
+
662
+ dist_reference = np.linalg.norm(spots_reference, axis=1)
663
+ distance_experiment = np.linalg.norm(spots_experiment, axis=1)
664
+
665
+ first_reflections = abs(distance_experiment - dist_reference.min()) < .2
666
+ print('Evaluate ', first_reflections.sum(), 'reflections')
667
+ closest_exp_reflections = spots_experiment[first_reflections]
668
+
669
+ def func(params, xdata, ydata):
670
+ dgx, dgy = params
671
+ return np.sqrt((xdata * dgx) ** 2 + (ydata * dgy) ** 2) - dist_reference.min()
672
+
673
+ x0 = [1.001, 0.999]
674
+ [dg, _] = scipy.optimize.leastsq(func, x0, args=(closest_exp_reflections[:, 0],
675
+ closest_exp_reflections[:, 1]))
676
+ return dg
677
+
678
+
679
+ def align_crystal_reflections(spots, crystals):
680
+ """ Depreciated - use diffraction spots"""
681
+
682
+ crystal_reflections_polar = []
683
+ angles = []
684
+ exp_r, exp_phi = cart2pol(spots) # just in polar coordinates
685
+
686
+ for tags in crystals:
687
+ # sorted by r and phi , only positive angles
688
+ r, phi, indices = xy2polar(tags['allowed']['g'])
689
+ # we mask the experimental values that are found already
690
+ angle = 0.
691
+
692
+ angle_i = np.argmin(np.abs(exp_r - r[1]))
693
+ angle = exp_phi[angle_i] - phi[0]
694
+ angles.append(angle) # Determine rotation angle
695
+
696
+ crystal_reflections_polar.append([r, angle + phi, indices])
697
+ tags['allowed']['g_rotated'] = pol2cart(r, angle + phi)
698
+ """for spot in tags['allowed']['g']:
699
+ dif = np.linalg.norm(spots[:, 0:2]-spot[0:2], axis=1)
700
+ """
701
+ return crystal_reflections_polar, angles
@@ -0,0 +1 @@
1
+ from pyTEMlib import image_dialog