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/utilities.py ADDED
@@ -0,0 +1,431 @@
1
+ """
2
+ # ###############################################################
3
+ # Utility Functions for spectroscopy data in pyTEMlib
4
+ # ################################################################
5
+ """
6
+ import typing
7
+ import warnings
8
+ import numpy as np
9
+ from numba import jit
10
+ import scipy
11
+ import sidpy
12
+
13
+ from .xrpa_x_sections import x_sections
14
+
15
+ ELECTRON_REST_ENERGY = 5.10998918e5 # electron rest energy in eV
16
+
17
+ major_edges = ['K1', 'L3', 'M5', 'N5']
18
+
19
+ all_edges = ['K1', 'L1', 'L2', 'L3', 'M1', 'M2', 'M3', 'M4', 'M5',
20
+ 'N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'O1', 'O2',
21
+ 'O3', 'O4', 'O5', 'O6', 'O7', 'P1', 'P2', 'P3']
22
+
23
+ shell_occupancy = {'K1': 2, 'L1': 2, 'L2': 2, 'L3': 4, 'M1': 2, 'M2': 2, 'M3': 4, 'M4': 4, 'M5': 6,
24
+ 'N1': 2, 'N2': 2, 'N3': 4, 'N4': 4, 'N5': 6, 'N6': 6, 'N7': 8, 'O1': 2, 'O2': 2,
25
+ 'O3': 4, 'O4': 4, 'O5': 6, 'O6': 6, 'O7': 8, 'O8': 8, 'O9': 10}
26
+
27
+
28
+ first_close_edges = ['K1', 'L3', 'M5', 'M3', 'N5', 'N3']
29
+
30
+ elements = [' ', 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na',
31
+ 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V',
32
+ 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br',
33
+ 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag',
34
+ 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr',
35
+ 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu',
36
+ 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi']
37
+
38
+ def get_wavelength(acceleration_voltage: float) -> float:
39
+ """
40
+ Calculates the relativistic corrected de Broglie wavelength of an electron in meter
41
+
42
+ Parameter:
43
+ ---------
44
+ acceleration_voltage: float
45
+ acceleration voltage in volt
46
+ Returns:
47
+ -------
48
+ wavelength: float
49
+ wave length in meter
50
+ """
51
+ if not isinstance(acceleration_voltage, (int, float)):
52
+ raise TypeError('Acceleration voltage has to be a real number')
53
+
54
+ ev = acceleration_voltage * scipy.constants.elementary_charge
55
+ h = scipy.constants.Planck
56
+ m0 = scipy.constants.electron_mass
57
+ c = scipy.constants.speed_of_light
58
+ wavelength = h / np.sqrt(2 * m0 * ev * (1 + (ev / (2 * m0 * c ** 2))))
59
+ return wavelength
60
+
61
+
62
+ def get_wave_length(acceleration_voltage: float) -> float:
63
+ """Deprecated function, use get_wavelength instead"""
64
+ warnings.warn("get_wave_length is deprecated, use get_wavelength instead",
65
+ DeprecationWarning,
66
+ stacklevel=2)
67
+ return get_wavelength(acceleration_voltage)
68
+
69
+
70
+ def depth_of_focus(acceleration_voltage: float, convergence_angle: float) -> float:
71
+ """calculate depth of focus
72
+
73
+ Parameters
74
+ ----------
75
+ acceleration_voltage : float
76
+ acceleration voltage in eV
77
+ convergence_angle : float
78
+ convergence angle in radians
79
+
80
+ Returns
81
+ -------
82
+ float
83
+ depth of focus in meters
84
+ """
85
+
86
+ wavelength = get_wavelength(acceleration_voltage)
87
+ return wavelength / convergence_angle**2
88
+
89
+
90
+ def current_to_number_of_electrons(current: float) -> float:
91
+ """convert current in Ampere to number of electrons per second
92
+
93
+ Parameters
94
+ ----------
95
+ current : float
96
+ current in Ampere
97
+
98
+ Returns
99
+ -------
100
+ float
101
+ number of electrons per second
102
+ """
103
+ return current / scipy.constants.elementary_charge
104
+
105
+
106
+
107
+ def effective_collection_angle(energy_scale: np.ndarray,
108
+ alpha: float,
109
+ beta: float,
110
+ beam_ev: float) -> float:
111
+ """Calculates the effective collection angle in mrad:
112
+
113
+ Translate from original Fortran program
114
+ Calculates the effective collection angle in mrad:
115
+ Parameter
116
+ ---------
117
+ energy_scale: numpy array
118
+ first and last energy loss of spectrum in eV
119
+ alpha: float
120
+ convergence angle in mrad
121
+ beta: float
122
+ collection angle in mrad
123
+ beamKV: float
124
+ acceleration voltage in V
125
+
126
+ Returns
127
+ -------
128
+ eff_beta: float
129
+ effective collection angle in mrad
130
+
131
+ # function y = effbeta(ene, alpha, beta, beam_kv) Note Pierre uses keV
132
+ #
133
+ # This program computes etha(alpha,beta), that is the collection
134
+ # efficiency associated to the following geometry :
135
+ #
136
+ # alpha = half angle of illumination (0 -> pi/2)
137
+ # beta = half angle of collection (0 -> pi/2)
138
+ # (pi/2 = 1570.795 mrad)
139
+ #
140
+ # A constant angular distribution of incident electrons is assumed
141
+ # for any incident angle (-alpha,alpha). These electrons imping the
142
+ # target and a single energy-loss event occurs, with a characteristic
143
+ # angle theta-e (relativistic). The angular distribution of the
144
+ # electrons after the target is analytically derived.
145
+ # This program integrates this distribution from theta=0 up to
146
+ # theta=beta with an adjustable angular step.
147
+ # This program also computes beta* which is the theoretical
148
+ # collection angle which would give the same value of etha(alpha,beta)
149
+ # with a parallel incident beam.
150
+ #
151
+ # subroutines and function subprograms required
152
+ # ---------------------------------------------
153
+ # none
154
+ #
155
+ # comments
156
+ # --------
157
+ #
158
+ # The following parameters are asked as input :
159
+ # accelerating voltage (kV), energy loss range (eV) for the study,
160
+ # energy loss step (eV) in this range, alpha (mrad), beta (mrad).
161
+ # The program returns for each energy loss step :
162
+ # alpha (mrad), beta (mrad), theta-e (relativistic) (mrad),
163
+ # energy loss (eV), etha (#), beta * (mrad)
164
+ #
165
+ # author :
166
+ # --------
167
+ # Pierre TREBBIA
168
+ # US 41 : "Microscopie Electronique Analytique Quantitative"
169
+ # Laboratoire de Physique des Solides, Bat. 510
170
+ # Universite Paris-Sud, F91405 ORSAY Cedex
171
+ # Phone : (33-1) 69 41 53 68
172
+ #
173
+ """
174
+ if beam_ev == 0:
175
+ beam_ev = 100.0 * 1e3
176
+
177
+ if alpha == 0:
178
+ return beta
179
+
180
+ if beta == 0:
181
+ return alpha
182
+
183
+ alpha = alpha * 0.001 # rad
184
+ beta = beta * 0.001 # rad
185
+ z7 = 500.0 # number of integration steps to be modified at will
186
+
187
+ # main loop on energy loss
188
+ for zx in range(int(energy_scale[0]), int(energy_scale[-1]), 100):
189
+ # ! zx = current energy loss
190
+ eta = 0.0
191
+ # x0 = relativistic theta-e
192
+ x0 = float(zx) * (beam_ev + 511060.) / (beam_ev * (beam_ev + 1022120.))
193
+ dtheta = (beta - 0.1 * np.sqrt((x0**2 + alpha**2))) / 500 # integration steps
194
+ #
195
+ # calculation of the analytical expression
196
+ #
197
+ for zi in range(1, int(z7)):
198
+ theta = 0.1 * np.sqrt((x0**2 + alpha**2)) + dtheta * float(zi)
199
+ x5 = theta**2
200
+ x6 = 4. * x5 * x0 * x0
201
+ x7 = (x0**2 + alpha**2) - x5
202
+ eta += 2. * theta * dtheta * np.log((np.sqrt(x7**2 + x6) + x7) / (2. * x0**2))
203
+ # addition of the central contribution
204
+ eta = eta + (x0**2 + alpha**2) / 100. * np.log(1. + alpha**2/x0**2)
205
+ # normalisation
206
+ eta = eta / alpha * alpha * np.log(1. + np.pi**2 / (4. * x0**2))
207
+ #
208
+ # correction by geometrical factor (beta/alpha)**2
209
+ #
210
+ if beta < alpha:
211
+ x5 = alpha / beta
212
+ eta = eta * x5**2
213
+
214
+ # etha2 = eta * 100.
215
+ #
216
+ # calculation of beta *
217
+ #
218
+ x6 = np.power((1. + (1. + np.pi**2 / (4. * x0**2))), eta)
219
+ x7 = x0 * np.sqrt(x6 - 1.)
220
+ beta = x7 * 1000. # in mrad
221
+
222
+ return beta
223
+
224
+ def set_default_metadata(current_dataset: sidpy.Dataset) -> None:
225
+ """sets default metadata for the dataset"""
226
+
227
+ if 'experiment' not in current_dataset.metadata:
228
+ current_dataset.metadata['experiment'] = {}
229
+ if 'convergence_angle' not in current_dataset.metadata['experiment']:
230
+ current_dataset.metadata['experiment']['convergence_angle'] = 30
231
+ if 'collection_angle' not in current_dataset.metadata['experiment']:
232
+ current_dataset.metadata['experiment']['collection_angle'] = 50
233
+ if 'acceleration_voltage' not in current_dataset.metadata['experiment']:
234
+ current_dataset.metadata['experiment']['acceleration_voltage'] = 200000
235
+
236
+
237
+ def lorentz(x, center, amplitude, width):
238
+ """ Lorentzian Function """
239
+ lorentz_peak = 0.5 * width / np.pi / ((x - center)**2 + (width / 2)**2)
240
+ return amplitude * lorentz_peak / lorentz_peak.max()
241
+
242
+ @jit
243
+ def gauss(x, p): # p[0]==mean, p[1]= amplitude p[2]==fwhm,
244
+ """Gaussian Function
245
+
246
+ p[0]==mean, p[1]= amplitude p[2]==fwhm
247
+ area = np.sqrt(2* np.pi)* p[1] * np.abs(p[2] / 2.3548)
248
+ FWHM = 2 * np.sqrt(2 np.log(2)) * sigma = 2.3548 * sigma
249
+ sigma = FWHM/3548
250
+ """
251
+ if p[2] == 0:
252
+ return x * 0.
253
+ return p[1] * np.exp(-(x - p[0])**2 / (2.0 * (p[2] / 2.3548)**2))
254
+
255
+ def get_atomic_number(z):
256
+ """Returns the atomic number independent of input as a string or number"""
257
+ return get_z(z)
258
+
259
+ def get_z(z: typing.Union[int, str]) -> int:
260
+ """Returns the atomic number independent of input as a string or number
261
+
262
+ Parameter
263
+ ---------
264
+ z: int, str
265
+ atomic number of chemical symbol (0 if not valid)
266
+ Return:
267
+ ------
268
+ z_out: int
269
+ atomic number
270
+ """
271
+ z_out = 0
272
+ if str(z).isdigit():
273
+ z_out = int(z)
274
+ elif isinstance(z, str):
275
+ z_out = elements.index(z)
276
+ else:
277
+ raise TypeError('A valid element string or number is required')
278
+ return z_out
279
+
280
+
281
+ def get_element_symbol(z: typing.Union[int, str]) -> str:
282
+ """Returns the element symbol independent of input as a string or number
283
+
284
+ Parameter
285
+ ---------
286
+ z: int, str
287
+ atomic number of chemical symbol (0 if not valid)
288
+ Return:
289
+ ------
290
+ symbol: str
291
+ element symbol
292
+ """
293
+ z_num = get_z(z)
294
+ if 0 < z_num < len(elements):
295
+ return elements[int(z_num)]
296
+ return ''
297
+
298
+
299
+ def get_x_sections(z: int=0) -> dict:
300
+ """Reads X-ray fluorescent cross-sections from a dictionary.
301
+
302
+ Parameters
303
+ ----------
304
+ z: int
305
+ atomic number if zero all cross-sections will be returned
306
+
307
+ Returns
308
+ -------
309
+ dictionary
310
+ cross-section of an element or of all elements if z = 0
311
+ """
312
+ if z < 1:
313
+ return x_sections
314
+ z = str(z)
315
+ if z in x_sections:
316
+ return x_sections[z]
317
+ return {}
318
+
319
+
320
+ def get_spectrum(dataset, x=0, y=0, bin_x=1, bin_y=1):
321
+ """
322
+ Extracts a spectrum from a sidpy.Dataset object
323
+ Parameter
324
+ ---------
325
+ dataset: sidpy.Dataset object
326
+ contains spectrum or spectrum image
327
+ x: int default = 0
328
+ x position of spectrum image
329
+ y: int default = 0
330
+ y position of spectrum
331
+ bin_x: int default = 1
332
+ binning of spectrum image in x-direction
333
+ bin_y: int default = 1
334
+ binning of spectrum image in y-direction
335
+
336
+ Returns:
337
+ --------
338
+ spectrum: sidpy.Dataset object
339
+
340
+ """
341
+ if dataset.data_type.name == 'SPECTRUM':
342
+ spectrum = dataset.copy()
343
+ else:
344
+ image_dims = dataset.get_image_dims()
345
+ x = min(x, dataset.shape[image_dims[0]] - bin_x)
346
+ y = min(y, dataset.shape[image_dims[1]] - bin_y)
347
+ selection = []
348
+ dimensions = dataset.get_dimension_types()
349
+ for dim, dimension_type in enumerate(dimensions):
350
+ # print(dim, axis.dimension_type)
351
+ if dimension_type == 'SPATIAL':
352
+ if dim == image_dims[0]:
353
+ selection.append(slice(x, x + bin_x))
354
+ else:
355
+ selection.append(slice(y, y + bin_y))
356
+ elif dimension_type == 'SPECTRAL':
357
+ selection.append(slice(None))
358
+ elif dimension_type == 'CHANNEL':
359
+ selection.append(slice(None))
360
+ else:
361
+ selection.append(slice(0, 1))
362
+ spectrum = dataset[tuple(selection)].mean(axis=tuple(image_dims))
363
+ spectrum.squeeze().compute()
364
+ spectrum.data_type = 'Spectrum'
365
+ return spectrum
366
+
367
+ def second_derivative(dataset: sidpy.Dataset) -> None:
368
+ """Calculates second derivative of a sidpy.dataset"""
369
+ energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
370
+ if dataset.data_type.name == 'SPECTRAL_IMAGE':
371
+ spectrum = dataset.view.get_spectrum()
372
+ else:
373
+ spectrum = np.array(dataset)
374
+ spec = scipy.ndimage.gaussian_filter(spectrum, 3)
375
+ dispersion = energy_scale.slope
376
+ second_dif = np.roll(spec, -3) - 2 * spec + np.roll(spec, +3)
377
+ second_dif[:3] = 0
378
+ second_dif[-3:] = 0
379
+
380
+ # find if there is a strong edge at high energy_scale
381
+ noise_level = 2. * np.std(second_dif[3:50])
382
+ [indices, _] = scipy.signal.find_peaks(second_dif, noise_level)
383
+ width = max(50 / dispersion, 50)
384
+ start_end_noise = int(len(energy_scale) - width)
385
+ for index in indices[::-1]:
386
+ if index > start_end_noise:
387
+ start_end_noise = index - 70
388
+
389
+ # noise_level_start = sensitivity * np.std(second_dif[3:50])
390
+ # noise_level_end = sensitivity * np.std(second_dif[start_end_noise: start_end_noise + 50])
391
+ # slope = (noise_level_end - noise_level_start) / (len(energy_scale) - 400)
392
+ # noise_level = noise_level_start #+ np.arange(len(energy_scale)) * slope
393
+ return second_dif , noise_level
394
+
395
+ def get_rotation_matrix(angles, in_radians=False):
396
+ """ Rotation of zone axis by mistilt
397
+
398
+ Parameters
399
+ ----------
400
+ angles: ist or numpy array of float
401
+ list of mistilt angles (default in degrees)
402
+ in_radians: boolean default False
403
+ default is angles in degrees
404
+
405
+ Returns
406
+ -------
407
+ rotation_matrix: np.ndarray (3x3)
408
+ rotation matrix in 3d
409
+ """
410
+
411
+ if not isinstance(angles, (np.ndarray, list)):
412
+ raise TypeError('angles must be a list of float of length 3')
413
+ if len(angles) != 3:
414
+ raise TypeError('angles must be a list of float of length 3')
415
+
416
+ if in_radians:
417
+ alpha, beta, gamma = angles
418
+ else:
419
+ alpha, beta, gamma = np.radians(angles)
420
+ # first we rotate alpha about x-axis
421
+ c, s = np.cos(alpha), np.sin(alpha)
422
+ rot_x = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
423
+
424
+ # second we rotate beta about y-axis
425
+ c, s = np.cos(beta), np.sin(beta)
426
+ rot_y = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
427
+
428
+ # third we rotate gamma about z-axis
429
+ c, s = np.cos(gamma), np.sin(gamma)
430
+ rot_z = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
431
+ return np.dot(np.dot(rot_x, rot_y), rot_z)
pyTEMlib/version.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ version
3
+ """
4
+ __version__ = '0.2025.12.0'
5
+ __time__ = '2025-11-16 20:58:26'