pyTEMlib 0.2023.8.0__tar.gz → 0.2024.2.0__tar.gz

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 (50) hide show
  1. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/LICENSE +1 -1
  2. {pyTEMlib-0.2023.8.0/pyTEMlib.egg-info → pyTEMlib-0.2024.2.0}/PKG-INFO +1 -1
  3. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/config_dir.py +0 -1
  4. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/crystal_tools.py +22 -26
  5. pyTEMlib-0.2024.2.0/pyTEMlib/eds_tools.py +558 -0
  6. pyTEMlib-0.2024.2.0/pyTEMlib/eels_dialog.py +748 -0
  7. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/eels_dialog_utilities.py +218 -341
  8. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/eels_tools.py +1526 -1583
  9. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/file_tools.py +52 -48
  10. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/graph_tools.py +3 -4
  11. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/image_tools.py +171 -41
  12. pyTEMlib-0.2024.2.0/pyTEMlib/info_widget.py +997 -0
  13. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/kinematic_scattering.py +77 -512
  14. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/peak_dialog.py +162 -288
  15. pyTEMlib-0.2024.2.0/pyTEMlib/version.py +6 -0
  16. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/xrpa_x_sections.py +173 -97
  17. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0/pyTEMlib.egg-info}/PKG-INFO +1 -1
  18. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib.egg-info/SOURCES.txt +0 -5
  19. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib.egg-info/requires.txt +1 -1
  20. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/setup.py +1 -1
  21. pyTEMlib-0.2023.8.0/pyTEMlib/eds_tools.py +0 -105
  22. pyTEMlib-0.2023.8.0/pyTEMlib/eels_dialog.py +0 -1363
  23. pyTEMlib-0.2023.8.0/pyTEMlib/eels_dlg.py +0 -252
  24. pyTEMlib-0.2023.8.0/pyTEMlib/info_dialog.py +0 -665
  25. pyTEMlib-0.2023.8.0/pyTEMlib/info_dlg.py +0 -239
  26. pyTEMlib-0.2023.8.0/pyTEMlib/info_widget.py +0 -655
  27. pyTEMlib-0.2023.8.0/pyTEMlib/interactive_eels.py +0 -35
  28. pyTEMlib-0.2023.8.0/pyTEMlib/version.py +0 -6
  29. pyTEMlib-0.2023.8.0/pyTEMlib/viz.py +0 -481
  30. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/MANIFEST.in +0 -0
  31. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/README.rst +0 -0
  32. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/__init__.py +0 -0
  33. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/animation.py +0 -0
  34. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/atom_tools.py +0 -0
  35. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/diffraction_plot.py +0 -0
  36. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/dynamic_scattering.py +0 -0
  37. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/file_tools_qt.py +0 -0
  38. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/graph_viz.py +0 -0
  39. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/image_dialog.py +0 -0
  40. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/image_dlg.py +0 -0
  41. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/interactive_image.py +0 -0
  42. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/microscope.py +0 -0
  43. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/peak_dlg.py +0 -0
  44. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/probe_tools.py +0 -0
  45. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/sidpy_tools.py +0 -0
  46. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib/simulation_tools.py +0 -0
  47. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib.egg-info/dependency_links.txt +0 -0
  48. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib.egg-info/entry_points.txt +0 -0
  49. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/pyTEMlib.egg-info/top_level.txt +0 -0
  50. {pyTEMlib-0.2023.8.0 → pyTEMlib-0.2024.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019 Gerd Duscher
3
+ Copyright (c) 2024, Gerd
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: pyTEMlib
3
- Version: 0.2023.8.0
3
+ Version: 0.2024.2.0
4
4
  Summary: pyTEM: TEM Data Quantification library through a model-based approach
5
5
  Home-page: https://pycroscopy.github.io/pyTEMlib/about.html
6
6
  Author: Gerd Duscher
@@ -7,7 +7,6 @@
7
7
  config_dir: setup of directory ~/.pyTEMlib for custom sources and database
8
8
  """
9
9
  import os
10
- import numpy as np
11
10
 
12
11
  # import wget
13
12
  if os.name == 'posix':
@@ -27,6 +27,7 @@ import ase.data.colors
27
27
 
28
28
  import matplotlib.pylab as plt # basic plotting
29
29
  from scipy.spatial import cKDTree
30
+ import scipy
30
31
  _spglib_present = True
31
32
  try:
32
33
  import spglib
@@ -137,26 +138,26 @@ def get_projection(crystal, layers=1):
137
138
  for pro in projected:
138
139
  atomic_numbers.append(projected_crystal.get_atomic_numbers()[pro].sum())
139
140
 
140
- projected_crystal.rotate(np.degrees(angle)%360, 'z', rotate_cell=True)
141
+ projected_crystal.rotate(np.degrees(angle) % 360, 'z', rotate_cell=True)
141
142
 
142
- near_base = np.array([projected_crystal.cell[0,:2], -projected_crystal.cell[0,:2],
143
- projected_crystal.cell[1,:2], -projected_crystal.cell[1,:2],
144
- projected_crystal.cell[0,:2] + projected_crystal.cell[1,:2],
145
- -(projected_crystal.cell[0,:2] + projected_crystal.cell[1,:2])])
146
- lines = np.array( [[[0, near_base[0,0]],[0, near_base[0,1]]],
147
- [[0, near_base[2,0]],[0, near_base[2,1]]],
148
- [[near_base[0,0], near_base[4,0]],[near_base[0,1], near_base[4,1]]],
149
- [[near_base[2,0], near_base[4,0]],[near_base[2,1], near_base[4,1]]]])
143
+ near_base = np.array([projected_crystal.cell[0, :2], -projected_crystal.cell[0, :2],
144
+ projected_crystal.cell[1, :2], -projected_crystal.cell[1, :2],
145
+ projected_crystal.cell[0, :2] + projected_crystal.cell[1, :2],
146
+ -(projected_crystal.cell[0, :2] + projected_crystal.cell[1, :2])])
147
+ lines = np.array([[[0, near_base[0, 0]], [0, near_base[0, 1]]],
148
+ [[0, near_base[2, 0]], [0, near_base[2, 1]]],
149
+ [[near_base[0, 0], near_base[4, 0]], [near_base[0, 1], near_base[4, 1]]],
150
+ [[near_base[2, 0], near_base[4, 0]], [near_base[2, 1], near_base[4, 1]]]])
150
151
  projected_atoms = []
151
152
  for index in projected:
152
- projected_atoms.append(index[0])
153
+ projected_atoms.append(index[0])
153
154
 
154
- projected_crystal.info['projection']={'indices': projected,
155
- 'projected': projected_atoms,
156
- 'projected_Z': atomic_numbers,
157
- 'angle': np.degrees(angle)+180%360,
158
- 'near_base': near_base,
159
- 'lines': lines}
155
+ projected_crystal.info['projection'] = {'indices': projected,
156
+ 'projected': projected_atoms,
157
+ 'projected_Z': atomic_numbers,
158
+ 'angle': np.degrees(angle)+180 % 360,
159
+ 'near_base': near_base,
160
+ 'lines': lines}
160
161
  return projected_crystal
161
162
 
162
163
 
@@ -283,13 +284,8 @@ def ball_and_stick(atoms, extend=1, max_bond_length=0.):
283
284
  neighbor_list.update(super_cell)
284
285
  bond_matrix = neighbor_list.get_connectivity_matrix()
285
286
 
286
- del_double = []
287
- for (k, s) in bond_matrix.keys():
288
- if k > s:
289
- del_double.append((k, s))
290
- for key in del_double:
291
- bond_matrix.pop(key)
292
-
287
+ bond_matrix = np.triu(bond_matrix.toarray())
288
+ bond_matrix = scipy.sparse.dok_array(bond_matrix)
293
289
  if super_cell.info is None:
294
290
  super_cell.info = {}
295
291
  super_cell.info['plot_cell'] = {'bond_matrix': bond_matrix, 'corner_vectors': corner_vectors,
@@ -298,7 +294,7 @@ def ball_and_stick(atoms, extend=1, max_bond_length=0.):
298
294
  return super_cell
299
295
 
300
296
 
301
- def plot_unit_cell(atoms, extend=1, max_bond_length=1.0, ax = None):
297
+ def plot_unit_cell(atoms, extend=1, max_bond_length=1.0, ax=None):
302
298
  """
303
299
  Simple plot of unit cell
304
300
  """
@@ -309,8 +305,8 @@ def plot_unit_cell(atoms, extend=1, max_bond_length=1.0, ax = None):
309
305
  positions = super_cell.positions - super_cell.cell.lengths()/2
310
306
 
311
307
  if ax is None:
312
- fig = plt.figure()
313
- ax = fig.add_subplot(111, projection='3d')
308
+ fig = plt.figure()
309
+ ax = fig.add_subplot(111, projection='3d')
314
310
  # draw unit_cell
315
311
 
316
312
  for line in super_cell.info['plot_cell']['corner_matrix'].keys():
@@ -0,0 +1,558 @@
1
+ """
2
+ eds_tools
3
+ Model based quantification of energy-dispersive X-ray spectroscopy data
4
+ Copyright by Gerd Duscher
5
+
6
+ The University of Tennessee, Knoxville
7
+ Department of Materials Science & Engineering
8
+
9
+ Sources:
10
+
11
+ Units:
12
+ everything is in SI units, except length is given in nm and angles in mrad.
13
+
14
+ Usage:
15
+ See the notebooks for examples of these routines
16
+
17
+ All the input and output is done through a dictionary which is to be found in the meta_data
18
+ attribute of the sidpy.Dataset
19
+ """
20
+ import numpy as np
21
+
22
+ import scipy
23
+ from scipy.interpolate import interp1d, splrep # splev, splint
24
+ from scipy import interpolate
25
+ from scipy.signal import peak_prominences
26
+ from scipy.ndimage import gaussian_filter
27
+ from sklearn.mixture import GaussianMixture
28
+ from sklearn.cluster import KMeans
29
+ import scipy.constants as const
30
+
31
+ from scipy import constants
32
+ import matplotlib.pyplot as plt
33
+ # import matplotlib.patches as patches
34
+
35
+ # from matplotlib.widgets import SpanSelector
36
+ # import ipywidgets as widgets
37
+ # from IPython.display import display
38
+
39
+ import requests
40
+
41
+ from scipy.optimize import leastsq # least square fitting routine fo scipy
42
+
43
+ import sidpy
44
+
45
+ import pickle # pkg_resources
46
+ import pyTEMlib.eels_tools
47
+ from pyTEMlib.xrpa_x_sections import x_sections
48
+
49
+ elements_list = pyTEMlib.eels_tools.elements
50
+
51
+ shell_occupancy = {'K1': 2, 'L1': 2, 'L2': 2, 'L3': 4, 'M1': 2, 'M2': 2, 'M3': 4, 'M4': 4, 'M5': 6,
52
+ 'N1': 2, 'N2': 2, 'N3': 4, 'N4': 4, 'N5': 6, 'N6': 6, 'N7': 8,
53
+ 'O1': 2, 'O2': 2, 'O3': 4, 'O4': 4, 'O5': 6, 'O6': 6, 'O7': 8, 'O8': 8, 'O9': 10}
54
+
55
+
56
+ def detector_response(dataset):
57
+ tags = dataset.metadata['experiment']
58
+
59
+ tags['acceleration_voltage_V'] = tags['microscope']['acceleration_voltage_V']
60
+ energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
61
+ if 'start_channel' not in tags['detector']:
62
+ tags['detector']['start_channel'] = np.searchsorted(energy_scale, 100)
63
+
64
+ start = tags['detector']['start_channel']
65
+ detector_efficiency = np.zeros(len(dataset))
66
+ detector_efficiency[start:] += get_detector_response(tags, energy_scale[start:])
67
+ tags['detector']['detector_efficiency'] = detector_efficiency
68
+ return detector_efficiency
69
+
70
+
71
+ def get_detector_response(detector_definition, energy_scale):
72
+ """
73
+ Calculates response of Si drift detector for EDS spectrum background based on detector parameters
74
+
75
+ Parameters:
76
+ ----------
77
+ detector_definition: dictionary
78
+ definition of detector
79
+ energy_scale: numpy array (1 dim)
80
+ energy scale of spectrum should start at about 100eV!!
81
+
82
+ Return:
83
+ -------
84
+ response: numpy array with length(energy_scale)
85
+ detector response
86
+
87
+ Example
88
+ -------
89
+
90
+ tags ={}
91
+ tags['acceleration_voltage_V'] = 200000
92
+
93
+ tags['detector'] ={}
94
+
95
+ ## layer thicknesses of commen materials in EDS detectors in m
96
+ tags['detector']['Al_thickness'] = 0.03 * 1e-6 # in m
97
+ tags['detector']['Be_thickness'] = 0. # in m
98
+ tags['detector']['Au_thickness'] = 0.0 * 1e-6 # in m
99
+ tags['detector']['Par_thickness'] = 0 *1e-6 # in m # Window
100
+
101
+ tags['detector']['SiDeadThickness'] = .03 *1e-6 # in m
102
+
103
+ tags['detector']['SiLiveThickness'] = 0.05 # in m
104
+ tags['detector']['detector_area'] = 30 * 1e-6 #in m2
105
+ tags['detector']['resolution'] = 125 # in eV
106
+
107
+ energy_scale = np.linspace(.01,20,1199)*1000 # i eV
108
+ start = np.searchsorted(spectrum.energy, 100)
109
+ energy_scale = spectrum.energy[start:]
110
+ detector_Efficiency= pyTEMlib.eds_tools.detector_response(tags, spectrum.energy[start:])
111
+
112
+ p = np.array([1, 37, .3])/10000*3
113
+ E_0= 200000
114
+ background = np.zeros(len(spectrum))
115
+ background[start:] = detector_Efficiency * (p[0] + p[1]*(E_0-energy_scale)/energy_scale + p[2]*(E_0-energy_scale)**2/energy_scale)
116
+
117
+
118
+ plt.figure()
119
+ plt.plot(spectrum.energy, spectrum, label = 'spec')
120
+ plt.plot(spectrum.energy, background, label = 'background')
121
+ plt.show()
122
+
123
+ """
124
+ response = np.ones(len(energy_scale))
125
+ x_sections = pyTEMlib.eels_tools.get_x_sections()
126
+
127
+ def get_absorption(Z, t):
128
+ photoabsorption = x_sections[str(Z)]['dat']/1e10/x_sections[str(Z)]['photoabs_to_sigma']
129
+ lin = interp1d(x_sections[str(Z)]['ene'], photoabsorption, kind='linear')
130
+ mu = lin(energy_scale) * x_sections[str(Z)]['nominal_density']*100. #1/cm -> 1/m
131
+ return np.exp(-mu * t)
132
+
133
+ if 'Al_thickness' in detector_definition['detector']:
134
+ response *= get_absorption(13, detector_definition['detector']['Al_thickness'])
135
+ if 'Be_thickness' in detector_definition['detector']:
136
+ response *= get_absorption(5, detector_definition['detector']['Be_thickness'])
137
+ if 'Au_thickness' in detector_definition['detector']:
138
+ response *= get_absorption(79, detector_definition['detector']['Au_thickness'])
139
+ if 'Par_thickness' in detector_definition['detector']:
140
+ response *= get_absorption(6, detector_definition['detector']['Par_thickness'])
141
+ if 'SiDeadThickness' in detector_definition['detector']:
142
+ response *= get_absorption(14, detector_definition['detector']['SiDeadThickness'])
143
+
144
+ if 'SiLiveThickness' in detector_definition['detector']:
145
+ response *= 1-get_absorption(14, detector_definition['detector']['SiLiveThickness'])
146
+ return response
147
+
148
+
149
+ def detect_peaks(dataset, minimum_number_of_peaks=30):
150
+ if not isinstance(dataset, sidpy.Dataset):
151
+ raise TypeError('Needs an sidpy dataset')
152
+ if not dataset.data_type.name == 'SPECTRUM':
153
+ raise TypeError('Need a spectrum')
154
+
155
+ energy_scale = dataset.get_spectral_dims(return_axis=True)[0]
156
+ if 'detector' not in dataset.metadata:
157
+ if 'energy_resolution' not in dataset.metadata['detector']:
158
+ dataset.metadata['detector']['energy_resolution'] = 138
159
+ print('Using energy resolution of 138 eV')
160
+ if 'start_channel' not in dataset.metadata['detector']:
161
+ dataset.metadata['detector']['start_channel'] = start = np.searchsorted(energy_scale, 100)
162
+ resolution = dataset.metadata['detector']['energy_resolution']
163
+
164
+ start = dataset.metadata['detector']['start_channel']
165
+ ## we use half the width of the resolution for smearing
166
+ width = int(np.ceil(resolution/(energy_scale[1]-energy_scale[0])/2)+1)
167
+ new_spectrum = scipy.signal.savgol_filter(np.array(dataset)[start:], width, 2) ## we use half the width of the resolution for smearing
168
+ prominence = 10
169
+ minor_peaks, _ = scipy.signal.find_peaks(new_spectrum, prominence=prominence)
170
+
171
+ while len(minor_peaks) > minimum_number_of_peaks:
172
+ prominence+=10
173
+ minor_peaks, _ = scipy.signal.find_peaks(new_spectrum, prominence=prominence)
174
+ return np.array(minor_peaks)+start
175
+
176
+ def find_elements(spectrum, minor_peaks):
177
+ if not isinstance(spectrum, sidpy.Dataset):
178
+ raise TypeError(' Need a sidpy dataset')
179
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
180
+ elements = []
181
+ for peak in minor_peaks:
182
+ found = False
183
+ for element in range(3,82):
184
+ if 'lines' in x_sections[str(element)]:
185
+ if 'K-L3' in x_sections[str(element)]['lines']:
186
+ if abs(x_sections[str(element)]['lines']['K-L3']['position']- energy_scale[peak]) <10:
187
+ found = True
188
+ if x_sections[str(element)]['name'] not in elements:
189
+ elements.append( x_sections[str(element)]['name'])
190
+ if not found:
191
+ if 'K-L2' in x_sections[str(element)]['lines']:
192
+ if abs(x_sections[str(element)]['lines']['K-L2']['position']- energy_scale[peak]) <10:
193
+ found = True
194
+ if x_sections[str(element)]['name'] not in elements:
195
+ elements.append( x_sections[str(element)]['name'])
196
+ if not found:
197
+ if 'L3-M5' in x_sections[str(element)]['lines']:
198
+ if abs(x_sections[str(element)]['lines']['L3-M5']['position']- energy_scale[peak]) <30:
199
+ if x_sections[str(element)]['name'] not in elements:
200
+ elements.append( x_sections[str(element)]['name'])
201
+ return elements
202
+
203
+ def get_x_ray_lines(spectrum, elements):
204
+ out_tags = {}
205
+ alpha_K = 1e6
206
+ alpha_L = 6.5e7
207
+ alpha_M = 8*1e8 # 2.2e10
208
+ # My Fit
209
+ alpha_K = .9e6
210
+ alpha_L = 6.e7
211
+ alpha_M = 6*1e8 # 2.2e10
212
+ # omega_K = Z**4/(alpha_K+Z**4)
213
+ # omega_L = Z**4/(alpha_L+Z**4)
214
+ # omega_M = Z**4/(alpha_M+Z**4)
215
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
216
+ for element in elements:
217
+ atomic_number = elements_list.index(element)
218
+ out_tags[element] ={'Z': atomic_number}
219
+
220
+ if 'K-L3' in x_sections[str(atomic_number)]['lines']:
221
+ if x_sections[str(atomic_number)]['lines']['K-L3']['position'] < energy_scale[-1]:
222
+ height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines']['K-L3']['position'] )].compute()
223
+ out_tags[element]['K-family'] = {'height': height}
224
+ if 'K' in x_sections[str(atomic_number)]['fluorescent_yield']:
225
+ out_tags[element]['K-family']['yield'] = x_sections[str(atomic_number)]['fluorescent_yield']['K']
226
+ else:
227
+ out_tags[element]['K-family']['yield'] = atomic_number**4/(alpha_K+atomic_number**4)/4/1.4
228
+
229
+ if 'L3-M5' in x_sections[str(atomic_number)]['lines']:
230
+ if x_sections[str(atomic_number)]['lines']['L3-M5']['position'] < energy_scale[-1]:
231
+ height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines']['L3-M5']['position'] )].compute()
232
+ out_tags[element]['L-family'] = {'height': height}
233
+ if 'L' in x_sections[str(atomic_number)]['fluorescent_yield']:
234
+ out_tags[element]['L-family']['yield'] = x_sections[str(atomic_number)]['fluorescent_yield']['L']
235
+ else:
236
+ out_tags[element]['L-family']['yield'] = (atomic_number**4/(alpha_L+atomic_number**4))**2
237
+
238
+ if 'M5-N6' in x_sections[str(atomic_number)]['lines']:
239
+ if x_sections[str(atomic_number)]['lines']['M5-N6']['position'] < energy_scale[-1]:
240
+ height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines']['M5-N6']['position'] )].compute()
241
+ out_tags[element]['M-family'] = {'height': height}
242
+ if 'M' in x_sections[str(atomic_number)]['fluorescent_yield']:
243
+ out_tags[element]['M-family']['yield'] = x_sections[str(atomic_number)]['fluorescent_yield']['M']
244
+ else:
245
+ out_tags[element]['M-family']['yield'] = (atomic_number**4/(alpha_M+atomic_number**4))**2
246
+
247
+ for key, line in x_sections[str(atomic_number)]['lines'].items():
248
+ other = True
249
+ if line['weight'] > 0.01 and line['position'] < 3e4:
250
+ if 'K-family' in out_tags[element]:
251
+ if key[0] == 'K':
252
+ other = False
253
+ out_tags[element]['K-family'][key]=line
254
+ if 'L-family' in out_tags[element]:
255
+ if key[:2] in ['L2', 'L3']:
256
+ other = False
257
+ out_tags[element]['L-family'][key]=line
258
+ if 'M-family' in out_tags[element]:
259
+ if key[:2] in ['M5', 'M4']:
260
+ other = False
261
+ out_tags[element]['M-family'][key]=line
262
+ if other:
263
+ if 'other' not in out_tags[element]:
264
+ out_tags[element]['other'] = {}
265
+ height = spectrum[np.searchsorted(energy_scale, x_sections[str(atomic_number)]['lines'][key]['position'] )].compute()
266
+ out_tags[element]['other'][key]=line
267
+ out_tags[element]['other'][key]['height'] = height
268
+
269
+ xs = get_eds_cross_sections(atomic_number)
270
+ if 'K' in xs and 'K-family' in out_tags[element]:
271
+ out_tags[element]['K-family']['ionization_x_section'] = xs['K']
272
+ if 'L' in xs and 'L-family' in out_tags[element]:
273
+ out_tags[element]['L-family']['ionization_x_section'] = xs['L']
274
+ if 'M' in xs and 'M-family' in out_tags[element]:
275
+ out_tags[element]['M-family']['ionization_x_section'] = xs['M']
276
+
277
+ """
278
+ We really should use the sum of the family
279
+ for key, x_lines in out_tags.items():
280
+ if 'K-family' in x_lines:
281
+ xs = pyTEMlib.eels_tools.xsec_xrpa(np.arange(100)+x_sections[str(x_lines['Z'])]['K1']['onset'], 200,x_lines['Z'], 100).sum()
282
+
283
+ x_lines['K-family']['ionization_x_section'] = xs
284
+
285
+ if 'L-family' in x_lines:
286
+ xs = pyTEMlib.eels_tools.xsec_xrpa(np.arange(100)+x_sections[str(x_lines['Z'])]['L3']['onset'], 200,x_lines['Z'], 100).sum()
287
+ x_lines['L-family']['ionization_x_section'] = xs
288
+ if 'M-family' in x_lines:
289
+ xs = pyTEMlib.eels_tools.xsec_xrpa(np.arange(100)+x_sections[str(x_lines['Z'])]['M5']['onset'], 200,x_lines['Z'], 100).sum()
290
+ x_lines['M-family']['ionization_x_section'] = xs
291
+ """
292
+ if 'EDS' not in spectrum.metadata:
293
+ spectrum.metadata['EDS'] = {}
294
+
295
+ spectrum.metadata['EDS']['lines'] = out_tags
296
+ return out_tags
297
+
298
+
299
+ def getFWHM(E, E_ref, FWHM_ref):
300
+ return np.sqrt(2.5*(E-E_ref)+FWHM_ref**2)
301
+
302
+ def gaussian(enrgy_scale, mu, FWHM):
303
+ sig = FWHM/2/np.sqrt(2*np.log(2))
304
+ return np.exp(-np.power(np.array(enrgy_scale) - mu, 2.) / (2 * np.power(sig, 2.)))
305
+
306
+ def get_peak(E, energy_scale):
307
+ E_ref = 5895.0
308
+ FWHM_ref = 136 #eV
309
+ FWHM = getFWHM(E, E_ref, FWHM_ref)
310
+ gaus = gaussian(energy_scale, E, FWHM)
311
+
312
+ return gaus /(gaus.sum()+1e-12)
313
+
314
+
315
+ def initial_model_parameter(spectrum):
316
+ tags = spectrum.metadata['EDS']['lines']
317
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0]
318
+ p = []
319
+ peaks = []
320
+ keys = []
321
+ for element, lines in tags.items():
322
+ if 'K-family' in lines:
323
+ model = np.zeros(len(energy_scale))
324
+ for line, info in lines['K-family'].items():
325
+ if line[0] == 'K':
326
+ model += get_peak(info['position'], energy_scale)*info['weight']
327
+ lines['K-family']['peaks'] = model/model.sum()
328
+ lines['K-family']['height'] /= lines['K-family']['peaks'].max()
329
+ p.append(lines['K-family']['height'])
330
+ peaks.append(lines['K-family']['peaks'])
331
+ keys.append(element+':K-family')
332
+ if 'L-family' in lines:
333
+ model = np.zeros(len(energy_scale))
334
+ for line, info in lines['L-family'].items():
335
+ if line[0] == 'L':
336
+ model += get_peak(info['position'], energy_scale)*info['weight']
337
+ lines['L-family']['peaks'] = model/model.sum()
338
+ lines['L-family']['height'] /= lines['L-family']['peaks'].max()
339
+ p.append(lines['L-family']['height'])
340
+ peaks.append(lines['L-family']['peaks'])
341
+ keys.append(element+':L-family')
342
+ if 'M-family' in lines:
343
+ model = np.zeros(len(energy_scale))
344
+ for line, info in lines['M-family'].items():
345
+ if line[0] == 'M':
346
+ model += get_peak(info['position'], energy_scale)*info['weight']
347
+ lines['M-family']['peaks'] = model/model.sum()
348
+ lines['M-family']['height'] /= lines['M-family']['peaks'].max()
349
+ p.append(lines['M-family']['height'])
350
+ peaks.append(lines['M-family']['peaks'])
351
+ keys.append(element+':M-family')
352
+
353
+ if 'other' in lines:
354
+ for line, info in lines['other'].items():
355
+ info['peak'] = get_peak(info['position'], energy_scale)
356
+ peaks.append(info['peak'])
357
+ p.append(info['height'])
358
+ keys.append(element+':other:'+line)
359
+
360
+ #p.extend([300, 10, 1.e-04])
361
+ # p.extend([1, 300, -.02])
362
+ p.extend([1e7, 1e-3, 1500, 20])
363
+ return np.array(peaks), np.array(p), keys
364
+
365
+ def get_model(spectrum, start=100):
366
+
367
+ peaks, pp, keys = initial_model_parameter(spectrum)
368
+ model = np.zeros(len(spectrum))
369
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0].values
370
+ pp= spectrum.metadata['EDS']['parameters']
371
+ for i in range(len(pp) - 3):
372
+ model += peaks[i] * pp[i]
373
+ if 'detector_efficiency' in spectrum.metadata['experiment']['detector'].keys():
374
+ detector_efficiency = spectrum.metadata['experiment']['detector']['detector_efficiency']
375
+ else:
376
+ detector_efficiency = None
377
+ E_0 = spectrum.metadata['experiment']['acceleration_voltage_V']
378
+
379
+ if detector_efficiency is not None:
380
+ model[start:] += detector_efficiency[start:] * (pp[-3] + pp[-2] * (E_0 - energy_scale) / energy_scale +
381
+ pp[-1] * (E_0 - energy_scale) ** 2 / energy_scale)
382
+
383
+ return model
384
+
385
+ def fit_model(spectrum, elements, use_detector_efficiency=False):
386
+ out_tags = get_x_ray_lines(spectrum, elements)
387
+ peaks, pin, keys = initial_model_parameter(spectrum)
388
+ energy_scale = spectrum.get_spectral_dims(return_axis=True)[0].values
389
+
390
+ if 'detector' in spectrum.metadata['experiment'].keys():
391
+ if 'start_channel' not in spectrum.metadata['experiment']['detector']:
392
+ spectrum.metadata['experiment']['detector']['start_channel'] = np.searchsorted(energy_scale, 100)
393
+ if 'detector_efficiency' in spectrum.metadata['experiment']['detector'].keys():
394
+ if use_detector_efficiency:
395
+ detector_efficiency = spectrum.metadata['experiment']['detector']['detector_efficiency']
396
+ else:
397
+ use_detector_efficiency = False
398
+ else:
399
+ print('need detector information to fit spectrum')
400
+ return
401
+ start = spectrum.metadata['experiment']['detector']['start_channel']
402
+ energy_scale = energy_scale[start:]
403
+
404
+ E_0= spectrum.metadata['experiment']['acceleration_voltage_V']
405
+
406
+ def residuals(pp, yy):
407
+ #get_model(peaks, pp, detector_efficiency=None)
408
+ model = np.zeros(len(yy))
409
+ for i in range(len(pp)-4):
410
+ model += peaks[i]*pp[i]
411
+ # pp[-3:] = np.abs(pp[-3:])
412
+
413
+ if use_detector_efficiency:
414
+ bremsstrahlung = pp[-4] / (energy_scale + pp[-3] * energy_scale**2 + pp[-2] * energy_scale**.5) - pp[-1]
415
+
416
+ model[start:] += detector_efficiency[start:] * bremsstrahlung
417
+ #(pp[-3] + pp[-2] * (E_0 - energy_scale) / energy_scale +
418
+ # pp[-1] * (E_0-energy_scale) ** 2 / energy_scale))
419
+
420
+ err = np.abs((yy - model)[start:]) # /np.sqrt(np.abs(yy[start:])+1e-12)
421
+
422
+ return err
423
+
424
+ y = np.array(spectrum) # .compute()
425
+ [p, _] = leastsq(residuals, pin, args=(y))
426
+
427
+ # print(pin[-6:], p[-6:])
428
+
429
+ update_fit_values(out_tags, p)
430
+
431
+
432
+ if 'EDS' not in spectrum.metadata:
433
+ spectrum.metadata['EDS'] = {}
434
+ spectrum.metadata['EDS']['lines'] = out_tags
435
+
436
+ return np.array(peaks), np.array(p)
437
+
438
+
439
+ def update_fit_values(out_tags, p):
440
+ index = 0
441
+ for element, lines in out_tags.items():
442
+ if 'K-family' in lines:
443
+ lines['K-family']['height'] = p[index]
444
+ index += 1
445
+ if 'L-family' in lines:
446
+ lines['L-family']['height'] = p[index]
447
+ index += 1
448
+ if 'M-family' in lines:
449
+ lines['M-family']['height'] =p[index]
450
+ index += 1
451
+ if 'other' in lines:
452
+ for line, info in lines['other'].items():
453
+ info['height'] = p[index]
454
+ index += 1
455
+
456
+
457
+ def get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd):
458
+ background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [start_bgd, end_bgd], verbose=False)
459
+ cross_section_core = Xsection- background[0]
460
+ cross_section_core[cross_section_core < 0] = 0.0
461
+ cross_section_core[energy_scale < end_bgd] = 0.0
462
+ return cross_section_core
463
+
464
+
465
+ def get_eds_cross_sections(z):
466
+ energy_scale = np.arange(10, 20000)
467
+ Xsection = pyTEMlib.eels_tools.xsec_xrpa(energy_scale, 200, z, 400.)
468
+ edge_info = pyTEMlib.eels_tools.get_x_sections(z)
469
+ eds_cross_sections = {}
470
+ if 'K1' in edge_info:
471
+ start_bgd = edge_info['K1']['onset'] * 0.8
472
+ end_bgd = edge_info['K1']['onset'] - 5
473
+ if start_bgd > end_bgd:
474
+ start_bgd = end_bgd-100
475
+ if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
476
+ eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
477
+ eds_xsection = Xsection - eds_xsection
478
+ eds_xsection[eds_xsection<0] = 0.
479
+ start_sum = np.searchsorted(energy_scale, edge_info['K1']['onset'])
480
+ eds_cross_sections['K'] = eds_xsection[start_sum:start_sum+200].sum()
481
+ if 'L3' in edge_info:
482
+ start_bgd = edge_info['L3']['onset'] * 0.8
483
+ end_bgd = edge_info['L3']['onset'] - 5
484
+ if start_bgd > end_bgd:
485
+ start_bgd = end_bgd-100
486
+ if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
487
+ eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
488
+ eds_xsection = Xsection - eds_xsection
489
+ eds_xsection[eds_xsection<0] = 0.
490
+ start_sum = np.searchsorted(energy_scale, edge_info['L3']['onset'])
491
+ eds_cross_sections['L'] = eds_xsection[start_sum:start_sum+200].sum()
492
+ if 'M5' in edge_info:
493
+ start_bgd = edge_info['M5']['onset'] * 0.8
494
+ end_bgd = edge_info['M5']['onset'] - 5
495
+ if start_bgd > end_bgd:
496
+ start_bgd = end_bgd-100
497
+ if start_bgd > energy_scale[0] and end_bgd< energy_scale[-1]-100:
498
+ eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
499
+ eds_xsection = Xsection - eds_xsection
500
+ eds_xsection[eds_xsection<0] = 0.
501
+ start_sum = np.searchsorted(energy_scale, edge_info['M5']['onset'])
502
+ eds_cross_sections['M'] = eds_xsection[start_sum:start_sum+200].sum()
503
+ return eds_cross_sections
504
+
505
+
506
+ def get_phases(dataset, mode='kmeans', number_of_phases=4):
507
+ X_vec = np.array(dataset).reshape(dataset.shape[0]*dataset.shape[1], dataset.shape[2])
508
+ X_vec = np.divide(X_vec.T, X_vec.sum(axis=1)).T
509
+ if mode != 'kmeans':
510
+ gmm = GaussianMixture(n_components=number_of_phases, covariance_type="full") #choose number of components
511
+
512
+ gmm_results = gmm.fit(np.array(X_vec)) #we can intelligently fold the data and perform GM
513
+ gmm_labels = gmm_results.fit_predict(X_vec)
514
+
515
+ dataset.metadata['gaussian_mixing_model'] = {'map': gmm_labels.reshape(dataset.shape[0], dataset.shape[1]),
516
+ 'covariances': gmm.covariances_,
517
+ 'weights': gmm.weights_,
518
+ 'means': gmm_results.means_}
519
+ else:
520
+ km = KMeans(number_of_phases, n_init =10) #choose number of clusters
521
+ km_results = km.fit(np.array(X_vec)) #we can intelligently fold the data and perform Kmeans
522
+ dataset.metadata['kmeans'] = {'map': km_results.labels_.reshape(dataset.shape[0], dataset.shape[1]),
523
+ 'means': km_results.cluster_centers_}
524
+
525
+ def plot_phases(dataset, image=None, survey_image=None):
526
+ if survey_image is not None:
527
+ ncols = 3
528
+ else:
529
+ ncols = 2
530
+ axis_index = 0
531
+ fig, axes = plt.subplots(nrows=1, ncols=ncols, figsize = (10,3))
532
+ if survey_image is not None:
533
+ im = axes[0].imshow(survey_image.T)
534
+ axis_index += 1
535
+ #if 'gaussian_mixing_model' in dataset.metadata:
536
+ # phase_spectra = dataset.metadata['gaussian_mixing_model']['means']
537
+ # map = dataset.metadata['gaussian_mixing_model']['map']
538
+ #el
539
+ if 'kmeans' in dataset.metadata:
540
+ phase_spectra = dataset.metadata['kmeans']['means']
541
+ map = dataset.metadata['kmeans']['map']
542
+
543
+ cmap = plt.get_cmap('jet', len(phase_spectra))
544
+ im = axes[axis_index].imshow(image.T,cmap='gray')
545
+ im = axes[axis_index].imshow(map.T, cmap=cmap,vmin=np.min(map) - 0.5,
546
+ vmax=np.max(map) + 0.5,alpha=0.2)
547
+
548
+ cbar = fig.colorbar(im, ax=axes[axis_index])
549
+ cbar.ax.set_yticks(np.arange(0, len(phase_spectra) ))
550
+ cbar.ax.set_ylabel("GMM Phase", fontsize = 14)
551
+ axis_index += 1
552
+ for index, spectrum in enumerate(phase_spectra):
553
+ axes[axis_index].plot(dataset.energy/1000, spectrum, color = cmap(index), label=str(index))
554
+ axes[axis_index].set_xlabel('energy (keV)')
555
+ plt.legend()
556
+ plt.tight_layout()
557
+ plt.show()
558
+ return fig