pyTEMlib 0.2020.11.0__py3-none-any.whl → 0.2024.8.4__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.

Files changed (59) hide show
  1. pyTEMlib/__init__.py +11 -11
  2. pyTEMlib/animation.py +631 -0
  3. pyTEMlib/atom_tools.py +240 -222
  4. pyTEMlib/config_dir.py +57 -29
  5. pyTEMlib/core_loss_widget.py +658 -0
  6. pyTEMlib/crystal_tools.py +1255 -0
  7. pyTEMlib/diffraction_plot.py +756 -0
  8. pyTEMlib/dynamic_scattering.py +293 -0
  9. pyTEMlib/eds_tools.py +609 -0
  10. pyTEMlib/eels_dialog.py +749 -486
  11. pyTEMlib/{interactive_eels.py → eels_dialog_utilities.py} +1199 -1524
  12. pyTEMlib/eels_tools.py +2031 -1731
  13. pyTEMlib/file_tools.py +1276 -491
  14. pyTEMlib/file_tools_qt.py +193 -0
  15. pyTEMlib/graph_tools.py +1166 -450
  16. pyTEMlib/graph_viz.py +449 -0
  17. pyTEMlib/image_dialog.py +158 -0
  18. pyTEMlib/image_dlg.py +146 -0
  19. pyTEMlib/image_tools.py +1399 -956
  20. pyTEMlib/info_widget.py +933 -0
  21. pyTEMlib/interactive_image.py +1 -0
  22. pyTEMlib/kinematic_scattering.py +1196 -0
  23. pyTEMlib/low_loss_widget.py +176 -0
  24. pyTEMlib/microscope.py +61 -78
  25. pyTEMlib/peak_dialog.py +1047 -350
  26. pyTEMlib/peak_dlg.py +286 -248
  27. pyTEMlib/probe_tools.py +653 -202
  28. pyTEMlib/sidpy_tools.py +153 -129
  29. pyTEMlib/simulation_tools.py +104 -87
  30. pyTEMlib/version.py +6 -3
  31. pyTEMlib/xrpa_x_sections.py +20972 -0
  32. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/LICENSE +21 -21
  33. pyTEMlib-0.2024.8.4.dist-info/METADATA +93 -0
  34. pyTEMlib-0.2024.8.4.dist-info/RECORD +37 -0
  35. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/WHEEL +6 -5
  36. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/entry_points.txt +0 -1
  37. pyTEMlib/KinsCat.py +0 -2685
  38. pyTEMlib/__version__.py +0 -2
  39. pyTEMlib/data/TEMlibrc +0 -68
  40. pyTEMlib/data/edges_db.csv +0 -189
  41. pyTEMlib/data/edges_db.pkl +0 -0
  42. pyTEMlib/data/fparam.txt +0 -103
  43. pyTEMlib/data/microscopes.csv +0 -7
  44. pyTEMlib/data/microscopes.xml +0 -167
  45. pyTEMlib/data/path.txt +0 -1
  46. pyTEMlib/defaults_parser.py +0 -86
  47. pyTEMlib/dm3_reader.py +0 -609
  48. pyTEMlib/edges_db.py +0 -76
  49. pyTEMlib/eels_dlg.py +0 -240
  50. pyTEMlib/hdf_utils.py +0 -481
  51. pyTEMlib/image_tools1.py +0 -2194
  52. pyTEMlib/info_dialog.py +0 -227
  53. pyTEMlib/info_dlg.py +0 -205
  54. pyTEMlib/nion_reader.py +0 -293
  55. pyTEMlib/nsi_reader.py +0 -165
  56. pyTEMlib/structure_tools.py +0 -316
  57. pyTEMlib-0.2020.11.0.dist-info/METADATA +0 -20
  58. pyTEMlib-0.2020.11.0.dist-info/RECORD +0 -42
  59. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/top_level.txt +0 -0
