pyTEMlib 0.2023.3.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 +673 -42
- pyTEMlib/eels_dialog_utilities.py +8 -4
- pyTEMlib/eels_dlg.py +6 -5
- pyTEMlib/eels_tools.py +118 -38
- 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 +807 -120
- pyTEMlib/peak_dlg.py +53 -24
- pyTEMlib/version.py +2 -2
- pyTEMlib/viz.py +217 -0
- pyTEMlib/xrpa_x_sections.py +39 -25
- {pyTEMlib-0.2023.3.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.3.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/WHEEL +1 -1
- pyTEMlib-0.2023.3.0.dist-info/RECORD +0 -39
- {pyTEMlib-0.2023.3.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/LICENSE +0 -0
- {pyTEMlib-0.2023.3.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/entry_points.txt +0 -0
- {pyTEMlib-0.2023.3.0.dist-info → pyTEMlib-0.2023.8.0.dist-info}/top_level.txt +0 -0
pyTEMlib/peak_dialog.py
CHANGED
|
@@ -7,17 +7,23 @@ 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
|
-
|
|
20
|
-
|
|
25
|
+
from pyTEMlib import eels_tools
|
|
26
|
+
from pyTEMlib import peak_dlg
|
|
21
27
|
|
|
22
28
|
advanced_present = True
|
|
23
29
|
try:
|
|
@@ -28,20 +34,626 @@ 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
|
"""
|
|
34
633
|
EELS Input Dialog for ELNES Analysis
|
|
35
634
|
"""
|
|
36
635
|
|
|
37
|
-
def __init__(self,
|
|
636
|
+
def __init__(self, datasets=None):
|
|
38
637
|
super().__init__(None, QtCore.Qt.WindowStaysOnTopHint)
|
|
638
|
+
|
|
639
|
+
if datasets is None:
|
|
640
|
+
# make a dummy dataset
|
|
641
|
+
datasets = ft.make_dummy_dataset('spectrum')
|
|
642
|
+
if not isinstance(datasets, dict):
|
|
643
|
+
datasets= {'Channel_000': datasets}
|
|
644
|
+
|
|
645
|
+
self.dataset = datasets[list(datasets.keys())[0]]
|
|
646
|
+
self.datasets = datasets
|
|
39
647
|
# Create an instance of the GUI
|
|
40
|
-
|
|
648
|
+
if 'low_loss' in self.dataset.metadata:
|
|
649
|
+
mode = 'low_loss'
|
|
650
|
+
else:
|
|
651
|
+
mode = 'core_loss'
|
|
652
|
+
|
|
653
|
+
self.ui = peak_dlg.UiDialog(self, mode=mode)
|
|
41
654
|
|
|
42
655
|
self.set_action()
|
|
43
656
|
|
|
44
|
-
self.dataset = dataset
|
|
45
657
|
self.energy_scale = np.array([])
|
|
46
658
|
self.peak_out_list = []
|
|
47
659
|
self.p_out = []
|
|
@@ -49,14 +661,11 @@ if Qt_available:
|
|
|
49
661
|
self.show_regions = False
|
|
50
662
|
self.show()
|
|
51
663
|
|
|
52
|
-
|
|
53
|
-
# make a dummy dataset
|
|
54
|
-
dataset = ft.make_dummy_dataset('spectrum')
|
|
664
|
+
|
|
55
665
|
|
|
56
|
-
if not isinstance(dataset, sidpy.Dataset):
|
|
666
|
+
if not isinstance(self.dataset, sidpy.Dataset):
|
|
57
667
|
raise TypeError('dataset has to be a sidpy dataset')
|
|
58
|
-
self.
|
|
59
|
-
self.spec_dim = ft.get_dimensions_by_type('spectral', dataset)
|
|
668
|
+
self.spec_dim = ft.get_dimensions_by_type('spectral', self.dataset)
|
|
60
669
|
if len(self.spec_dim) != 1:
|
|
61
670
|
raise TypeError('We need exactly one SPECTRAL dimension')
|
|
62
671
|
self.spec_dim = self.spec_dim[0]
|
|
@@ -72,6 +681,7 @@ if Qt_available:
|
|
|
72
681
|
self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
|
|
73
682
|
'amplitude': 1000.0, 'width': 1.0,
|
|
74
683
|
'type': 'Gauss', 'asymmetry': 0}}
|
|
684
|
+
|
|
75
685
|
|
|
76
686
|
self.peaks = self.dataset.metadata['peak_fit']
|
|
77
687
|
if 'fit_start' not in self.peaks:
|
|
@@ -120,7 +730,13 @@ if Qt_available:
|
|
|
120
730
|
self.ui.smooth_list.addItems(self.ui.iteration_list)
|
|
121
731
|
self.ui.smooth_list.setCurrentIndex(0)
|
|
122
732
|
|
|
733
|
+
if 'low_loss' in self.dataset.metadata:
|
|
734
|
+
self.ui.iteration_list = ['0']
|
|
735
|
+
|
|
736
|
+
|
|
123
737
|
self.figure.canvas.mpl_connect('button_press_event', self.plot)
|
|
738
|
+
|
|
739
|
+
|
|
124
740
|
self.plot()
|
|
125
741
|
|
|
126
742
|
def update(self):
|
|
@@ -145,12 +761,20 @@ if Qt_available:
|
|
|
145
761
|
self.ui.edit8.setText(f"{self.peaks['peaks'][str(peak_index)]['asymmetry']:.2f}")
|
|
146
762
|
|
|
147
763
|
def plot(self):
|
|
764
|
+
|
|
148
765
|
spec_dim = ft.get_dimensions_by_type(sidpy.DimensionType.SPECTRAL, self.dataset)
|
|
149
766
|
spec_dim = spec_dim[0]
|
|
150
767
|
self.energy_scale = spec_dim[1].values
|
|
151
768
|
if self.dataset.data_type == sidpy.DataType.SPECTRAL_IMAGE:
|
|
152
769
|
spectrum = self.dataset.view.get_spectrum()
|
|
153
770
|
self.axis = self.dataset.view.axes[1]
|
|
771
|
+
name = 's'
|
|
772
|
+
if 'zero_loss' in self.dataset.metadata:
|
|
773
|
+
x = self.dataset.view.x
|
|
774
|
+
y = self.dataset.view.y
|
|
775
|
+
self.energy_scale -= self.dataset.metadata['zero_loss']['shifts'][x, y]
|
|
776
|
+
name = f"shift { self.dataset.metadata['zero_loss']['shifts'][x, y]:.3f}"
|
|
777
|
+
self.setWindowTitle(f'plot {x}')
|
|
154
778
|
else:
|
|
155
779
|
spectrum = np.array(self.dataset)
|
|
156
780
|
self.axis = self.dataset.view.axis
|
|
@@ -160,10 +784,13 @@ if Qt_available:
|
|
|
160
784
|
self.axis.clear()
|
|
161
785
|
|
|
162
786
|
self.axis.plot(self.energy_scale, spectrum, label='spectrum')
|
|
787
|
+
if 'zero_loss' in self.dataset.metadata:
|
|
788
|
+
self.axis.plot(self.energy_scale, spectrum, label=name)
|
|
789
|
+
|
|
163
790
|
if len(self.model) > 1:
|
|
164
791
|
self.axis.plot(self.energy_scale, self.model, label='model')
|
|
165
792
|
self.axis.plot(self.energy_scale, spectrum - self.model, label='difference')
|
|
166
|
-
self.axis.plot(self.energy_scale, (spectrum - self.model) / np.sqrt(spectrum), label='Poisson')
|
|
793
|
+
#self.axis.plot(self.energy_scale, (spectrum - self.model) / np.sqrt(spectrum), label='Poisson')
|
|
167
794
|
self.axis.legend()
|
|
168
795
|
self.axis.set_xlim(x_limit)
|
|
169
796
|
self.axis.set_ylim(y_limit)
|
|
@@ -171,7 +798,7 @@ if Qt_available:
|
|
|
171
798
|
|
|
172
799
|
for index, peak in self.peaks['peaks'].items():
|
|
173
800
|
p = [peak['position'], peak['amplitude'], peak['width']]
|
|
174
|
-
self.axis.plot(self.energy_scale,
|
|
801
|
+
self.axis.plot(self.energy_scale, eels_tools.gauss(self.energy_scale, p))
|
|
175
802
|
|
|
176
803
|
def fit_peaks(self):
|
|
177
804
|
"""Fit spectrum with peaks given in peaks dictionary"""
|
|
@@ -196,7 +823,9 @@ if Qt_available:
|
|
|
196
823
|
|
|
197
824
|
energy_scale = self.energy_scale[start_channel:end_channel]
|
|
198
825
|
# select the core loss model if it exists. Otherwise, we will fit to the full spectrum.
|
|
199
|
-
if self.
|
|
826
|
+
if 'model' in self.dataset.metadata:
|
|
827
|
+
model = self.dataset.metadata['model'][start_channel:end_channel]
|
|
828
|
+
elif self.core_loss:
|
|
200
829
|
print('Core loss model found. Fitting on top of the model.')
|
|
201
830
|
model = self.dataset.metadata['edges']['model']['spectrum'][start_channel:end_channel]
|
|
202
831
|
else:
|
|
@@ -207,14 +836,14 @@ if Qt_available:
|
|
|
207
836
|
difference = np.array(spectrum[start_channel:end_channel] - model)
|
|
208
837
|
|
|
209
838
|
# find the optimum fitting parameters
|
|
210
|
-
[self.p_out, _] = scipy.optimize.leastsq(
|
|
839
|
+
[self.p_out, _] = scipy.optimize.leastsq(eels_tools.residuals_smooth, np.array(p_in), ftol=1e-3,
|
|
211
840
|
args=(energy_scale, difference, False))
|
|
212
841
|
|
|
213
842
|
# construct the fit data from the optimized parameters
|
|
214
843
|
self.peak_model = np.zeros(len(self.energy_scale))
|
|
215
844
|
self.model = np.zeros(len(self.energy_scale))
|
|
216
845
|
self.model[start_channel:end_channel] = model
|
|
217
|
-
fit =
|
|
846
|
+
fit = eels_tools.model_smooth(energy_scale, self.p_out, False)
|
|
218
847
|
self.peak_model[start_channel:end_channel] = fit
|
|
219
848
|
self.dataset.metadata['peak_fit']['edge_model'] = self.model
|
|
220
849
|
self.model = self.model + self.peak_model
|
|
@@ -246,6 +875,8 @@ if Qt_available:
|
|
|
246
875
|
spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
|
|
247
876
|
if spec_dim[1][0] > 0:
|
|
248
877
|
self.model = self.dataset.metadata['edges']['model']['spectrum']
|
|
878
|
+
elif 'model' in self.dataset.metadata:
|
|
879
|
+
self.model = self.dataset.metadata['model']
|
|
249
880
|
else:
|
|
250
881
|
self.model = np.zeros(len(spec_dim[1]))
|
|
251
882
|
|
|
@@ -270,85 +901,86 @@ if Qt_available:
|
|
|
270
901
|
onsets.append(edge['all_edges'][sym]['onset'] + edge['chemical_shift'])
|
|
271
902
|
# if 'sym' == edge['symmetry']:
|
|
272
903
|
edges.append([key, f"{element}-{sym}", onsets[-1]])
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
904
|
+
for key, peak in self.peaks['peaks'].items():
|
|
905
|
+
if key.isdigit():
|
|
906
|
+
distance = self.energy_scale[-1]
|
|
907
|
+
index = -1
|
|
908
|
+
for ii, onset in enumerate(onsets):
|
|
909
|
+
if onset < peak['position'] < onset+50:
|
|
910
|
+
if distance > np.abs(peak['position'] - onset):
|
|
911
|
+
distance = np.abs(peak['position'] - onset) # TODO: check whether absolute is good
|
|
912
|
+
distance_onset = peak['position'] - onset
|
|
913
|
+
index = ii
|
|
914
|
+
if index >= 0:
|
|
915
|
+
peak['associated_edge'] = edges[index][1] # check if more info is necessary
|
|
916
|
+
peak['distance_to_onset'] = distance_onset
|
|
286
917
|
|
|
287
918
|
def find_white_lines(self):
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if
|
|
292
|
-
if
|
|
293
|
-
if peak['
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if
|
|
304
|
-
if
|
|
305
|
-
|
|
306
|
-
f"{sym[
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
self.
|
|
327
|
-
|
|
328
|
-
self.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
919
|
+
if 'edges' in self.dataset.metadata:
|
|
920
|
+
white_lines = {}
|
|
921
|
+
for index, peak in self.peaks['peaks'].items():
|
|
922
|
+
if index.isdigit():
|
|
923
|
+
if 'associated_edge' in peak:
|
|
924
|
+
if peak['associated_edge'][-2:] in ['L3', 'L2', 'M5', 'M4']:
|
|
925
|
+
if peak['distance_to_onset'] < 10:
|
|
926
|
+
area = np.sqrt(2 * np.pi) * peak['amplitude'] * np.abs(peak['width']/np.sqrt(2 * np.log(2)))
|
|
927
|
+
if peak['associated_edge'] not in white_lines:
|
|
928
|
+
white_lines[peak['associated_edge']] = 0.
|
|
929
|
+
if area > 0:
|
|
930
|
+
white_lines[peak['associated_edge']] += area # TODO: only positive ones?
|
|
931
|
+
white_line_ratios = {}
|
|
932
|
+
white_line_sum = {}
|
|
933
|
+
for sym, area in white_lines.items():
|
|
934
|
+
if sym[-2:] in ['L2', 'M4', 'M2']:
|
|
935
|
+
if area > 0 and f"{sym[:-1]}{int(sym[-1]) + 1}" in white_lines:
|
|
936
|
+
if white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"] > 0:
|
|
937
|
+
white_line_ratios[f"{sym}/{sym[-2]}{int(sym[-1]) + 1}"] = area / white_lines[
|
|
938
|
+
f"{sym[:-1]}{int(sym[-1]) + 1}"]
|
|
939
|
+
white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] = (
|
|
940
|
+
area + white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"])
|
|
941
|
+
|
|
942
|
+
areal_density = 1.
|
|
943
|
+
if 'edges' in self.dataset.metadata:
|
|
944
|
+
for key, edge in self.dataset.metadata['edges'].items():
|
|
945
|
+
if key.isdigit():
|
|
946
|
+
if edge['element'] == sym.split('-')[0]:
|
|
947
|
+
areal_density = edge['areal_density']
|
|
948
|
+
break
|
|
949
|
+
white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] /= areal_density
|
|
950
|
+
|
|
951
|
+
self.peaks['white_lines'] = white_lines
|
|
952
|
+
self.peaks['white_line_ratios'] = white_line_ratios
|
|
953
|
+
self.peaks['white_line_sums'] = white_line_sum
|
|
954
|
+
self.ui.wl_list = []
|
|
955
|
+
self.ui.wls_list = []
|
|
956
|
+
if len(self.peaks['white_line_ratios']) > 0:
|
|
957
|
+
for key in self.peaks['white_line_ratios']:
|
|
958
|
+
self.ui.wl_list.append(key)
|
|
959
|
+
for key in self.peaks['white_line_sums']:
|
|
960
|
+
self.ui.wls_list.append(key)
|
|
961
|
+
|
|
962
|
+
self.ui.listwl.clear()
|
|
963
|
+
self.ui.listwl.addItems(self.ui.wl_list)
|
|
964
|
+
self.ui.listwl.setCurrentIndex(0)
|
|
965
|
+
self.ui.unitswl.setText(f"{self.peaks['white_line_ratios'][self.ui.wl_list[0]]:.2f}")
|
|
966
|
+
|
|
967
|
+
self.ui.listwls.clear()
|
|
968
|
+
self.ui.listwls.addItems(self.ui.wls_list)
|
|
969
|
+
self.ui.listwls.setCurrentIndex(0)
|
|
970
|
+
self.ui.unitswls.setText(f"{self.peaks['white_line_sums'][self.ui.wls_list[0]]*1e6:.4f} ppm")
|
|
971
|
+
else:
|
|
972
|
+
self.ui.wl_list.append('Ratio')
|
|
973
|
+
self.ui.wls_list.append('Sum')
|
|
974
|
+
|
|
975
|
+
self.ui.listwl.clear()
|
|
976
|
+
self.ui.listwl.addItems(self.ui.wl_list)
|
|
977
|
+
self.ui.listwl.setCurrentIndex(0)
|
|
978
|
+
self.ui.unitswl.setText('')
|
|
979
|
+
|
|
980
|
+
self.ui.listwls.clear()
|
|
981
|
+
self.ui.listwls.addItems(self.ui.wls_list)
|
|
982
|
+
self.ui.listwls.setCurrentIndex(0)
|
|
983
|
+
self.ui.unitswls.setText('')
|
|
352
984
|
|
|
353
985
|
def find_peaks(self):
|
|
354
986
|
number_of_peaks = int(str(self.ui.find_edit.displayText()).strip())
|
|
@@ -421,7 +1053,7 @@ if Qt_available:
|
|
|
421
1053
|
self.peaks['peaks'][str(peak_index)]['width'] = value
|
|
422
1054
|
|
|
423
1055
|
def on_list_enter(self):
|
|
424
|
-
|
|
1056
|
+
self.setWindowTitle(f'list {self.sender}, {self.ui.list_model}')
|
|
425
1057
|
if self.sender() == self.ui.list3:
|
|
426
1058
|
if self.ui.list3.currentText().lower() == 'add peak':
|
|
427
1059
|
peak_index = self.ui.list3.currentIndex()
|
|
@@ -439,7 +1071,16 @@ if Qt_available:
|
|
|
439
1071
|
self.ui.unitswl.setText(f"{self.peaks['white_line_ratios'][self.ui.wl_list[wl_index]]:.2f}")
|
|
440
1072
|
self.ui.listwls.setCurrentIndex(wl_index)
|
|
441
1073
|
self.ui.unitswls.setText(f"{self.peaks['white_line_sums'][self.ui.wls_list[wl_index]] * 1e6:.4f} ppm")
|
|
442
|
-
|
|
1074
|
+
elif self.sender() == self.ui.list_model:
|
|
1075
|
+
self.setWindowTitle('list 1')
|
|
1076
|
+
if self.sender().currentIndex() == 1:
|
|
1077
|
+
if 'resolution_function' in self.datasets:
|
|
1078
|
+
self.setWindowTitle('list 2')
|
|
1079
|
+
self.dataset.metadata['model'] = np.array(self.datasets['resolution_function'])
|
|
1080
|
+
else:
|
|
1081
|
+
self.ui.list_model.setCurrentIndex(0)
|
|
1082
|
+
else:
|
|
1083
|
+
self.ui.list_model.setCurrentIndex(0)
|
|
443
1084
|
def set_action(self):
|
|
444
1085
|
pass
|
|
445
1086
|
self.ui.edit1.editingFinished.connect(self.on_enter)
|
|
@@ -452,35 +1093,81 @@ if Qt_available:
|
|
|
452
1093
|
self.ui.find_button.clicked.connect(self.find_peaks)
|
|
453
1094
|
self.ui.smooth_button.clicked.connect(self.smooth)
|
|
454
1095
|
self.ui.fit_button.clicked.connect(self.fit_peaks)
|
|
455
|
-
self.ui
|
|
456
|
-
|
|
457
|
-
|
|
1096
|
+
if hasattr(self.ui, 'listwls'):
|
|
1097
|
+
self.ui.listwls.activated[str].connect(self.on_list_enter)
|
|
1098
|
+
self.ui.listwl.activated[str].connect(self.on_list_enter)
|
|
1099
|
+
else:
|
|
1100
|
+
self.ui.zl_button.clicked.connect(self.fit_zero_loss)
|
|
1101
|
+
self.ui.drude_button.clicked.connect(self.smooth)
|
|
1102
|
+
self.ui.list_model.activated[str].connect(self.on_list_enter)
|
|
1103
|
+
|
|
1104
|
+
def fit_zero_loss(self):
|
|
1105
|
+
"""get shift of spectrum form zero-loss peak position"""
|
|
1106
|
+
zero_loss_fit_width=0.3
|
|
1107
|
+
|
|
1108
|
+
energy_scale = self.dataset.energy_loss
|
|
1109
|
+
zl_dataset = self.dataset.copy()
|
|
1110
|
+
zl_dataset.title = 'resolution_function'
|
|
1111
|
+
shifts = np.zeros(self.dataset.shape[0:2])
|
|
1112
|
+
zero_p = np.zeros([self.dataset.shape[0],self.dataset.shape[1],6])
|
|
1113
|
+
fwhm_p = np.zeros(self.dataset.shape[0:2])
|
|
1114
|
+
bin_x = bin_y = 1
|
|
1115
|
+
total_spec = int(self.dataset.shape[0]/bin_x)*int(self.dataset.shape[1]/bin_y)
|
|
1116
|
+
self.ui.progress.setMaximum(total_spec)
|
|
1117
|
+
self.ui.progress.setValue(0)
|
|
1118
|
+
zero_loss_fit_width=0.3
|
|
1119
|
+
ind = 0
|
|
1120
|
+
for x in range(self.dataset.shape[0]):
|
|
1121
|
+
for y in range(self.dataset.shape[1]):
|
|
1122
|
+
ind += 1
|
|
1123
|
+
self.ui.progress.setValue(ind)
|
|
1124
|
+
spectrum = self.dataset[x, y, :]
|
|
1125
|
+
fwhm, delta_e = eels_tools.fix_energy_scale(spectrum, energy_scale)
|
|
1126
|
+
z_loss, p_zl = eels_tools.resolution_function(energy_scale - delta_e, spectrum, zero_loss_fit_width)
|
|
1127
|
+
fwhm2, delta_e2 = eels_tools.fix_energy_scale(z_loss, energy_scale - delta_e)
|
|
1128
|
+
shifts[x, y] = delta_e + delta_e2
|
|
1129
|
+
zero_p[x,y,:] = p_zl
|
|
1130
|
+
zl_dataset[x,y] = z_loss
|
|
1131
|
+
fwhm_p[x,y] = fwhm2
|
|
1132
|
+
|
|
1133
|
+
zl_dataset.metadata['zero_loss'] = {'parameter': zero_p,
|
|
1134
|
+
'shifts': shifts,
|
|
1135
|
+
'fwhm': fwhm_p}
|
|
1136
|
+
self.dataset.metadata['zero_loss'] = {'parameter': zero_p,
|
|
1137
|
+
'shifts': shifts,
|
|
1138
|
+
'fwhm': fwhm_p}
|
|
1139
|
+
|
|
1140
|
+
self.datasets['resolution_function'] = zl_dataset
|
|
1141
|
+
self.update()
|
|
1142
|
+
self.plot()
|
|
1143
|
+
|
|
1144
|
+
|
|
458
1145
|
|
|
459
|
-
|
|
460
|
-
|
|
1146
|
+
def smooth(dataset, iterations, advanced_present):
|
|
1147
|
+
"""Gaussian mixture model (non-Bayesian)
|
|
461
1148
|
|
|
462
|
-
|
|
463
|
-
|
|
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.
|
|
464
1151
|
|
|
465
|
-
|
|
1152
|
+
"""
|
|
466
1153
|
|
|
467
|
-
|
|
468
|
-
|
|
1154
|
+
# TODO: add sensitivity to dialog and the two functions below
|
|
1155
|
+
peaks = dataset.metadata['peak_fit']
|
|
469
1156
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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]
|
|
476
1163
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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]
|
|
483
1170
|
|
|
484
|
-
|
|
1171
|
+
number_of_peaks = np.searchsorted(area * -1, -np.average(area))
|
|
485
1172
|
|
|
486
|
-
|
|
1173
|
+
return peak_model, peak_out_list, number_of_peaks
|