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/crystal_tools.py +47 -5
- pyTEMlib/eels_dialog.py +656 -41
- pyTEMlib/eels_dialog_utilities.py +8 -4
- pyTEMlib/eels_dlg.py +4 -3
- pyTEMlib/eels_tools.py +93 -39
- pyTEMlib/file_tools.py +95 -13
- pyTEMlib/graph_tools.py +716 -10
- pyTEMlib/image_dialog.py +1 -1
- pyTEMlib/image_dlg.py +1 -1
- pyTEMlib/image_tools.py +82 -39
- pyTEMlib/info_dialog.py +356 -15
- pyTEMlib/info_dlg.py +1 -1
- pyTEMlib/info_widget.py +655 -0
- pyTEMlib/interactive_eels.py +12 -4
- pyTEMlib/peak_dialog.py +621 -22
- pyTEMlib/peak_dlg.py +1 -1
- pyTEMlib/version.py +2 -2
- pyTEMlib/viz.py +217 -0
- {pyTEMlib-0.2023.4.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/METADATA +5 -5
- pyTEMlib-0.2023.8.0.dist-info/RECORD +40 -0
- {pyTEMlib-0.2023.4.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/WHEEL +1 -1
- pyTEMlib-0.2023.4.0.dist-info/RECORD +0 -39
- {pyTEMlib-0.2023.4.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/LICENSE +0 -0
- {pyTEMlib-0.2023.4.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/entry_points.txt +0 -0
- {pyTEMlib-0.2023.4.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/top_level.txt +0 -0
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
|
-
|
|
548
|
-
|
|
1146
|
+
def smooth(dataset, iterations, advanced_present):
|
|
1147
|
+
"""Gaussian mixture model (non-Bayesian)
|
|
549
1148
|
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
556
|
-
|
|
1154
|
+
# TODO: add sensitivity to dialog and the two functions below
|
|
1155
|
+
peaks = dataset.metadata['peak_fit']
|
|
557
1156
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
1171
|
+
number_of_peaks = np.searchsorted(area * -1, -np.average(area))
|
|
573
1172
|
|
|
574
|
-
|
|
1173
|
+
return peak_model, peak_out_list, number_of_peaks
|