pyTEMlib/peak_dialog.py CHANGED
@@ -1,350 +1,1047 @@
1
- from PyQt5 import QtCore, QtWidgets
2
-
3
- import numpy as np
4
- import scipy
5
- import scipy.optimize
6
- import scipy.signal
7
-
8
- import sidpy
9
- import pyTEMlib.file_tools as ft
10
- import pyTEMlib.eels_tools as eels
11
- import pyTEMlib.peak_dlg as peak_dlg
12
-
13
- advanced_present = True
14
- try:
15
- import advanced_eels_tools
16
- print('advanced EELS features enabled')
17
- except ModuleNotFoundError:
18
- advanced_present = False
19
-
20
- _version = .001
21
-
22
-
23
- class PeakFitDialog(QtWidgets.QDialog):
24
- """
25
- EELS Input Dialog for Chemical Analysis
26
- """
27
-
28
- def __init__(self, dataset=None):
29
- super().__init__(None, QtCore.Qt.WindowStaysOnTopHint)
30
- # Create an instance of the GUI
31
- self.ui = peak_dlg.UiDialog(self)
32
-
33
- self.set_action()
34
-
35
- self.dataset = dataset
36
- self.energy_scale = np.array([])
37
- self.model = np.array([])
38
- self.peak_model = np.array([])
39
- self.peak_out_list = []
40
- self.p_out = []
41
- self.edges = {}
42
- self.axis = None
43
- self.show_regions = False
44
- self.show()
45
-
46
- if dataset is None:
47
- # make a dummy dataset
48
- dataset = ft.make_dummy_dataset('spectrum')
49
-
50
- if not isinstance(dataset, sidpy.Dataset):
51
- raise TypeError('dataset has to be a sidpy dataset')
52
- self.dataset = dataset
53
- self.spec_dim = ft.get_dimensions_by_type('spectral', dataset)
54
- if len(self.spec_dim) != 1:
55
- raise TypeError('We need exactly one SPECTRAL dimension')
56
- self.spec_dim = self.spec_dim[0]
57
- self.energy_scale = self.spec_dim[1].values.copy()
58
-
59
- if 'peak_fit' not in self.dataset.metadata:
60
- self.dataset.metadata['peak_fit'] = {}
61
- if 'edges' in self.dataset.metadata:
62
- if 'fit_area' in self.dataset.metadata['edges']:
63
- self.dataset.metadata['peak_fit']['fit_start'] = \
64
- self.dataset.metadata['edges']['fit_area']['fit_start']
65
- self.dataset.metadata['peak_fit']['fit_end'] = self.dataset.metadata['edges']['fit_area']['fit_end']
66
-
67
- self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
68
- 'amplitude': 1000.0,
69
- 'width': 1.0,
70
- 'type': 'Gauss', 'asymmetry': 0}}
71
-
72
- self.peaks = self.dataset.metadata['peak_fit']
73
- if 'fit_start' not in self.peaks:
74
- self.peaks['fit_start'] = self.energy_scale[1]
75
- self.peaks['fit_end'] = self.energy_scale[-2]
76
-
77
- self.update()
78
- self.dataset.plot()
79
- if hasattr(self.dataset.view, 'axes'):
80
- self.axis = self.dataset.view.axes[-1]
81
- elif hasattr(self.dataset.view, 'axis'):
82
- self.axis = self.dataset.view.axis
83
- self.figure = self.axis.figure
84
-
85
- if not advanced_present:
86
- self.ui.iteration_list = ['0']
87
- self.ui.smooth_list.clear()
88
- self.ui.smooth_list.addItems(self.ui.iteration_list)
89
- self.ui.smooth_list.setCurrentIndex(0)
90
- self.plot()
91
-
92
- def update(self):
93
- # self.setWindowTitle('update')
94
- self.ui.edit1.setText(f"{self.peaks['fit_start']:.2f}")
95
- self.ui.edit2.setText(f"{self.peaks['fit_end']:.2f}")
96
-
97
- peak_index = self.ui.list3.currentIndex()
98
- if str(peak_index) not in self.peaks['peaks']:
99
- self.peaks['peaks'][str(peak_index)] = {'position': self.energy_scale[1], 'amplitude': 1000.0,
100
- 'width': 1.0, 'type': 'Gauss', 'asymmetry': 0}
101
- self.ui.list4.setCurrentText(self.peaks['peaks'][str(peak_index)]['type'])
102
- if 'associated_edge' in self.peaks['peaks'][str(peak_index)]:
103
- self.ui.unit3.setText(self.peaks['peaks'][str(peak_index)]['associated_edge'])
104
- else:
105
- self.ui.unit3.setText('')
106
- self.ui.edit5.setText(f"{self.peaks['peaks'][str(peak_index)]['position']:.2f}")
107
- self.ui.edit6.setText(f"{self.peaks['peaks'][str(peak_index)]['amplitude']:.2f}")
108
- self.ui.edit7.setText(f"{self.peaks['peaks'][str(peak_index)]['width']:.2f}")
109
- if 'asymmetry' not in self.peaks['peaks'][str(peak_index)]:
110
- self.peaks['peaks'][str(peak_index)]['asymmetry'] = 0.
111
- self.ui.edit8.setText(f"{self.peaks['peaks'][str(peak_index)]['asymmetry']:.2f}")
112
-
113
- def plot(self):
114
- spec_dim = ft.get_dimensions_by_type(sidpy.DimensionTypes.SPECTRAL, self.dataset)
115
- spec_dim = spec_dim[0]
116
- self.energy_scale = spec_dim[1].values
117
- if self.dataset.data_type == sidpy.DataTypes.SPECTRAL_IMAGE:
118
- spectrum = self.dataset.view.get_spectrum()
119
- self.axis = self.dataset.view.axes[1]
120
- else:
121
- spectrum = np.array(self.dataset)
122
- self.axis = self.dataset.view.axis
123
-
124
- x_limit = self.axis.get_xlim()
125
- y_limit = self.axis.get_ylim()
126
- self.axis.clear()
127
-
128
- self.axis.plot(self.energy_scale, spectrum, label='spectrum')
129
- if len(self.model) > 1:
130
- self.axis.plot(self.energy_scale, self.model, label='model')
131
- self.axis.plot(self.energy_scale, spectrum - self.model, label='difference')
132
- self.axis.plot(self.energy_scale, (spectrum - self.model) / np.sqrt(spectrum), label='Poisson')
133
- self.axis.legend()
134
- self.axis.set_xlim(x_limit)
135
- self.axis.set_ylim(y_limit)
136
- self.axis.figure.canvas.draw_idle()
137
-
138
- for index, peak in self.peaks['peaks'].items():
139
- p = [peak['position'], peak['amplitude'], peak['width']]
140
- self.axis.plot(self.energy_scale, eels.gauss(self.energy_scale, p))
141
-
142
- def fit_peaks(self):
143
- p_in = []
144
- for key, peak in self.peaks['peaks'].items():
145
- if key.isdigit():
146
- p_in.append(peak['position'])
147
- p_in.append(peak['amplitude'])
148
- p_in.append(peak['width'])
149
- if self.dataset.data_type == sidpy.DataTypes.SPECTRAL_IMAGE:
150
- spectrum = self.dataset.view.get_spectrum()
151
- else:
152
- spectrum = np.array(self.dataset)
153
- energy_scale = np.array(self.energy_scale)
154
- start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
155
- end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
156
-
157
- energy_scale = self.dataset.energy_scale[start_channel:end_channel]
158
- if self.dataset.energy_scale[0] > 0:
159
- if 'edges' not in self.dataset.metadata:
160
- return
161
- if 'model' not in self.dataset.metadata['edges']:
162
- return
163
- model = self.dataset.metadata['edges']['model']['spectrum'][start_channel:end_channel]
164
-
165
- else:
166
- model = np.zeros(end_channel - start_channel)
167
-
168
- difference = np.array(spectrum - model)
169
-
170
- [self.p_out, _] = scipy.optimize.leastsq(eels.residuals_smooth, np.array(p_in), ftol=1e-3,
171
- args=(energy_scale, difference, False))
172
- self.peak_model = np.zeros(len(self.dataset.energy_scale))
173
- fit = eels.model_smooth(energy_scale, self.p_out, False)
174
- self.peak_model[start_channel:end_channel] = fit
175
- if self.dataset.energy_scale[0] > 0:
176
- self.model = self.dataset.metadata['edges']['model']['spectrum']
177
- else:
178
- self.model = np.zeros(len(self.dataset.energy_scale))
179
- self.model = self.model + self.peak_model
180
-
181
- self.plot()
182
-
183
- def smooth(self):
184
- iterations = int(self.ui.smooth_list.currentIndex())
185
-
186
- # TODO: add sensitivity to dialog and the two funcitons below
187
- if advanced_present:
188
- self.peak_model, self.peak_out_list = advanced_eels_tools.smooth(self.dataset,
189
- self.peaks['fit_start'],
190
- self.peaks['fit_end'],
191
- iterations=iterations)
192
- else:
193
- self.peak_model, self.peak_out_list = eels.find_peaks(self.dataset, self.peaks['fit_start'],
194
- self.peaks['fit_end'])
195
-
196
- spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
197
- if spec_dim[1][0] > 0:
198
- self.model = self.dataset.metadata['edges']['model']['spectrum']
199
- else:
200
- self.model = np.zeros(len(spec_dim[1]))
201
- self.model = self.model+self.peak_model
202
-
203
- self.plot()
204
-
205
- def find_associated_edges(self):
206
- onsets = []
207
- edges = []
208
- if 'edges' in self.dataset.metadata:
209
- for key, edge in self.dataset.metadata['edges'].items():
210
- if key.isdigit():
211
- element = edge['element']
212
- for sym in edge['all_edges']: # TODO: Could be replaced with exclude
213
- onsets.append(edge['all_edges'][sym]['onset'] + edge['chemical_shift'])
214
- # if 'sym' == edge['symmetry']:
215
- edges.append([key, f"{element}-{sym}", onsets[-1]])
216
- for key, peak in self.peaks['peaks'].items():
217
- if key.isdigit():
218
- distance = self.energy_scale[-1]
219
- index = -1
220
- for ii, onset in enumerate(onsets):
221
- if onset-5 < peak['position'] < onset+50:
222
- if distance > np.abs(peak['position'] - onset):
223
- distance = np.abs(peak['position'] - onset) # TODO: check whether absolute is good
224
- index = ii
225
- if index > 0:
226
- peak['associated_edge'] = edges[index][1] # check if more info is necessary
227
-
228
- def find_white_lines(self):
229
- white_lines = {}
230
- for index, peak in self.peaks['peaks'].items():
231
- if index.isdigit():
232
- if 'associated_edge' in peak:
233
- if peak['associated_edge'][-2:] in ['L3', 'L2', 'M5', 'M4']:
234
- area = np.sqrt(2 * np.pi) * peak['amplitude'] * np.abs(peak['width'] / np.sqrt(2 * np.log(2)))
235
- if peak['associated_edge'] not in white_lines:
236
- white_lines[peak['associated_edge']] = 0.
237
- white_lines[peak['associated_edge']] += area # TODO: only positive ones?
238
- white_line_ratios = {}
239
- white_line_sum = {}
240
- for sym, area in white_lines.items():
241
- if sym[-2:] in ['L2', 'M4', 'M2']:
242
- if area > 0 and f"{sym[:-1]}{int(sym[-1]) + 1}" in white_lines:
243
- if white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"] > 0:
244
- white_line_ratios[f"{sym}/{sym[-2]}{int(sym[-1]) + 1}"] = area / white_lines[
245
- f"{sym[:-1]}{int(sym[-1]) + 1}"]
246
- white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] = (
247
- area + white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"])
248
-
249
- areal_density = 1.
250
- if 'edges' in self.dataset.metadata:
251
- for key, edge in self.dataset.metadata['edges'].items():
252
- if key.isdigit():
253
- if edge['element'] == sym.split('-')[0]:
254
- areal_density = edge['areal_density']
255
- break
256
- white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] /= areal_density
257
-
258
- self.peaks['white_lines'] = white_lines
259
- self.peaks['white_line_ratios'] = white_line_ratios
260
- self.peaks['white_line_sums'] = white_line_sum
261
- self.ui.wl_list = []
262
- self.ui.wls_list = []
263
- if len(self.peaks['white_line_ratios']) > 0:
264
- for key in self.peaks['white_line_ratios']:
265
- self.ui.wl_list.append(key)
266
- for key in self.peaks['white_line_sums']:
267
- self.ui.wls_list.append(key)
268
-
269
- self.ui.listwl.clear()
270
- self.ui.listwl.addItems(self.ui.wl_list)
271
- self.ui.listwl.setCurrentIndex(0)
272
- self.ui.unitswl.setText(f"{self.peaks['white_line_ratios'][self.ui.wl_list[0]]:.2f}")
273
-
274
- self.ui.listwls.clear()
275
- self.ui.listwls.addItems(self.ui.wls_list)
276
- self.ui.listwls.setCurrentIndex(0)
277
- self.ui.unitswls.setText(f"{self.peaks['white_line_sums'][self.ui.wls_list[0]]*1e6:.4f} ppm")
278
- else:
279
- self.ui.wl_list.append('Ratio')
280
- self.ui.wls_list.append('Sum')
281
-
282
- self.ui.listwl.clear()
283
- self.ui.listwl.addItems(self.ui.wl_list)
284
- self.ui.listwl.setCurrentIndex(0)
285
- self.ui.unitswl.setText('')
286
-
287
- self.ui.listwls.clear()
288
- self.ui.listwls.addItems(self.ui.wls_list)
289
- self.ui.listwls.setCurrentIndex(0)
290
- self.ui.unitswls.setText('')
291
-
292
- def find_peaks(self):
293
- number_of_peaks = int(str(self.ui.find_edit.displayText()).strip())
294
-
295
- flat_list = [item for sublist in self.peak_out_list for item in sublist]
296
- new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
297
- arg_list = np.argsort(np.abs(new_list[:, 1]))
298
-
299
- self.ui.peak_list = []
300
- self.peaks['peaks'] = {}
301
- for i in range(number_of_peaks):
302
- self.ui.peak_list.append(f'Peak {i+1}')
303
- p = new_list[arg_list[-i-1]]
304
- self.peaks['peaks'][str(i)] = {'position': p[0], 'amplitude': p[1], 'width': p[2], 'type': 'Gauss',
305
- 'asymmetry': 0}
306
-
307
- self.ui.peak_list.append(f'add peak')
308
- self.ui.list3.clear()
309
- self.ui.list3.addItems(self.ui.peak_list)
310
- self.ui.list3.setCurrentIndex(0)
311
- self.find_associated_edges()
312
- self.find_white_lines()
313
-
314
- self.update()
315
- self.plot()
316
-
317
- def on_enter(self):
318
- if self.sender() == self.ui.edit1:
319
- value = float(str(self.ui.edit1.displayText()).strip())
320
- if value < self.energy_scale[0]:
321
- value = self.energy_scale[0]
322
- if value > self.energy_scale[-5]:
323
- value = self.energy_scale[-5]
324
- self.peaks['fit_start'] = value
325
- self.ui.edit1.setText(str(self.peaks['fit_start']))
326
- elif self.sender() == self.ui.edit2:
327
- value = float(str(self.ui.edit2.displayText()).strip())
328
- if value < self.energy_scale[5]:
329
- value = self.energy_scale[5]
330
- if value > self.energy_scale[-1]:
331
- value = self.energy_scale[-1]
332
- self.peaks['fit_end'] = value
333
- self.ui.edit2.setText(str(self.peaks['fit_end']))
334
-
335
- def on_list_enter(self):
336
- # self.setWindowTitle('list')
337
- self.update()
338
-
339
- def set_action(self):
340
- pass
341
- self.ui.edit1.editingFinished.connect(self.on_enter)
342
- self.ui.edit2.editingFinished.connect(self.on_enter)
343
- self.ui.edit5.editingFinished.connect(self.on_enter)
344
- self.ui.edit6.editingFinished.connect(self.on_enter)
345
- self.ui.edit7.editingFinished.connect(self.on_enter)
346
- self.ui.edit8.editingFinished.connect(self.on_enter)
347
- self.ui.list3.activated[str].connect(self.on_list_enter)
348
- self.ui.find_button.clicked.connect(self.find_peaks)
349
- self.ui.smooth_button.clicked.connect(self.smooth)
350
- self.ui.fit_button.clicked.connect(self.fit_peaks)
1
+ """
2
+ EELS Input Dialog for ELNES Analysis
3
+ """
4
+ from os import error
5
+ Qt_available = True
6
+ try:
7
+ from PyQt5 import QtCore, QtWidgets
8
+ except:
9
+ Qt_available = False
10
+ # print('Qt dialogs are not available')
11
+
12
+ import numpy as np
13
+ import scipy
14
+ import scipy.optimize
15
+ import scipy.signal
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
+
23
+ import sidpy
24
+ import pyTEMlib.file_tools as ft
25
+ from pyTEMlib import eels_tools
26
+ from pyTEMlib import peak_dlg
27
+ from pyTEMlib import eels_dialog_utilities
28
+
29
+ advanced_present = True
30
+ try:
31
+ import advanced_eels_tools
32
+ print('advanced EELS features enabled')
33
+ except ModuleNotFoundError:
34
+ advanced_present = False
35
+
36
+ _version = .001
37
+
38
+ def get_sidebar():
39
+ side_bar = ipywidgets.GridspecLayout(16, 3, width='auto', grid_gap="0px")
40
+ row = 0
41
+ side_bar[row, :3] = ipywidgets.Button(description='Fit Area',
42
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
43
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
44
+ row += 1
45
+ side_bar[row, :2] = ipywidgets.FloatText(value=7.5,description='Fit Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
46
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
47
+ row += 1
48
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Fit End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
49
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
50
+
51
+ row += 1
52
+ side_bar[row, :3] = ipywidgets.Button(description='Peak Finding',
53
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
54
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
55
+
56
+ row += 1
57
+
58
+
59
+ side_bar[row, :2] = ipywidgets.Dropdown(
60
+ options=[('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4)],
61
+ value=0,
62
+ description='Peaks:',
63
+ disabled=False,
64
+ layout=ipywidgets.Layout(width='200px'))
65
+
66
+ side_bar[row, 2] = ipywidgets.Button(
67
+ description='Smooth',
68
+ disabled=False,
69
+ button_style='', # 'success', 'info', 'warning', 'danger' or ''
70
+ tooltip='Do Gaussian Mixing',
71
+ layout=ipywidgets.Layout(width='100px'))
72
+
73
+ row += 1
74
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Number:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
75
+ side_bar[row, 2] = ipywidgets.Button(
76
+ description='Find',
77
+ disabled=False,
78
+ button_style='', # 'success', 'info', 'warning', 'danger' or ''
79
+ tooltip='Find first peaks from Gaussian mixture',
80
+ layout=ipywidgets.Layout(width='100px'))
81
+
82
+ row += 1
83
+
84
+ side_bar[row, :3] = ipywidgets.Button(description='Peaks',
85
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
86
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
87
+ row += 1
88
+ side_bar[row, :2] = ipywidgets.Dropdown(
89
+ options=[('Peak 1', 0), ('add peak', -1)],
90
+ value=0,
91
+ description='Peaks:',
92
+ disabled=False,
93
+ layout=ipywidgets.Layout(width='200px'))
94
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="", layout=ipywidgets.Layout(width='100px'))
95
+ row += 1
96
+ side_bar[row, :2] = ipywidgets.Dropdown(
97
+ options=[ 'Gauss', 'Lorentzian', 'Drude', 'Zero-Loss'],
98
+ value='Gauss',
99
+ description='Symmetry:',
100
+ disabled=False,
101
+ layout=ipywidgets.Layout(width='200px'))
102
+ row += 1
103
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Position:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
104
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
105
+ row += 1
106
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Amplitude:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
107
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
108
+ row += 1
109
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Width FWHM:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
110
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
111
+ row += 1
112
+ side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Asymmetry:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
113
+ side_bar[row, 2] = ipywidgets.widgets.Label(value="a.u.", layout=ipywidgets.Layout(width='100px'))
114
+ row += 1
115
+
116
+ side_bar[row, :3] = ipywidgets.Button(description='White-Line',
117
+ layout=ipywidgets.Layout(width='auto', grid_area='header'),
118
+ style=ipywidgets.ButtonStyle(button_color='lightblue'))
119
+
120
+ row += 1
121
+ side_bar[row, :2] = ipywidgets.Dropdown(
122
+ options=[('None', 0)],
123
+ value=0,
124
+ description='Ratio:',
125
+ disabled=False,
126
+ layout=ipywidgets.Layout(width='200px'))
127
+ side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
128
+ row += 1
129
+ side_bar[row, :2] = ipywidgets.Dropdown(
130
+ options=[('None', 0)],
131
+ value=0,
132
+ description= 'Sum:',
133
+ disabled=False,
134
+ layout=ipywidgets.Layout(width='200px'))
135
+ side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
136
+ return side_bar
137
+
138
+ class PeakFitWidget(object):
139
+ def __init__(self, datasets=None):
140
+ self.datasets = datasets
141
+ if not isinstance(datasets, dict):
142
+ raise TypeError('need dictioary of sidpy datasets')
143
+
144
+ self.sidebar = get_sidebar()
145
+ self.key = list(self.datasets)[0]
146
+ self.dataset = datasets[self.key]
147
+ if not isinstance(self.dataset, sidpy.Dataset):
148
+ raise TypeError('dataset or first item inhas to be a sidpy dataset')
149
+
150
+ self.model = np.array([])
151
+ self.y_scale = 1.0
152
+ self.change_y_scale = 1.0
153
+ self.spectrum_ll = None
154
+ self.low_loss_key = None
155
+
156
+ self.peaks = {}
157
+
158
+ self.show_regions = False
159
+
160
+ self.set_dataset()
161
+
162
+ self.app_layout = ipywidgets.AppLayout(
163
+ left_sidebar=self.sidebar,
164
+ center=self.view.panel,
165
+ footer=None,#message_bar,
166
+ pane_heights=[0, 10, 0],
167
+ pane_widths=[4, 10, 0],
168
+ )
169
+ display(self.app_layout)
170
+ self.set_action()
171
+
172
+ def line_select_callback(self, x_min, x_max):
173
+ self.start_cursor.value = np.round(x_min,3)
174
+ self.end_cursor.value = np.round(x_max, 3)
175
+ self.start_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.start_cursor.value)
176
+ self.end_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.end_cursor.value)
177
+
178
+
179
+ def set_peak_list(self):
180
+ self.peak_list = []
181
+ if 'peaks' not in self.peaks:
182
+ self.peaks['peaks'] = {}
183
+ key = 0
184
+ for key in self.peaks['peaks']:
185
+ if key.isdigit():
186
+ self.peak_list.append((f'Peak {int(key) + 1}', int(key)))
187
+ self.peak_list.append(('add peak', -1))
188
+ #self.sidebar[7, 0].options = self.peak_list
189
+ #self.sidebar[7, 0].value = 0
190
+
191
+
192
+ def plot(self, scale=True):
193
+
194
+ self.view.change_y_scale = self.change_y_scale
195
+ self.view.y_scale = self.y_scale
196
+ self.energy_scale = self.dataset.energy_loss.values
197
+
198
+ if self.dataset.data_type == sidpy.DataType.SPECTRAL_IMAGE:
199
+ spectrum = self.dataset.view.get_spectrum()
200
+ else:
201
+ spectrum = self.dataset
202
+ if len(self.model) > 1:
203
+ additional_spectra = {'model': self.model,
204
+ 'difference': spectrum-self.model}
205
+ else:
206
+ additional_spectra = {}
207
+ if 'peaks' in self.peaks:
208
+ if len(self.peaks)>0:
209
+ for index, peak in self.peaks['peaks'].items(): # ll
210
+ p = [peak['position'], peak['amplitude'], peak['width']]
211
+ additional_spectra[f'peak {index}']= eels_tools.gauss(self.energy_scale, p)
212
+ self.view.plot(scale=True, additional_spectra=additional_spectra )
213
+ self.change_y_scale = 1.
214
+
215
+ self.view.figure.canvas.draw_idle()
216
+
217
+
218
+ def set_dataset(self, index=0):
219
+ self.spec_dim = ft.get_dimensions_by_type('spectral', self.dataset)
220
+ if len(self.spec_dim) != 1:
221
+ raise TypeError('We need exactly one SPECTRAL dimension')
222
+ self.spec_dim = self.spec_dim[0]
223
+ self.energy_scale = self.spec_dim[1]
224
+
225
+ self.y_scale = 1.0
226
+ self.change_y_scale = 1.0
227
+
228
+ if 'peak_fit' not in self.dataset.metadata:
229
+ self.dataset.metadata['peak_fit'] = {}
230
+ if 'edges' in self.dataset.metadata:
231
+ if 'fit_area' in self.dataset.metadata['edges']:
232
+ self.dataset.metadata['peak_fit']['fit_start'] = self.dataset.metadata['edges']['fit_area']['fit_start']
233
+ self.dataset.metadata['peak_fit']['fit_end'] = self.dataset.metadata['edges']['fit_area']['fit_end']
234
+ self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
235
+ 'amplitude': 1000.0, 'width': 1.0,
236
+ 'type': 'Gauss', 'asymmetry': 0}}
237
+
238
+ self.peaks = self.dataset.metadata['peak_fit']
239
+ if 'fit_start' not in self.peaks:
240
+ self.peaks['fit_start'] = self.energy_scale[1]
241
+ if 'fit_end' not in self.peaks:
242
+ self.peaks['fit_end'] = self.energy_scale[-2]
243
+
244
+ if 'peak_model' in self.peaks:
245
+ self.peak_model = self.peaks['peak_model']
246
+ self.model = self.peak_model
247
+ if 'edge_model' in self.peaks:
248
+ self.model = self.model + self.peaks['edge_model']
249
+ else:
250
+ self.model = np.array([])
251
+ self.peak_model = np.array([])
252
+ if 'peak_out_list' in self.peaks:
253
+ self.peak_out_list = self.peaks['peak_out_list']
254
+ self.set_peak_list()
255
+
256
+ # check whether a core loss analysis has been done previously
257
+ if not hasattr(self, 'core_loss') and 'edges' in self.dataset.metadata:
258
+ self.core_loss = True
259
+ else:
260
+ self.core_loss = False
261
+
262
+ self.update()
263
+ if self.dataset.data_type.name =='SPECTRAL_IMAGE':
264
+ self.view = eels_dialog_utilities.SIPlot(self.dataset)
265
+ else:
266
+ self.view = eels_dialog_utilities.SpectrumPlot(self.dataset)
267
+ self.y_scale = 1.0
268
+ self.change_y_scale = 1.0
269
+
270
+ def set_fit_area(self, value):
271
+
272
+ self.peaks['fit_start'] = self.sidebar[1, 0].value
273
+ self.peaks['fit_end'] = self.sidebar[2, 0].value
274
+
275
+ self.plot()
276
+
277
+ def set_y_scale(self, value):
278
+ self.change_y_scale = 1/self.y_scale
279
+ if self.sidebar[12, 0].value:
280
+ dispersion = self.energy_scale[1] - self.energy_scale[0]
281
+ self.y_scale = 1/self.dataset.metadata['experiment']['flux_ppm'] * dispersion
282
+ else:
283
+ self.y_scale = 1.0
284
+
285
+ self.change_y_scale *= self.y_scale
286
+ self.update()
287
+ self.plot()
288
+
289
+ def update(self, index=0):
290
+
291
+ # self.setWindowTitle('update')
292
+ self.sidebar[1, 0].value = self.peaks['fit_start']
293
+ self.sidebar[2, 0].value = self.peaks['fit_end']
294
+
295
+ peak_index = self.sidebar[7, 0].value
296
+ self.peak_index = self.sidebar[7, 0].value
297
+ if str(peak_index) not in self.peaks['peaks']:
298
+ self.peaks['peaks'][str(peak_index)] = {'position': self.energy_scale[1], 'amplitude': 1000.0,
299
+ 'width': 1.0, 'type': 'Gauss', 'asymmetry': 0}
300
+ self.sidebar[8, 0].value = self.peaks['peaks'][str(peak_index)]['type']
301
+ if 'associated_edge' in self.peaks['peaks'][str(peak_index)]:
302
+ self.sidebar[7, 2].value = (self.peaks['peaks'][str(peak_index)]['associated_edge'])
303
+ else:
304
+ self.sidebar[7, 2].value = ''
305
+ self.sidebar[9, 0].value = self.peaks['peaks'][str(peak_index)]['position']
306
+ self.sidebar[10, 0].value = self.peaks['peaks'][str(peak_index)]['amplitude']
307
+ self.sidebar[11, 0].value = self.peaks['peaks'][str(peak_index)]['width']
308
+ if 'asymmetry' not in self.peaks['peaks'][str(peak_index)]:
309
+ self.peaks['peaks'][str(peak_index)]['asymmetry'] = 0.
310
+ self.sidebar[12, 0].value = self.peaks['peaks'][str(peak_index)]['asymmetry']
311
+
312
+
313
+ def fit_peaks(self, value = 0):
314
+ """Fit spectrum with peaks given in peaks dictionary"""
315
+ # print('Fitting peaks...')
316
+ p_in = []
317
+ for key, peak in self.peaks['peaks'].items():
318
+ if key.isdigit():
319
+ p_in.append(peak['position'])
320
+ p_in.append(peak['amplitude'])
321
+ p_in.append(peak['width'])
322
+
323
+ spectrum = np.array(self.dataset)
324
+
325
+ # set the energy scale and fit start and end points
326
+ energy_scale = np.array(self.energy_scale)
327
+ start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
328
+ end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
329
+
330
+ energy_scale = self.energy_scale[start_channel:end_channel]
331
+ # select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
332
+ if 'model' in self.dataset.metadata:
333
+ model = self.dataset.metadata['model'][start_channel:end_channel]
334
+ elif self.core_loss:
335
+ # print('Core loss model found. Fitting on top of the model.')
336
+ model = self.dataset.metadata['edges']['model']['spectrum'][start_channel:end_channel]
337
+ else:
338
+ # print('No core loss model found. Fitting to the full spectrum.')
339
+ model = np.zeros(end_channel - start_channel)
340
+
341
+ # if we have a core loss model we will only fit the difference between the model and the data.
342
+ difference = np.array(spectrum[start_channel:end_channel] - model)
343
+
344
+ # find the optimum fitting parameters
345
+ [self.p_out, _] = scipy.optimize.leastsq(eels_tools.residuals_smooth, np.array(p_in), ftol=1e-3,
346
+ args=(energy_scale, difference, False))
347
+
348
+ # construct the fit data from the optimized parameters
349
+ self.peak_model = np.zeros(len(self.energy_scale))
350
+ self.model = np.zeros(len(self.energy_scale))
351
+ self.model[start_channel:end_channel] = model
352
+ fit = eels_tools.model_smooth(energy_scale, self.p_out, False)
353
+ self.peak_model[start_channel:end_channel] = fit
354
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
355
+ self.model = self.model + self.peak_model
356
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
357
+
358
+ for key, peak in self.peaks['peaks'].items():
359
+ if key.isdigit():
360
+ p_index = int(key)*3
361
+ self.peaks['peaks'][key] = {'position': self.p_out[p_index],
362
+ 'amplitude': self.p_out[p_index+1],
363
+ 'width': self.p_out[p_index+2],
364
+ 'type': 'Gauss',
365
+ 'associated_edge': ''}
366
+
367
+ eels_tools.find_associated_edges(self.dataset)
368
+ self.find_white_lines()
369
+ self.update()
370
+ self.plot()
371
+
372
+
373
+
374
+ def find_white_lines(self):
375
+ eels_tools.find_white_lines(self.dataset)
376
+ self.wl_list = []
377
+ self.wls_list = []
378
+ if 'white_line_ratios' in self.dataset.metadata['peak_fit']:
379
+ if len(self.dataset.metadata['peak_fit']['white_line_ratios']) > 0:
380
+ for key in self.dataset.metadata['peak_fit']['white_line_ratios']:
381
+ self.wl_list.append(key)
382
+ for key in self.dataset.metadata['peak_fit']['white_line_sums']:
383
+ self.wls_list.append(key)
384
+
385
+ self.sidebar[14, 0].options = self.wl_list
386
+ self.sidebar[14, 0].value = self.wl_list[0]
387
+ self.sidebar[14, 2].value = f"{self.dataset.metadata['peak_fit']['white_line_ratios'][self.wl_list[0]]:.2f}"
388
+
389
+ self.sidebar[15, 0].options = self.wls_list
390
+ self.sidebar[15, 0].value = self.wls_list[0]
391
+ self.sidebar[15, 2].value = f"{self.dataset.metadata['peak_fit']['white_line_sums'][self.wls_list[0]]*1e6:.4f} ppm"
392
+
393
+ else:
394
+ self.wl_list.append('Ratio')
395
+ self.wls_list.append('Sum')
396
+
397
+ self.sidebar[14, 0].options = ['None']
398
+ self.sidebar[14, 0].value = 'None'
399
+ self.sidebar[14, 2].value = ' '
400
+
401
+ self.sidebar[15, 0].options = ['None']
402
+ self.sidebar[15, 0].value = 'None'
403
+ self.sidebar[15, 2].value = ' '
404
+
405
+ def find_peaks(self, value=0):
406
+ number_of_peaks = int(self.sidebar[5, 0].value)
407
+ if number_of_peaks > len(self.peak_out_list):
408
+ number_of_peaks = len(self.peak_out_list)
409
+ self.sidebar[5, 0].value = str(len(self.peak_out_list))
410
+ self.peak_list = []
411
+ self.peaks['peaks'] = {}
412
+ new_number_of_peaks = 0
413
+ for i in range(number_of_peaks):
414
+ self.peak_list.append((f'Peak {i+1}', i))
415
+ p = self.peak_out_list[i]
416
+ if p[1]>0:
417
+ self.peaks['peaks'][str(new_number_of_peaks)] = {'position': p[0], 'amplitude': p[1], 'width': p[2], 'type': 'Gauss',
418
+ 'asymmetry': 0}
419
+ new_number_of_peaks += 1
420
+ self.sidebar[5, 0].value = str(new_number_of_peaks)
421
+ self.peak_list.append((f'add peak', -1))
422
+
423
+ self.sidebar[7, 0].options = self.peak_list
424
+ self.sidebar[7, 0].value = 0
425
+
426
+ #eels_tools.find_associated_edges(self.dataset)
427
+ #self.find_white_lines()
428
+
429
+ self.update()
430
+ self.plot()
431
+
432
+ def smooth(self, value=0):
433
+ """Fit lots of Gaussian to spectrum and let the program sort it out
434
+
435
+ We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
436
+
437
+ """
438
+ iterations = self.sidebar[4, 0].value
439
+ self.sidebar[5, 0].value = 0
440
+ advanced_present=False
441
+
442
+ self.peak_model, self.peak_out_list, number_of_peaks = smooth(self.dataset, iterations, advanced_present)
443
+
444
+ spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
445
+ if spec_dim[1][0] > 0:
446
+ self.model = self.dataset.metadata['edges']['model']['spectrum']
447
+ elif 'model' in self.dataset.metadata:
448
+ self.model = self.dataset.metadata['model']
449
+ else:
450
+ self.model = np.zeros(len(spec_dim[1]))
451
+
452
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
453
+ self.model = self.model + self.peak_model
454
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
455
+ self.dataset.metadata['peak_fit']['peak_out_list'] = self.peak_out_list
456
+
457
+ self.sidebar[5, 0].value = str(len(self.peak_out_list))
458
+ self.update()
459
+ self.plot()
460
+
461
+ def make_model(self):
462
+ p_peaks = []
463
+ for key, peak in self.peaks['peaks'].items():
464
+ if key.isdigit():
465
+ p_peaks.append(peak['position'])
466
+ p_peaks.append(peak['amplitude'])
467
+ p_peaks.append(peak['width'])
468
+
469
+
470
+ # set the energy scale and fit start and end points
471
+ energy_scale = np.array(self.energy_scale)
472
+ start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
473
+ end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
474
+ energy_scale = self.energy_scale[start_channel:end_channel]
475
+ # select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
476
+
477
+ fit = eels_tools.model_smooth(energy_scale, p_peaks, False)
478
+ self.peak_model[start_channel:end_channel] = fit
479
+ if 'edge_model' in self.dataset.metadata['peak_fit']:
480
+ self.model = self.dataset.metadata['peak_fit']['edge_model'] + self.peak_model
481
+ else:
482
+ self.model = np.zeros(self.dataset.shape)
483
+
484
+ def modify_peak_position(self, value=-1):
485
+ peak_index = self.sidebar[7, 0].value
486
+ self.peaks['peaks'][str(peak_index)]['position'] = self.sidebar[9,0].value
487
+ self.make_model()
488
+ self.plot()
489
+
490
+ def modify_peak_amplitude(self, value=-1):
491
+ peak_index = self.sidebar[7, 0].value
492
+ self.peaks['peaks'][str(peak_index)]['amplitude'] = self.sidebar[10,0].value
493
+ self.make_model()
494
+ self.plot()
495
+
496
+ def modify_peak_width(self, value=-1):
497
+ peak_index = self.sidebar[7, 0].value
498
+ self.peaks['peaks'][str(peak_index)]['width'] = self.sidebar[11,0].value
499
+ self.make_model()
500
+ self.plot()
501
+
502
+ def peak_selection(self, change=None):
503
+ options = list(self.sidebar[7,0].options)
504
+
505
+ if self.sidebar[7, 0].value < 0:
506
+ options.insert(-1, (f'Peak {len(options)}', len(options)-1))
507
+ self.sidebar[7, 0].value = 0
508
+ self.sidebar[7,0].options = options
509
+ self.sidebar[7, 0].value = int(len(options)-2)
510
+
511
+ self.update()
512
+
513
+ def set_action(self):
514
+ self.sidebar[1, 0].observe(self.set_fit_area, names='value')
515
+ self.sidebar[2, 0].observe(self.set_fit_area, names='value')
516
+
517
+ self.sidebar[4, 2].on_click(self.smooth)
518
+ self.sidebar[7,0].observe(self.peak_selection)
519
+ self.sidebar[5,2].on_click(self.find_peaks)
520
+
521
+ self.sidebar[6, 0].on_click(self.fit_peaks)
522
+ self.sidebar[9, 0].observe(self.modify_peak_position, names='value')
523
+ self.sidebar[10, 0].observe(self.modify_peak_amplitude, names='value')
524
+ self.sidebar[11, 0].observe(self.modify_peak_width, names='value')
525
+
526
+
527
+
528
+
529
+ if Qt_available:
530
+ class PeakFitDialog(QtWidgets.QDialog):
531
+ """
532
+ EELS Input Dialog for ELNES Analysis
533
+ """
534
+
535
+ def __init__(self, datasets=None):
536
+ super().__init__(None, QtCore.Qt.WindowStaysOnTopHint)
537
+
538
+ if datasets is None:
539
+ # make a dummy dataset
540
+ datasets = ft.make_dummy_dataset('spectrum')
541
+ if not isinstance(datasets, dict):
542
+ datasets= {'Channel_000': datasets}
543
+
544
+ self.dataset = datasets[list(datasets.keys())[0]]
545
+ self.datasets = datasets
546
+ # Create an instance of the GUI
547
+ if 'low_loss' in self.dataset.metadata:
548
+ mode = 'low_loss'
549
+ else:
550
+ mode = 'core_loss'
551
+
552
+ self.ui = peak_dlg.UiDialog(self, mode=mode)
553
+
554
+ self.set_action()
555
+
556
+ self.energy_scale = np.array([])
557
+ self.peak_out_list = []
558
+ self.p_out = []
559
+ self.axis = None
560
+ self.show_regions = False
561
+ self.show()
562
+
563
+
564
+
565
+ if not isinstance(self.dataset, sidpy.Dataset):
566
+ raise TypeError('dataset has to be a sidpy dataset')
567
+ self.spec_dim = ft.get_dimensions_by_type('spectral', self.dataset)
568
+ if len(self.spec_dim) != 1:
569
+ raise TypeError('We need exactly one SPECTRAL dimension')
570
+ self.spec_dim = self.spec_dim[0]
571
+ self.energy_scale = self.spec_dim[1].values.copy()
572
+
573
+ if 'peak_fit' not in self.dataset.metadata:
574
+ self.dataset.metadata['peak_fit'] = {}
575
+ if 'edges' in self.dataset.metadata:
576
+ if 'fit_area' in self.dataset.metadata['edges']:
577
+ self.dataset.metadata['peak_fit']['fit_start'] = \
578
+ self.dataset.metadata['edges']['fit_area']['fit_start']
579
+ self.dataset.metadata['peak_fit']['fit_end'] = self.dataset.metadata['edges']['fit_area']['fit_end']
580
+ self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
581
+ 'amplitude': 1000.0, 'width': 1.0,
582
+ 'type': 'Gauss', 'asymmetry': 0}}
583
+
584
+
585
+ self.peaks = self.dataset.metadata['peak_fit']
586
+ if 'fit_start' not in self.peaks:
587
+ self.peaks['fit_start'] = self.energy_scale[1]
588
+ self.peaks['fit_end'] = self.energy_scale[-2]
589
+
590
+ if 'peak_model' in self.peaks:
591
+ self.peak_model = self.peaks['peak_model']
592
+ self.model = self.peak_model
593
+ if 'edge_model' in self.peaks:
594
+ self.model = self.model + self.peaks['edge_model']
595
+ else:
596
+ self.model = np.array([])
597
+ self.peak_model = np.array([])
598
+ if 'peak_out_list' in self.peaks:
599
+ self.peak_out_list = self.peaks['peak_out_list']
600
+ self.set_peak_list()
601
+
602
+ # check whether a core loss analysis has been done previously
603
+ if not hasattr(self, 'core_loss') and 'edges' in self.dataset.metadata:
604
+ self.core_loss = True
605
+ else:
606
+ self.core_loss = False
607
+
608
+ self.update()
609
+ self.dataset.plot()
610
+
611
+ if self.dataset.data_type.name == 'SPECTRAL_IMAGE':
612
+ if 'SI_bin_x' not in self.dataset.metadata['experiment']:
613
+ self.dataset.metadata['experiment']['SI_bin_x'] = 1
614
+ self.dataset.metadata['experiment']['SI_bin_y'] = 1
615
+ bin_x = self.dataset.metadata['experiment']['SI_bin_x']
616
+ bin_y = self.dataset.metadata['experiment']['SI_bin_y']
617
+
618
+ self.dataset.view.set_bin([bin_x, bin_y])
619
+
620
+ if hasattr(self.dataset.view, 'axes'):
621
+ self.axis = self.dataset.view.axes[-1]
622
+ elif hasattr(self.dataset.view, 'axis'):
623
+ self.axis = self.dataset.view.axis
624
+ self.figure = self.axis.figure
625
+
626
+ if not advanced_present:
627
+ self.ui.iteration_list = ['0']
628
+ self.ui.smooth_list.clear()
629
+ self.ui.smooth_list.addItems(self.ui.iteration_list)
630
+ self.ui.smooth_list.setCurrentIndex(0)
631
+
632
+ if 'low_loss' in self.dataset.metadata:
633
+ self.ui.iteration_list = ['0']
634
+
635
+
636
+ self.figure.canvas.mpl_connect('button_press_event', self.plot)
637
+
638
+
639
+ self.plot()
640
+
641
+ def update(self):
642
+ # self.setWindowTitle('update')
643
+ self.ui.edit1.setText(f"{self.peaks['fit_start']:.2f}")
644
+ self.ui.edit2.setText(f"{self.peaks['fit_end']:.2f}")
645
+
646
+ peak_index = self.ui.list3.currentIndex()
647
+ if str(peak_index) not in self.peaks['peaks']:
648
+ self.peaks['peaks'][str(peak_index)] = {'position': self.energy_scale[1], 'amplitude': 1000.0,
649
+ 'width': 1.0, 'type': 'Gauss', 'asymmetry': 0}
650
+ self.ui.list4.setCurrentText(self.peaks['peaks'][str(peak_index)]['type'])
651
+ if 'associated_edge' in self.peaks['peaks'][str(peak_index)]:
652
+ self.ui.unit3.setText(self.peaks['peaks'][str(peak_index)]['associated_edge'])
653
+ else:
654
+ self.ui.unit3.setText('')
655
+ self.ui.edit5.setText(f"{self.peaks['peaks'][str(peak_index)]['position']:.2f}")
656
+ self.ui.edit6.setText(f"{self.peaks['peaks'][str(peak_index)]['amplitude']:.2f}")
657
+ self.ui.edit7.setText(f"{self.peaks['peaks'][str(peak_index)]['width']:.2f}")
658
+ if 'asymmetry' not in self.peaks['peaks'][str(peak_index)]:
659
+ self.peaks['peaks'][str(peak_index)]['asymmetry'] = 0.
660
+ self.ui.edit8.setText(f"{self.peaks['peaks'][str(peak_index)]['asymmetry']:.2f}")
661
+
662
+ def plot(self):
663
+
664
+ spec_dim = ft.get_dimensions_by_type(sidpy.DimensionType.SPECTRAL, self.dataset)
665
+ spec_dim = spec_dim[0]
666
+ self.energy_scale = spec_dim[1].values
667
+ if self.dataset.data_type == sidpy.DataType.SPECTRAL_IMAGE:
668
+ spectrum = self.dataset.view.get_spectrum()
669
+ self.axis = self.dataset.view.axes[1]
670
+ name = 's'
671
+ if 'zero_loss' in self.dataset.metadata:
672
+ x = self.dataset.view.x
673
+ y = self.dataset.view.y
674
+ self.energy_scale -= self.dataset.metadata['zero_loss']['shifts'][x, y]
675
+ name = f"shift { self.dataset.metadata['zero_loss']['shifts'][x, y]:.3f}"
676
+ self.setWindowTitle(f'plot {x}')
677
+ else:
678
+ spectrum = np.array(self.dataset)
679
+ self.axis = self.dataset.view.axis
680
+
681
+ x_limit = self.axis.get_xlim()
682
+ y_limit = self.axis.get_ylim()
683
+ self.axis.clear()
684
+
685
+ self.axis.plot(self.energy_scale, spectrum, label='spectrum')
686
+ if 'zero_loss' in self.dataset.metadata:
687
+ self.axis.plot(self.energy_scale, spectrum, label=name)
688
+
689
+ if len(self.model) > 1:
690
+ self.axis.plot(self.energy_scale, self.model, label='model')
691
+ self.axis.plot(self.energy_scale, spectrum - self.model, label='difference')
692
+ #self.axis.plot(self.energy_scale, (spectrum - self.model) / np.sqrt(spectrum), label='Poisson')
693
+ self.axis.legend()
694
+ self.axis.set_xlim(x_limit)
695
+ self.axis.set_ylim(y_limit)
696
+ self.axis.figure.canvas.draw_idle()
697
+
698
+ for index, peak in self.peaks['peaks'].items():
699
+ p = [peak['position'], peak['amplitude'], peak['width']]
700
+ self.axis.plot(self.energy_scale, eels_tools.gauss(self.energy_scale, p))
701
+
702
+ def fit_peaks(self):
703
+ """Fit spectrum with peaks given in peaks dictionary"""
704
+ print('Fitting peaks...')
705
+ p_in = []
706
+ for key, peak in self.peaks['peaks'].items():
707
+ if key.isdigit():
708
+ p_in.append(peak['position'])
709
+ p_in.append(peak['amplitude'])
710
+ p_in.append(peak['width'])
711
+
712
+ # check whether we have a spectral image or just a single spectrum
713
+ if self.dataset.data_type == sidpy.DataType.SPECTRAL_IMAGE:
714
+ spectrum = self.dataset.view.get_spectrum()
715
+ else:
716
+ spectrum = np.array(self.dataset)
717
+
718
+ # set the energy scale and fit start and end points
719
+ energy_scale = np.array(self.energy_scale)
720
+ start_channel = np.searchsorted(energy_scale, self.peaks['fit_start'])
721
+ end_channel = np.searchsorted(energy_scale, self.peaks['fit_end'])
722
+
723
+ energy_scale = self.energy_scale[start_channel:end_channel]
724
+ # select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
725
+ if 'model' in self.dataset.metadata:
726
+ model = self.dataset.metadata['model'][start_channel:end_channel]
727
+ elif self.core_loss:
728
+ print('Core loss model found. Fitting on top of the model.')
729
+ model = self.dataset.metadata['edges']['model']['spectrum'][start_channel:end_channel]
730
+ else:
731
+ print('No core loss model found. Fitting to the full spectrum.')
732
+ model = np.zeros(end_channel - start_channel)
733
+
734
+ # if we have a core loss model we will only fit the difference between the model and the data.
735
+ difference = np.array(spectrum[start_channel:end_channel] - model)
736
+
737
+ # find the optimum fitting parameters
738
+ [self.p_out, _] = scipy.optimize.leastsq(eels_tools.residuals_smooth, np.array(p_in), ftol=1e-3,
739
+ args=(energy_scale, difference, False))
740
+
741
+ # construct the fit data from the optimized parameters
742
+ self.peak_model = np.zeros(len(self.energy_scale))
743
+ self.model = np.zeros(len(self.energy_scale))
744
+ self.model[start_channel:end_channel] = model
745
+ fit = eels_tools.model_smooth(energy_scale, self.p_out, False)
746
+ self.peak_model[start_channel:end_channel] = fit
747
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
748
+ self.model = self.model + self.peak_model
749
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
750
+
751
+ for key, peak in self.peaks['peaks'].items():
752
+ if key.isdigit():
753
+ p_index = int(key)*3
754
+ self.peaks['peaks'][key] = {'position': self.p_out[p_index],
755
+ 'amplitude': self.p_out[p_index+1],
756
+ 'width': self.p_out[p_index+2],
757
+ 'associated_edge': ''}
758
+
759
+ self.find_associated_edges()
760
+ self.find_white_lines()
761
+ self.update()
762
+ self.plot()
763
+
764
+ def smooth(self):
765
+ """Fit lots of Gaussian to spectrum and let the program sort it out
766
+
767
+ We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
768
+
769
+ """
770
+ if 'edges' in self.dataset.metadata:
771
+ if 'model' in self.dataset.metadata['edges']:
772
+ self.dataset.metadata['model'] = self.dataset.metadata['edges']['model']
773
+ if 'resolution_function' in self.datasets:
774
+ self.dataset.metadata['model'] = np.array(self.datasets['resolution_function'])
775
+ iterations = int(self.ui.smooth_list.currentIndex())
776
+
777
+ self.peak_model, self.peak_out_list, number_of_peaks = smooth(self.dataset, iterations, advanced_present)
778
+
779
+ spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
780
+ if spec_dim[1][0] > 0:
781
+ self.model = self.dataset.metadata['edges']['model']['spectrum']
782
+ elif 'model' in self.dataset.metadata:
783
+ self.model = self.dataset.metadata['model']
784
+ else:
785
+ self.model = np.zeros(len(spec_dim[1]))
786
+
787
+ self.ui.find_edit.setText(str(number_of_peaks))
788
+
789
+ self.dataset.metadata['peak_fit']['edge_model'] = self.model
790
+ self.model = self.model + self.peak_model
791
+ self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
792
+ self.dataset.metadata['peak_fit']['peak_out_list'] = self.peak_out_list
793
+
794
+ self.update()
795
+ self.plot()
796
+
797
+ def find_associated_edges(self):
798
+ onsets = []
799
+ edges = []
800
+ if 'edges' in self.dataset.metadata:
801
+ for key, edge in self.dataset.metadata['edges'].items():
802
+ if key.isdigit():
803
+ element = edge['element']
804
+ for sym in edge['all_edges']: # TODO: Could be replaced with exclude
805
+ onsets.append(edge['all_edges'][sym]['onset'] + edge['chemical_shift'])
806
+ # if 'sym' == edge['symmetry']:
807
+ edges.append([key, f"{element}-{sym}", onsets[-1]])
808
+ for key, peak in self.peaks['peaks'].items():
809
+ if key.isdigit():
810
+ distance = self.energy_scale[-1]
811
+ index = -1
812
+ for ii, onset in enumerate(onsets):
813
+ if onset < peak['position'] < onset+50:
814
+ if distance > np.abs(peak['position'] - onset):
815
+ distance = np.abs(peak['position'] - onset) # TODO: check whether absolute is good
816
+ distance_onset = peak['position'] - onset
817
+ index = ii
818
+ if index >= 0:
819
+ peak['associated_edge'] = edges[index][1] # check if more info is necessary
820
+ peak['distance_to_onset'] = distance_onset
821
+
822
+ def find_white_lines(self):
823
+ eels_tools.find_white_lines(self.dataset)
824
+
825
+ self.ui.wl_list = []
826
+ self.ui.wls_list = []
827
+ if len(self.peaks['white_line_ratios']) > 0:
828
+ for key in self.peaks['white_line_ratios']:
829
+ self.ui.wl_list.append(key)
830
+ for key in self.peaks['white_line_sums']:
831
+ self.ui.wls_list.append(key)
832
+
833
+ self.ui.listwl.clear()
834
+ self.ui.listwl.addItems(self.ui.wl_list)
835
+ self.ui.listwl.setCurrentIndex(0)
836
+ self.ui.unitswl.setText(f"{self.peaks['white_line_ratios'][self.ui.wl_list[0]]:.2f}")
837
+
838
+ self.ui.listwls.clear()
839
+ self.ui.listwls.addItems(self.ui.wls_list)
840
+ self.ui.listwls.setCurrentIndex(0)
841
+ self.ui.unitswls.setText(f"{self.peaks['white_line_sums'][self.ui.wls_list[0]]*1e6:.4f} ppm")
842
+ else:
843
+ self.ui.wl_list.append('Ratio')
844
+ self.ui.wls_list.append('Sum')
845
+
846
+ self.ui.listwl.clear()
847
+ self.ui.listwl.addItems(self.ui.wl_list)
848
+ self.ui.listwl.setCurrentIndex(0)
849
+ self.ui.unitswl.setText('')
850
+
851
+ self.ui.listwls.clear()
852
+ self.ui.listwls.addItems(self.ui.wls_list)
853
+ self.ui.listwls.setCurrentIndex(0)
854
+ self.ui.unitswls.setText('')
855
+
856
+ def find_peaks(self):
857
+ number_of_peaks = int(str(self.ui.find_edit.displayText()).strip())
858
+
859
+ # is now sorted in smooth function
860
+ # flat_list = [item for sublist in self.peak_out_list for item in sublist]
861
+ # new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
862
+ # arg_list = np.argsort(np.abs(new_list[:, 1]))
863
+
864
+ self.ui.peak_list = []
865
+ self.peaks['peaks'] = {}
866
+ for i in range(number_of_peaks):
867
+ self.ui.peak_list.append(f'Peak {i+1}')
868
+ p = self.peak_out_list[i]
869
+ self.peaks['peaks'][str(i)] = {'position': p[0], 'amplitude': p[1], 'width': p[2], 'type': 'Gauss',
870
+ 'asymmetry': 0}
871
+
872
+ self.ui.peak_list.append(f'add peak')
873
+ self.ui.list3.clear()
874
+ self.ui.list3.addItems(self.ui.peak_list)
875
+ self.ui.list3.setCurrentIndex(0)
876
+ self.find_associated_edges()
877
+ self.find_white_lines()
878
+
879
+ self.update()
880
+ self.plot()
881
+
882
+ def set_peak_list(self):
883
+ self.ui.peak_list = []
884
+ if 'peaks' not in self.peaks:
885
+ self.peaks['peaks'] = {}
886
+ key = 0
887
+ for key in self.peaks['peaks']:
888
+ if key.isdigit():
889
+ self.ui.peak_list.append(f'Peak {int(key) + 1}')
890
+ self.ui.find_edit.setText(str(int(key) + 1))
891
+ self.ui.peak_list.append(f'add peak')
892
+ self.ui.list3.clear()
893
+ self.ui.list3.addItems(self.ui.peak_list)
894
+ self.ui.list3.setCurrentIndex(0)
895
+
896
+ def on_enter(self):
897
+ if self.sender() == self.ui.edit1:
898
+ value = float(str(self.ui.edit1.displayText()).strip())
899
+ if value < self.energy_scale[0]:
900
+ value = self.energy_scale[0]
901
+ if value > self.energy_scale[-5]:
902
+ value = self.energy_scale[-5]
903
+ self.peaks['fit_start'] = value
904
+ self.ui.edit1.setText(str(self.peaks['fit_start']))
905
+ elif self.sender() == self.ui.edit2:
906
+ value = float(str(self.ui.edit2.displayText()).strip())
907
+ if value < self.energy_scale[5]:
908
+ value = self.energy_scale[5]
909
+ if value > self.energy_scale[-1]:
910
+ value = self.energy_scale[-1]
911
+ self.peaks['fit_end'] = value
912
+ self.ui.edit2.setText(str(self.peaks['fit_end']))
913
+ elif self.sender() == self.ui.edit5:
914
+ value = float(str(self.ui.edit5.displayText()).strip())
915
+ peak_index = self.ui.list3.currentIndex()
916
+ self.peaks['peaks'][str(peak_index)]['position'] = value
917
+ elif self.sender() == self.ui.edit6:
918
+ value = float(str(self.ui.edit6.displayText()).strip())
919
+ peak_index = self.ui.list3.currentIndex()
920
+ self.peaks['peaks'][str(peak_index)]['amplitude'] = value
921
+ elif self.sender() == self.ui.edit7:
922
+ value = float(str(self.ui.edit7.displayText()).strip())
923
+ peak_index = self.ui.list3.currentIndex()
924
+ self.peaks['peaks'][str(peak_index)]['width'] = value
925
+
926
+ def on_list_enter(self):
927
+ self.setWindowTitle(f'list {self.sender}, {self.ui.list_model}')
928
+ if self.sender() == self.ui.list3:
929
+ if self.ui.list3.currentText().lower() == 'add peak':
930
+ peak_index = self.ui.list3.currentIndex()
931
+ self.ui.list3.insertItem(peak_index, f'Peak {peak_index+1}')
932
+ self.peaks['peaks'][str(peak_index+1)] = {'position': self.energy_scale[1],
933
+ 'amplitude': 1000.0, 'width': 1.0,
934
+ 'type': 'Gauss', 'asymmetry': 0}
935
+ self.ui.list3.setCurrentIndex(peak_index)
936
+ self.update()
937
+
938
+ elif self.sender() == self.ui.listwls or self.sender() == self.ui.listwl:
939
+ wl_index = self.sender().currentIndex()
940
+
941
+ self.ui.listwl.setCurrentIndex(wl_index)
942
+ self.ui.unitswl.setText(f"{self.peaks['white_line_ratios'][self.ui.wl_list[wl_index]]:.2f}")
943
+ self.ui.listwls.setCurrentIndex(wl_index)
944
+ self.ui.unitswls.setText(f"{self.peaks['white_line_sums'][self.ui.wls_list[wl_index]] * 1e6:.4f} ppm")
945
+ elif self.sender() == self.ui.list_model:
946
+ self.setWindowTitle('list 1')
947
+ if self.sender().currentIndex() == 1:
948
+ if 'resolution_function' in self.datasets:
949
+ self.setWindowTitle('list 2')
950
+ self.dataset.metadata['model'] = np.array(self.datasets['resolution_function'])
951
+ else:
952
+ self.ui.list_model.setCurrentIndex(0)
953
+ else:
954
+ self.ui.list_model.setCurrentIndex(0)
955
+ def set_action(self):
956
+ pass
957
+ self.ui.edit1.editingFinished.connect(self.on_enter)
958
+ self.ui.edit2.editingFinished.connect(self.on_enter)
959
+ self.ui.edit5.editingFinished.connect(self.on_enter)
960
+ self.ui.edit6.editingFinished.connect(self.on_enter)
961
+ self.ui.edit7.editingFinished.connect(self.on_enter)
962
+ self.ui.edit8.editingFinished.connect(self.on_enter)
963
+ self.ui.list3.activated[str].connect(self.on_list_enter)
964
+ self.ui.find_button.clicked.connect(self.find_peaks)
965
+ self.ui.smooth_button.clicked.connect(self.smooth)
966
+ self.ui.fit_button.clicked.connect(self.fit_peaks)
967
+ if hasattr(self.ui, 'listwls'):
968
+ self.ui.listwls.activated[str].connect(self.on_list_enter)
969
+ self.ui.listwl.activated[str].connect(self.on_list_enter)
970
+ else:
971
+ self.ui.zl_button.clicked.connect(self.fit_zero_loss)
972
+ self.ui.drude_button.clicked.connect(self.smooth)
973
+ self.ui.list_model.activated[str].connect(self.on_list_enter)
974
+
975
+ def fit_zero_loss(self):
976
+ """get shift of spectrum form zero-loss peak position"""
977
+ zero_loss_fit_width=0.3
978
+
979
+ energy_scale = self.dataset.energy_loss
980
+ zl_dataset = self.dataset.copy()
981
+ zl_dataset.title = 'resolution_function'
982
+ shifts = np.zeros(self.dataset.shape[0:2])
983
+ zero_p = np.zeros([self.dataset.shape[0],self.dataset.shape[1],6])
984
+ fwhm_p = np.zeros(self.dataset.shape[0:2])
985
+ bin_x = bin_y = 1
986
+ total_spec = int(self.dataset.shape[0]/bin_x)*int(self.dataset.shape[1]/bin_y)
987
+ self.ui.progress.setMaximum(total_spec)
988
+ self.ui.progress.setValue(0)
989
+ zero_loss_fit_width=0.3
990
+ ind = 0
991
+ for x in range(self.dataset.shape[0]):
992
+ for y in range(self.dataset.shape[1]):
993
+ ind += 1
994
+ self.ui.progress.setValue(ind)
995
+ spectrum = self.dataset[x, y, :]
996
+ fwhm, delta_e = eels_tools.fix_energy_scale(spectrum, energy_scale)
997
+ z_loss, p_zl = eels_tools.resolution_function(energy_scale - delta_e, spectrum, zero_loss_fit_width)
998
+ fwhm2, delta_e2 = eels_tools.fix_energy_scale(z_loss, energy_scale - delta_e)
999
+ shifts[x, y] = delta_e + delta_e2
1000
+ zero_p[x,y,:] = p_zl
1001
+ zl_dataset[x,y] = z_loss
1002
+ fwhm_p[x,y] = fwhm2
1003
+
1004
+ zl_dataset.metadata['zero_loss'] = {'parameter': zero_p,
1005
+ 'shifts': shifts,
1006
+ 'fwhm': fwhm_p}
1007
+ self.dataset.metadata['zero_loss'] = {'parameter': zero_p,
1008
+ 'shifts': shifts,
1009
+ 'fwhm': fwhm_p}
1010
+
1011
+ self.datasets['resolution_function'] = zl_dataset
1012
+ self.update()
1013
+ self.plot()
1014
+
1015
+
1016
+
1017
+ def smooth(dataset, iterations, advanced_present):
1018
+ """Gaussian mixture model (non-Bayesian)
1019
+
1020
+ Fit lots of Gaussian to spectrum and let the program sort it out
1021
+ We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
1022
+
1023
+ """
1024
+
1025
+ # TODO: add sensitivity to dialog and the two functions below
1026
+ peaks = dataset.metadata['peak_fit']
1027
+
1028
+ peak_model, peak_out_list = eels_tools.find_peaks(dataset,
1029
+ peaks['fit_start'],
1030
+ peaks['fit_end'])
1031
+ #
1032
+ #cif advanced_present and iterations > 1:
1033
+ # peak_model, peak_out_list = advanced_eels_tools.smooth(dataset, peaks['fit_start'],
1034
+ # peaks['fit_end'], iterations=iterations)
1035
+ # else:
1036
+ # peak_model, peak_out_list = eels_tools.find_peaks(dataset, peaks['fit_start'], peaks['fit_end'])
1037
+ # peak_out_list = [peak_out_list]
1038
+
1039
+ new_list = np.reshape(peak_out_list, [len(peak_out_list) // 3, 3])
1040
+ area = np.sqrt(2 * np.pi) * np.abs(new_list[:, 1]) * np.abs(new_list[:, 2] / np.sqrt(2 * np.log(2)))
1041
+ arg_list = np.argsort(area)[::-1]
1042
+ area = area[arg_list]
1043
+ peak_out_list = new_list[arg_list]
1044
+
1045
+ number_of_peaks = np.searchsorted(area * -1, -np.average(area))
1046
+
1047
+ return peak_model, peak_out_list, number_of_peaks