pyTEMlib 0.2023.8.0__py2.py3-none-any.whl → 0.2024.2.0__py2.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.

pyTEMlib/eds_tools.py CHANGED
@@ -24,7 +24,8 @@ from scipy.interpolate import interp1d, splrep # splev, splint
24
24
  from scipy import interpolate
25
25
  from scipy.signal import peak_prominences
26
26
  from scipy.ndimage import gaussian_filter
27
-
27
+ from sklearn.mixture import GaussianMixture
28
+ from sklearn.cluster import KMeans
28
29
  import scipy.constants as const
29
30
 
30
31
  from scipy import constants
@@ -39,67 +40,519 @@ import requests
39
40
 
40
41
  from scipy.optimize import leastsq # least square fitting routine fo scipy
41
42
 
43
+ import sidpy
44
+
42
45
  import pickle # pkg_resources
43
- import pyTEMlib.eels_tools as eels
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
+
44
55
 
45
- shell_occupancy={'K1':2, 'L1':2, 'L2':2, 'L3':4, 'M1':2, 'M2':2, 'M3':4,'M4':4,'M5':6,
46
- 'N1':2, 'N2':2,' N3':4,'N4':4,'N5':6, 'N6':6,'N7':8,
47
- 'O1':2, 'O2':2,' O3':4,'O4':4,'O5':6, 'O6':6,'O7':8, 'O8':8, 'O9': 10 }
56
+ def detector_response(dataset):
57
+ tags = dataset.metadata['experiment']
48
58
 
49
- def detector_response(detector_definition, energy_scale):
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):
50
72
  """
51
- Parameters
73
+ Calculates response of Si drift detector for EDS spectrum background based on detector parameters
74
+
75
+ Parameters:
52
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!!
53
81
 
82
+ Return:
83
+ -------
84
+ response: numpy array with length(energy_scale)
85
+ detector response
54
86
 
55
87
  Example
56
88
  -------
57
89
 
58
- tags = {}
59
-
60
- tags['acceleration_voltage_V'] = 30000
90
+ tags ={}
91
+ tags['acceleration_voltage_V'] = 200000
61
92
 
62
93
  tags['detector'] ={}
63
- tags['detector']['layers'] ={}
64
94
 
65
95
  ## layer thicknesses of commen materials in EDS detectors in m
66
- tags['detector']['layers']['alLayer'] = {}
67
- tags['detector']['layers']['alLayer']['thickness'] = 30 *1e-9 # in m
68
- tags['detector']['layers']['alLayer']['Z'] = 13
69
-
70
- tags['detector']['layers']['deadLayer'] = {}
71
- tags['detector']['layers']['deadLayer']['thickness'] = 100 *1e-9 # in m
72
- tags['detector']['layers']['deadLayer']['Z'] = 14
73
-
74
- tags['detector']['layers']['window'] = {}
75
- tags['detector']['layers']['window']['thickness'] = 100 *1e-9 # in m
76
- tags['detector']['layers']['window']['Z'] = 6
77
-
78
- tags['detector']['detector'] = {}
79
- tags['detector']['detector']['thickness'] = 45 * 1e-3 # in m
80
- tags['detector']['detector']['Z'] = 14
81
- tags['detector']['detector']['area'] = 30 * 1e-6 #in m2
82
-
83
- energy_scale = np.linspace(.1,60,1199)*1000 i eV
84
- detector_response(tags, energy_scale)
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
+
85
123
  """
86
124
  response = np.ones(len(energy_scale))
87
- x_sections = eels.get_x_sections()
125
+ x_sections = pyTEMlib.eels_tools.get_x_sections()
88
126
 
89
- for key in detector_definition['layers']:
90
- Z = detector_definition['layers'][key]['Z']
91
- t = detector_definition['layers'][key]['thickness']
127
+ def get_absorption(Z, t):
92
128
  photoabsorption = x_sections[str(Z)]['dat']/1e10/x_sections[str(Z)]['photoabs_to_sigma']
93
- lin = interp1d(x_sections[str(Z)]['ene'], photoabsorption,kind='linear')
94
- mu = lin(energy_scale) * x_sections[str(Z)]['nominal_density']*100. #1/cm -> 1/m
95
-
96
- absorption = np.exp(-mu * t)
97
- response = response*absorption
98
- Z = detector_definition['detector']['Z']
99
- t = detector_definition['detector']['thickness']
100
- photoabsorption = x_sections[str(Z)]['dat']/1e10/x_sections[str(Z)]['photoabs_to_sigma']
101
- lin = interp1d(x_sections[str(Z)]['ene']/1000., photoabsorption,kind='linear')
102
- mu = lin(energy_scale) * x_sections[str(Z)]['nominal_density']*100. #1/cm -> 1/m
103
- response = response*(1.0 - np.exp(-mu * t))# * oo4pi;
104
- return(response)
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
+
105
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