pyTEMlib 0.2023.4.0__py2.py3-none-any.whl → 0.2023.8.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/peak_dialog.py CHANGED
@@ -7,13 +7,19 @@ try:
7
7
  from PyQt5 import QtCore, QtWidgets
8
8
  except:
9
9
  Qt_available = False
10
- print('Qt dialogs are not available')
10
+ # print('Qt dialogs are not available')
11
11
 
12
12
  import numpy as np
13
13
  import scipy
14
14
  import scipy.optimize
15
15
  import scipy.signal
16
16
 
17
+ import ipywidgets
18
+ from IPython.display import display
19
+ import matplotlib
20
+ import matplotlib.pylab as plt
21
+ import matplotlib.patches as patches
22
+
17
23
  import sidpy
18
24
  import pyTEMlib.file_tools as ft
19
25
  from pyTEMlib import eels_tools
@@ -28,6 +34,599 @@ except ModuleNotFoundError:
28
34
 
29
35
  _version = .001
30
36
 
37
+ def get_sidebar():
38
+ side_bar = ipywidgets.GridspecLayout(16, 3, width='auto', grid_gap="0px")
39
+ row = 0
40
+ side_bar[row, :3] = ipywidgets.Button(description='Fit Area',
41
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
42
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
43
+ row += 1
44
+ side_bar[row, :2] = ipywidgets.FloatText(value=7.5,description='Fit Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
45
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
46
+ row += 1
47
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Fit End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
48
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
49
+
50
+ row += 1
51
+ side_bar[row, :3] = ipywidgets.Button(description='Peak Finding',
52
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
53
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
54
+
55
+ row += 1
56
+
57
+
58
+ side_bar[row, :2] = ipywidgets.Dropdown(
59
+ options=[('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4)],
60
+ value=0,
61
+ description='Peaks:',
62
+ disabled=False,
63
+ layout=ipywidgets.Layout(width='200px'))
64
+
65
+ side_bar[row, 2] = ipywidgets.Button(
66
+ description='Smooth',
67
+ disabled=False,
68
+ button_style='', # 'success', 'info', 'warning', 'danger' or ''
69
+ tooltip='Do Gaussian Mixing',
70
+ layout=ipywidgets.Layout(width='100px'))
71
+
72
+ row += 1
73
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Number:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
74
+ side_bar[row, 2] = ipywidgets.Button(
75
+ description='Find',
76
+ disabled=False,
77
+ button_style='', # 'success', 'info', 'warning', 'danger' or ''
78
+ tooltip='Find first peaks from Gaussian mixture',
79
+ layout=ipywidgets.Layout(width='100px'))
80
+
81
+ row += 1
82
+
83
+ side_bar[row, :3] = ipywidgets.Button(description='Peaks',
84
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
85
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
86
+ row += 1
87
+ side_bar[row, :2] = ipywidgets.Dropdown(
88
+ options=[('Peak 1', 0), ('Add Peak', -1)],
89
+ value=0,
90
+ description='Peaks:',
91
+ disabled=False,
92
+ layout=ipywidgets.Layout(width='200px'))
93
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="", layout=ipywidgets.Layout(width='100px'))
94
+ row += 1
95
+ side_bar[row, :2] = ipywidgets.Dropdown(
96
+ options=[ 'Gauss', 'Lorentzian', 'Drude', 'Zero-Loss'],
97
+ value='Gauss',
98
+ description='Symmetry:',
99
+ disabled=False,
100
+ layout=ipywidgets.Layout(width='200px'))
101
+ row += 1
102
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Position:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
103
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
104
+ row += 1
105
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Amplitude:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
106
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
107
+ row += 1
108
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Width FWHM:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
109
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
110
+ row += 1
111
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Asymmetry:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
112
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="a.u.", layout=ipywidgets.Layout(width='100px'))
113
+ row += 1
114
+
115
+ side_bar[row, :3] = ipywidgets.Button(description='White-Line',
116
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
117
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
118
+
119
+ row += 1
120
+ side_bar[row, :2] = ipywidgets.Dropdown(
121
+ options=[('None', 0)],
122
+ value=0,
123
+ description='Ratio:',
124
+ disabled=False,
125
+ layout=ipywidgets.Layout(width='200px'))
126
+ side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
127
+ row += 1
128
+ side_bar[row, :2] = ipywidgets.Dropdown(
129
+ options=[('None', 0)],
130
+ value=0,
131
+ description= 'Sum:',
132
+ disabled=False,
133
+ layout=ipywidgets.Layout(width='200px'))
134
+ side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
135
+ return side_bar
136
+
137
+ class PeakFitWidget(object):
138
+ def __init__(self, datasets=None):
139
+ self.datasets = datasets
140
+ if not isinstance(datasets, dict):
141
+ raise TypeError('dataset or first item inhas to be a sidpy dataset')
142
+
143
+ self.sidebar = get_sidebar()
144
+ self.key = list(self.datasets)[0]
145
+ self.dataset = datasets[self.key]
146
+ if not isinstance(self.dataset, sidpy.Dataset):
147
+ raise TypeError('dataset or first item inhas to be a sidpy dataset')
148
+ self.spec_dim = ft.get_dimensions_by_type('spectral', self.dataset)
149
+ if len(self.spec_dim) != 1:
150
+ raise TypeError('We need exactly one SPECTRAL dimension')
151
+ self.spec_dim = self.spec_dim[0]
152
+ #self.energy_scale = self.dataset._axes[self.spec_dim]
153
+
154
+ self.energy_scale = self.spec_dim[1]
155
+
156
+
157
+ self.model = np.array([])
158
+ self.y_scale = 1.0
159
+ self.change_y_scale = 1.0
160
+ self.spectrum_ll = None
161
+ self.low_loss_key = None
162
+
163
+ self.peaks = {}
164
+
165
+ self.show_regions = False
166
+
167
+ with plt.ioff():
168
+ self.fig = plt.figure()
169
+ self.fig.canvas.toolbar_position = 'right'
170
+ self.fig.canvas.toolbar_visible = True
171
+ #self.set_dataset()
172
+ self.set_action()
173
+ self.y_scale = 1.0
174
+ self.change_y_scale = 1.0
175
+ self.plot(scale=False)
176
+
177
+ if 'peak_fit' not in self.dataset.metadata:
178
+ self.dataset.metadata['peak_fit'] = {}
179
+ if 'edges' in self.dataset.metadata:
180
+ if 'fit_area' in self.dataset.metadata['edges']:
181
+ self.dataset.metadata['peak_fit']['fit_start'] = \
182
+ self.dataset.metadata['edges']['fit_area']['fit_start']
183
+ self.dataset.metadata['peak_fit']['fit_end'] = self.dataset.metadata['edges']['fit_area']['fit_end']
184
+ self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
185
+ 'amplitude': 1000.0, 'width': 1.0,
186
+ 'type': 'Gauss', 'asymmetry': 0}}
187
+
188
+
189
+ self.peaks = self.dataset.metadata['peak_fit']
190
+ if 'fit_start' not in self.peaks:
191
+ self.peaks['fit_start'].value = self.energy_scale[1]
192
+ self.peaks['fit_end'].value = self.energy_scale[-2]
193
+
194
+ if 'peak_model' in self.peaks:
195
+ self.peak_model = self.peaks['peak_model']
196
+ self.model = self.peak_model
197
+ if 'edge_model' in self.peaks:
198
+ self.model = self.model + self.peaks['edge_model']
199
+ else:
200
+ self.model = np.array([])
201
+ self.peak_model = np.array([])
202
+ if 'peak_out_list' in self.peaks:
203
+ self.peak_out_list = self.peaks['peak_out_list']
204
+ self.set_peak_list()
205
+
206
+ # check whether a core loss analysis has been done previously
207
+ if not hasattr(self, 'core_loss') and 'edges' in self.dataset.metadata:
208
+ self.core_loss = True
209
+ else:
210
+ self.core_loss = False
211
+
212
+ self.update()
213
+
214
+ self.selector = matplotlib.widgets.SpanSelector(self.fig.gca(), self.line_select_callback,
215
+ direction="horizontal",
216
+ interactive=True,
217
+ props=dict(facecolor='blue', alpha=0.2))
218
+ self.start_cursor = ipywidgets.FloatText(value=0, description='Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
219
+ self.end_cursor = ipywidgets.FloatText(value=0, description='End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
220
+ self.panel = ipywidgets.VBox([ipywidgets.HBox([ipywidgets.Label('',layout=ipywidgets.Layout(width='100px')), ipywidgets.Label('Cursor:'),
221
+ self.start_cursor,ipywidgets.Label('eV'),
222
+ self.end_cursor, ipywidgets.Label('eV')]),
223
+ self.fig.canvas])
224
+
225
+ self.app_layout = ipywidgets.AppLayout(
226
+ left_sidebar=self.sidebar,
227
+ center=self.panel,
228
+ footer=None,#message_bar,
229
+ pane_heights=[0, 10, 0],
230
+ pane_widths=[4, 10, 0],
231
+ )
232
+ display(self.app_layout)
233
+
234
+ def line_select_callback(self, x_min, x_max):
235
+ self.start_cursor.value = np.round(x_min,3)
236
+ self.end_cursor.value = np.round(x_max, 3)
237
+ self.start_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.start_cursor.value)
238
+ self.end_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.end_cursor.value)
239
+
240
+
241
+ def set_peak_list(self):
242
+ self.peak_list = []
243
+ if 'peaks' not in self.peaks:
244
+ self.peaks['peaks'] = {}
245
+ key = 0
246
+ for key in self.peaks['peaks']:
247
+ if key.isdigit():
248
+ self.peak_list.append((f'Peak {int(key) + 1}', int(key)))
249
+ self.peak_list.append(('add peak', -1))
250
+ self.sidebar[7, 0].options = self.peak_list
251
+ self.sidebar[7, 0].value = 0
252
+
253
+
254
+ def plot(self, scale=True):
255
+
256
+ ylim = self.fig.gca().get_ylim()
257
+
258
+ ax = self.fig.gca()
259
+ ax.clear()
260
+ ax.plot(self.energy_scale, self.datasets[self.key]*self.y_scale, label=self.datasets[self.key].title)
261
+ ax.set_xlabel(self.datasets[self.key].labels[0])
262
+ ax.set_ylabel(self.datasets[self.key].data_descriptor)
263
+ ax.ticklabel_format(style='sci', scilimits=(-2, 3))
264
+ if scale:
265
+ ax.set_ylim(np.array(ylim)*self.change_y_scale)
266
+ self.change_y_scale = 1.0
267
+ if self.y_scale != 1.:
268
+ ax.set_ylabel('scattering probability (ppm/eV)')
269
+ self.selector = matplotlib.widgets.SpanSelector(self.fig.gca(), self.line_select_callback,
270
+ direction="horizontal",
271
+ interactive=True,
272
+ props=dict(facecolor='blue', alpha=0.2))
273
+
274
+ if len(self.model) > 1:
275
+ ax.plot(self.energy_scale, self.model*self.y_scale, label='model')
276
+ difference_spec = self.datasets[self.key] - self.model
277
+ ax.plot(self.energy_scale, difference_spec*self.y_scale, label='difference')
278
+ # axis.plot(self.energy_scale, (self.datasets[key] - self.model) / np.sqrt(self.datasets[key])*self.y_scale, label='Poisson')
279
+
280
+ if 'peaks' in self.peaks:
281
+ for index, peak in self.peaks['peaks'].items():
282
+ p = [peak['position'], peak['amplitude'], peak['width']]
283
+ ax.plot(self.energy_scale, eels_tools.gauss(self.energy_scale, p))
284
+
285
+ ax.legend()
286
+
287
+
288
+
289
+ def set_dataset(self, index=0):
290
+ if 'edges' not in self.dataset.metadata or self.dataset.metadata['edges'] == {}:
291
+ self.dataset.metadata['edges'] = {'0': {}, 'model': {}, 'use_low_loss': False}
292
+
293
+ self.edges = self.dataset.metadata['edges']
294
+ if '0' not in self.edges:
295
+ self.edges['0'] = {}
296
+
297
+ if 'fit_area' not in self.edges:
298
+ self.edges['fit_area'] = {}
299
+ if 'fit_start' not in self.edges['fit_area']:
300
+ self.sidebar[1,0].value = np.round(self.energy_scale[50], 3)
301
+ self.edges['fit_area']['fit_start'] = self.sidebar[1,0].value
302
+ else:
303
+ self.sidebar[1,0].value = np.round(self.edges['fit_area']['fit_start'],3)
304
+ if 'fit_end' not in self.edges['fit_area']:
305
+ self.sidebar[2,0].value = np.round(self.energy_scale[-2], 3)
306
+ self.edges['fit_area']['fit_end'] = self.sidebar[2,0].value
307
+ else:
308
+ self.sidebar[2,0].value = np.round(self.edges['fit_area']['fit_end'],3)
309
+
310
+ if self.dataset.data_type.name == 'SPECTRAL_IMAGE':
311
+ if 'SI_bin_x' not in self.dataset.metadata['experiment']:
312
+ self.dataset.metadata['experiment']['SI_bin_x'] = 1
313
+ self.dataset.metadata['experiment']['SI_bin_y'] = 1
314
+
315
+ bin_x = self.dataset.metadata['experiment']['SI_bin_x']
316
+ bin_y = self.dataset.metadata['experiment']['SI_bin_y']
317
+ # self.dataset.view.set_bin([bin_x, bin_y])
318
+ self.update()
319
+
320
+ def set_fit_area(self, value):
321
+ """
322
+ if self.sidebar[1,0].value > self.sidebar[2,0].value:
323
+ self.sidebar[1,0].value = self.sidebar[2,0].value - 1.0
324
+ if float(self.sidebar[1,0].value) < self.energy_scale[0]:
325
+ self.sidebar[1,0].value = self.energy_scale[0]
326
+ if self.sidebar[2,0].value > self.energy_scale[-1]:
327
+ self.sidebar[2,0].value = self.energy_scale[-1]
328
+ """
329
+ self.peaks['fit_start'] = self.sidebar[1, 0].value
330
+ self.peaks['fit_end'] = self.sidebar[2, 0].value
331
+
332
+ self.plot()
333
+
334
+ def set_y_scale(self, value):
335
+ self.change_y_scale = 1/self.y_scale
336
+ if self.sidebar[12, 0].value:
337
+ dispersion = self.energy_scale[1] - self.energy_scale[0]
338
+ self.y_scale = 1/self.dataset.metadata['experiment']['flux_ppm'] * dispersion
339
+ else:
340
+ self.y_scale = 1.0
341
+
342
+ self.change_y_scale *= self.y_scale
343
+ self.update()
344
+ self.plot()
345
+
346
+ def update(self, index=0):
347
+
348
+ # self.setWindowTitle('update')
349
+ self.sidebar[1, 0].value = self.peaks['fit_start']
350
+ self.sidebar[2, 0].value = self.peaks['fit_end']
351
+
352
+ peak_index = self.sidebar[7, 0].value
353
+ self.peak_index = self.sidebar[7, 0].value
354
+ if str(peak_index) not in self.peaks['peaks']:
355
+ self.peaks['peaks'][str(peak_index)] = {'position': self.energy_scale[1], 'amplitude': 1000.0,
356
+ 'width': 1.0, 'type': 'Gauss', 'asymmetry': 0}
357
+ self.sidebar[8, 0].value = self.peaks['peaks'][str(peak_index)]['type']
358
+ if 'associated_edge' in self.peaks['peaks'][str(peak_index)]:
359
+ self.sidebar[7, 2].value = (self.peaks['peaks'][str(peak_index)]['associated_edge'])
360
+ else:
361
+ self.sidebar[7, 2].value = ''
362
+ self.sidebar[9, 0].value = self.peaks['peaks'][str(peak_index)]['position']
363
+ self.sidebar[10, 0].value = self.peaks['peaks'][str(peak_index)]['amplitude']
364
+ self.sidebar[11, 0].value = self.peaks['peaks'][str(peak_index)]['width']
365
+ if 'asymmetry' not in self.peaks['peaks'][str(peak_index)]:
366
+ self.peaks['peaks'][str(peak_index)]['asymmetry'] = 0.
367
+ self.sidebar[12, 0].value = self.peaks['peaks'][str(peak_index)]['asymmetry']
368
+
369
+
370
+ def fit_peaks(self, value = 0):
371
+ """Fit spectrum with peaks given in peaks dictionary"""
372
+ print('Fitting peaks...')
373
+ p_in = []
374
+ for key, peak in self.peaks['peaks'].items():
375
+ if key.isdigit():
376
+ p_in.append(peak['position'])
377
+ p_in.append(peak['amplitude'])
378
+ p_in.append(peak['width'])
379
+
380
+ spectrum = np.array(self.dataset)
381
+
382
+ # set the energy scale and fit start and end points
383
+ energy_scale = np.array(self.energy_scale)
384
+ start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
385
+ end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
386
+
387
+ energy_scale = self.energy_scale[start_channel:end_channel]
388
+ # select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
389
+ if 'model' in self.dataset.metadata:
390
+ model = self.dataset.metadata['model'][start_channel:end_channel]
391
+ elif self.core_loss:
392
+ print('Core loss model found. Fitting on top of the model.')
393
+ model = self.dataset.metadata['edges']['model']['spectrum'][start_channel:end_channel]
394
+ else:
395
+ print('No core loss model found. Fitting to the full spectrum.')
396
+ model = np.zeros(end_channel - start_channel)
397
+
398
+ # if we have a core loss model we will only fit the difference between the model and the data.
399
+ difference = np.array(spectrum[start_channel:end_channel] - model)
400
+
401
+ # find the optimum fitting parameters
402
+ [self.p_out, _] = scipy.optimize.leastsq(eels_tools.residuals_smooth, np.array(p_in), ftol=1e-3,
403
+ args=(energy_scale, difference, False))
404
+
405
+ # construct the fit data from the optimized parameters
406
+ self.peak_model = np.zeros(len(self.energy_scale))
407
+ self.model = np.zeros(len(self.energy_scale))
408
+ self.model[start_channel:end_channel] = model
409
+ fit = eels_tools.model_smooth(energy_scale, self.p_out, False)
410
+ self.peak_model[start_channel:end_channel] = fit
411
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
412
+ self.model = self.model + self.peak_model
413
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
414
+
415
+ for key, peak in self.peaks['peaks'].items():
416
+ if key.isdigit():
417
+ p_index = int(key)*3
418
+ self.peaks['peaks'][key] = {'position': self.p_out[p_index],
419
+ 'amplitude': self.p_out[p_index+1],
420
+ 'width': self.p_out[p_index+2],
421
+ 'type': 'Gauss',
422
+ 'associated_edge': ''}
423
+
424
+ self.find_associated_edges()
425
+ self.find_white_lines()
426
+ self.update()
427
+ self.plot()
428
+
429
+ def find_associated_edges(self):
430
+ onsets = []
431
+ edges = []
432
+ if 'edges' in self.dataset.metadata:
433
+ for key, edge in self.dataset.metadata['edges'].items():
434
+ if key.isdigit():
435
+ element = edge['element']
436
+ for sym in edge['all_edges']: # TODO: Could be replaced with exclude
437
+ onsets.append(edge['all_edges'][sym]['onset'] + edge['chemical_shift'])
438
+ # if 'sym' == edge['symmetry']:
439
+ edges.append([key, f"{element}-{sym}", onsets[-1]])
440
+ for key, peak in self.peaks['peaks'].items():
441
+ if key.isdigit():
442
+ distance = self.energy_scale[-1]
443
+ index = -1
444
+ for ii, onset in enumerate(onsets):
445
+ if onset < peak['position'] < onset+50:
446
+ if distance > np.abs(peak['position'] - onset):
447
+ distance = np.abs(peak['position'] - onset) # TODO: check whether absolute is good
448
+ distance_onset = peak['position'] - onset
449
+ index = ii
450
+ if index >= 0:
451
+ peak['associated_edge'] = edges[index][1] # check if more info is necessary
452
+ peak['distance_to_onset'] = distance_onset
453
+
454
+ def find_white_lines(self):
455
+ if 'edges' in self.dataset.metadata:
456
+ white_lines = {}
457
+ for index, peak in self.peaks['peaks'].items():
458
+ if index.isdigit():
459
+ if 'associated_edge' in peak:
460
+ if peak['associated_edge'][-2:] in ['L3', 'L2', 'M5', 'M4']:
461
+ if peak['distance_to_onset'] < 10:
462
+ area = np.sqrt(2 * np.pi) * peak['amplitude'] * np.abs(peak['width']/np.sqrt(2 * np.log(2)))
463
+ if peak['associated_edge'] not in white_lines:
464
+ white_lines[peak['associated_edge']] = 0.
465
+ if area > 0:
466
+ white_lines[peak['associated_edge']] += area # TODO: only positive ones?
467
+ white_line_ratios = {}
468
+ white_line_sum = {}
469
+ for sym, area in white_lines.items():
470
+ if sym[-2:] in ['L2', 'M4', 'M2']:
471
+ if area > 0 and f"{sym[:-1]}{int(sym[-1]) + 1}" in white_lines:
472
+ if white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"] > 0:
473
+ white_line_ratios[f"{sym}/{sym[-2]}{int(sym[-1]) + 1}"] = area / white_lines[
474
+ f"{sym[:-1]}{int(sym[-1]) + 1}"]
475
+ white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] = (
476
+ area + white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"])
477
+
478
+ areal_density = 1.
479
+ if 'edges' in self.dataset.metadata:
480
+ for key, edge in self.dataset.metadata['edges'].items():
481
+ if key.isdigit():
482
+ if edge['element'] == sym.split('-')[0]:
483
+ areal_density = edge['areal_density']
484
+ break
485
+ white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] /= areal_density
486
+
487
+ self.peaks['white_lines'] = white_lines
488
+ self.peaks['white_line_ratios'] = white_line_ratios
489
+ self.peaks['white_line_sums'] = white_line_sum
490
+ self.wl_list = []
491
+ self.wls_list = []
492
+ if len(self.peaks['white_line_ratios']) > 0:
493
+ for key in self.peaks['white_line_ratios']:
494
+ self.wl_list.append(key)
495
+ for key in self.peaks['white_line_sums']:
496
+ self.wls_list.append(key)
497
+
498
+ self.sidebar[14, 0].options = self.wl_list
499
+ self.sidebar[14, 0].value = self.wl_list[0]
500
+ self.sidebar[14, 2].value = f"{self.peaks['white_line_ratios'][self.wl_list[0]]:.2f}"
501
+
502
+ self.sidebar[15, 0].options = self.wls_list
503
+ self.sidebar[15, 0].value = self.wls_list[0]
504
+ self.sidebar[15, 2].value = f"{self.peaks['white_line_sums'][self.wls_list[0]]*1e6:.4f} ppm"
505
+
506
+ else:
507
+ self.wl_list.append('Ratio')
508
+ self.wls_list.append('Sum')
509
+
510
+ self.sidebar[14, 0].options = ['None']
511
+ self.sidebar[14, 0].value = 'None'
512
+ self.sidebar[14, 2].value = ' '
513
+
514
+ self.sidebar[15, 0].options = ['None']
515
+ self.sidebar[15, 0].value = 'None'
516
+ self.sidebar[15, 2].value = ' '
517
+ def find_peaks(self, value=0):
518
+ number_of_peaks = int(self.sidebar[5, 0].value)
519
+
520
+ # is now sorted in smooth function
521
+ # flat_list = [item for sublist in self.peak_out_list for item in sublist]
522
+ # new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
523
+ # arg_list = np.argsort(np.abs(new_list[:, 1]))
524
+
525
+ self.peak_list = []
526
+ self.peaks['peaks'] = {}
527
+ for i in range(number_of_peaks):
528
+ self.peak_list.append((f'Peak {i+1}', i))
529
+ p = self.peak_out_list[i]
530
+ self.peaks['peaks'][str(i)] = {'position': p[0], 'amplitude': p[1], 'width': p[2], 'type': 'Gauss',
531
+ 'asymmetry': 0}
532
+
533
+ self.peak_list.append((f'add peak', -1))
534
+
535
+ self.sidebar[7, 0].options = self.peak_list
536
+ self.sidebar[7, 0].value = 0
537
+ self.find_associated_edges()
538
+ self.find_white_lines()
539
+
540
+ self.update()
541
+ self.plot()
542
+
543
+ def smooth(self, value=0):
544
+ """Fit lots of Gaussian to spectrum and let the program sort it out
545
+
546
+ We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
547
+
548
+ """
549
+ iterations = self.sidebar[4, 0].value
550
+ self.sidebar[5, 0].value = 0
551
+ advanced_present=False
552
+
553
+ self.peak_model, self.peak_out_list, number_of_peaks = smooth(self.dataset, iterations, advanced_present)
554
+
555
+ spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
556
+ if spec_dim[1][0] > 0:
557
+ self.model = self.dataset.metadata['edges']['model']['spectrum']
558
+ elif 'model' in self.dataset.metadata:
559
+ self.model = self.dataset.metadata['model']
560
+ else:
561
+ self.model = np.zeros(len(spec_dim[1]))
562
+
563
+ self.sidebar[5, 0].value = number_of_peaks
564
+
565
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
566
+ self.model = self.model + self.peak_model
567
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
568
+ self.dataset.metadata['peak_fit']['peak_out_list'] = self.peak_out_list
569
+
570
+ self.update()
571
+ self.plot()
572
+
573
+ def make_model(self):
574
+ p_peaks = []
575
+ for key, peak in self.peaks['peaks'].items():
576
+ if key.isdigit():
577
+ p_peaks.append(peak['position'])
578
+ p_peaks.append(peak['amplitude'])
579
+ p_peaks.append(peak['width'])
580
+
581
+
582
+ # set the energy scale and fit start and end points
583
+ energy_scale = np.array(self.energy_scale)
584
+ start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
585
+ end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
586
+ energy_scale = self.energy_scale[start_channel:end_channel]
587
+ # select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
588
+
589
+ fit = eels_tools.model_smooth(energy_scale, p_peaks, False)
590
+ self.peak_model[start_channel:end_channel] = fit
591
+ if 'edge_model' in self.dataset.metadata['peak_fit']:
592
+ self.model = self.dataset.metadata['peak_fit']['edge_model'] + self.peak_model
593
+ else:
594
+ self.model = np.zeros(self.dataset.shape)
595
+ def modify_peak_position(self, value=-1):
596
+ peak_index = self.sidebar[7, 0].value
597
+ self.peaks['peaks'][str(peak_index)]['position'] = self.sidebar[9,0].value
598
+ self.make_model()
599
+ self.plot()
600
+
601
+ def modify_peak_amplitude(self, value=-1):
602
+ peak_index = self.sidebar[7, 0].value
603
+ self.peaks['peaks'][str(peak_index)]['amplitude'] = self.sidebar[10,0].value
604
+ self.make_model()
605
+ self.plot()
606
+
607
+ def modify_peak_width(self, value=-1):
608
+ peak_index = self.sidebar[7, 0].value
609
+ self.peaks['peaks'][str(peak_index)]['width'] = self.sidebar[11,0].value
610
+ self.make_model()
611
+ self.plot()
612
+
613
+
614
+ def set_action(self):
615
+ self.sidebar[1, 0].observe(self.set_fit_area, names='value')
616
+ self.sidebar[2, 0].observe(self.set_fit_area, names='value')
617
+
618
+ self.sidebar[4, 2].on_click(self.smooth)
619
+ self.sidebar[7,0].observe(self.update)
620
+ self.sidebar[5,2].on_click(self.find_peaks)
621
+
622
+ self.sidebar[6, 0].on_click(self.fit_peaks)
623
+ self.sidebar[9, 0].observe(self.modify_peak_position, names='value')
624
+ self.sidebar[10, 0].observe(self.modify_peak_amplitude, names='value')
625
+ self.sidebar[11, 0].observe(self.modify_peak_width, names='value')
626
+
627
+
628
+
629
+
31
630
  if Qt_available:
32
631
  class PeakFitDialog(QtWidgets.QDialog):
33
632
  """
@@ -544,31 +1143,31 @@ if Qt_available:
544
1143
 
545
1144
 
546
1145
 
547
- def smooth(dataset, iterations, advanced_present):
548
- """Gaussian mixture model (non-Bayesian)
1146
+ def smooth(dataset, iterations, advanced_present):
1147
+ """Gaussian mixture model (non-Bayesian)
549
1148
 
550
- Fit lots of Gaussian to spectrum and let the program sort it out
551
- We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
1149
+ Fit lots of Gaussian to spectrum and let the program sort it out
1150
+ We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
552
1151
 
553
- """
1152
+ """
554
1153
 
555
- # TODO: add sensitivity to dialog and the two functions below
556
- peaks = dataset.metadata['peak_fit']
1154
+ # TODO: add sensitivity to dialog and the two functions below
1155
+ peaks = dataset.metadata['peak_fit']
557
1156
 
558
- if advanced_present and iterations > 1:
559
- peak_model, peak_out_list = advanced_eels_tools.smooth(dataset, peaks['fit_start'],
560
- peaks['fit_end'], iterations=iterations)
561
- else:
562
- peak_model, peak_out_list = eels_tools.find_peaks(dataset, peaks['fit_start'], peaks['fit_end'])
563
- peak_out_list = [peak_out_list]
1157
+ if advanced_present and iterations > 1:
1158
+ peak_model, peak_out_list = advanced_eels_tools.smooth(dataset, peaks['fit_start'],
1159
+ peaks['fit_end'], iterations=iterations)
1160
+ else:
1161
+ peak_model, peak_out_list = eels_tools.find_peaks(dataset, peaks['fit_start'], peaks['fit_end'])
1162
+ peak_out_list = [peak_out_list]
564
1163
 
565
- flat_list = [item for sublist in peak_out_list for item in sublist]
566
- new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
567
- area = np.sqrt(2 * np.pi) * np.abs(new_list[:, 1]) * np.abs(new_list[:, 2] / np.sqrt(2 * np.log(2)))
568
- arg_list = np.argsort(area)[::-1]
569
- area = area[arg_list]
570
- peak_out_list = new_list[arg_list]
1164
+ flat_list = [item for sublist in peak_out_list for item in sublist]
1165
+ new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
1166
+ area = np.sqrt(2 * np.pi) * np.abs(new_list[:, 1]) * np.abs(new_list[:, 2] / np.sqrt(2 * np.log(2)))
1167
+ arg_list = np.argsort(area)[::-1]
1168
+ area = area[arg_list]
1169
+ peak_out_list = new_list[arg_list]
571
1170
 
572
- number_of_peaks = np.searchsorted(area * -1, -np.average(area))
1171
+ number_of_peaks = np.searchsorted(area * -1, -np.average(area))
573
1172
 
574
- return peak_model, peak_out_list, number_of_peaks
1173
+ return peak_model, peak_out_list, number_of_peaks