pyTEMlib 0.2025.4.2__py3-none-any.whl → 0.2025.9.1__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.
- build/lib/pyTEMlib/__init__.py +33 -0
- build/lib/pyTEMlib/animation.py +640 -0
- build/lib/pyTEMlib/atom_tools.py +238 -0
- build/lib/pyTEMlib/config_dir.py +31 -0
- build/lib/pyTEMlib/crystal_tools.py +1219 -0
- build/lib/pyTEMlib/diffraction_plot.py +756 -0
- build/lib/pyTEMlib/dynamic_scattering.py +293 -0
- build/lib/pyTEMlib/eds_tools.py +826 -0
- build/lib/pyTEMlib/eds_xsections.py +432 -0
- build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
- build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
- build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- build/lib/pyTEMlib/file_reader.py +274 -0
- build/lib/pyTEMlib/file_tools.py +811 -0
- build/lib/pyTEMlib/get_bote_salvat.py +69 -0
- build/lib/pyTEMlib/graph_tools.py +1153 -0
- build/lib/pyTEMlib/graph_viz.py +599 -0
- build/lib/pyTEMlib/image/__init__.py +37 -0
- build/lib/pyTEMlib/image/image_atoms.py +270 -0
- build/lib/pyTEMlib/image/image_clean.py +197 -0
- build/lib/pyTEMlib/image/image_distortion.py +299 -0
- build/lib/pyTEMlib/image/image_fft.py +277 -0
- build/lib/pyTEMlib/image/image_graph.py +926 -0
- build/lib/pyTEMlib/image/image_registration.py +316 -0
- build/lib/pyTEMlib/image/image_utilities.py +309 -0
- build/lib/pyTEMlib/image/image_window.py +421 -0
- build/lib/pyTEMlib/image_tools.py +699 -0
- build/lib/pyTEMlib/interactive_image.py +1 -0
- build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
- build/lib/pyTEMlib/microscope.py +61 -0
- build/lib/pyTEMlib/probe_tools.py +906 -0
- build/lib/pyTEMlib/sidpy_tools.py +153 -0
- build/lib/pyTEMlib/simulation_tools.py +104 -0
- build/lib/pyTEMlib/test.py +437 -0
- build/lib/pyTEMlib/utilities.py +314 -0
- build/lib/pyTEMlib/version.py +5 -0
- build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
- pyTEMlib/__init__.py +25 -3
- pyTEMlib/animation.py +31 -22
- pyTEMlib/atom_tools.py +29 -34
- pyTEMlib/config_dir.py +2 -28
- pyTEMlib/crystal_tools.py +129 -165
- pyTEMlib/eds_tools.py +559 -342
- pyTEMlib/eds_xsections.py +432 -0
- pyTEMlib/eels_tools/__init__.py +44 -0
- pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- pyTEMlib/eels_tools/eels_database.py +134 -0
- pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- pyTEMlib/file_reader.py +274 -0
- pyTEMlib/file_tools.py +260 -1130
- pyTEMlib/get_bote_salvat.py +69 -0
- pyTEMlib/graph_tools.py +101 -174
- pyTEMlib/graph_viz.py +150 -0
- pyTEMlib/image/__init__.py +37 -0
- pyTEMlib/image/image_atoms.py +270 -0
- pyTEMlib/image/image_clean.py +197 -0
- pyTEMlib/image/image_distortion.py +299 -0
- pyTEMlib/image/image_fft.py +277 -0
- pyTEMlib/image/image_graph.py +926 -0
- pyTEMlib/image/image_registration.py +316 -0
- pyTEMlib/image/image_utilities.py +309 -0
- pyTEMlib/image/image_window.py +421 -0
- pyTEMlib/image_tools.py +154 -928
- pyTEMlib/kinematic_scattering.py +1 -1
- pyTEMlib/probe_tools.py +1 -1
- pyTEMlib/test.py +437 -0
- pyTEMlib/utilities.py +314 -0
- pyTEMlib/version.py +2 -3
- pyTEMlib/xrpa_x_sections.py +14 -10
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
- pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
- pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
- pyTEMlib/core_loss_widget.py +0 -721
- pyTEMlib/eels_dialog.py +0 -754
- pyTEMlib/eels_dialog_utilities.py +0 -1199
- pyTEMlib/eels_tools.py +0 -2359
- pyTEMlib/file_tools_qt.py +0 -193
- pyTEMlib/image_dialog.py +0 -158
- pyTEMlib/image_dlg.py +0 -146
- pyTEMlib/info_widget.py +0 -1086
- pyTEMlib/info_widget3.py +0 -1120
- pyTEMlib/low_loss_widget.py +0 -479
- pyTEMlib/peak_dialog.py +0 -1129
- pyTEMlib/peak_dlg.py +0 -286
- pytemlib-0.2025.4.2.dist-info/RECORD +0 -38
- pytemlib-0.2025.4.2.dist-info/top_level.txt +0 -1
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
- {pytemlib-0.2025.4.2.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1199 +0,0 @@
|
|
|
1
|
-
""" Interactive routines for EELS analysis
|
|
2
|
-
|
|
3
|
-
this file provides additional dialogs for EELS quantification
|
|
4
|
-
|
|
5
|
-
Author: Gerd Duscher
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import numpy as np
|
|
9
|
-
|
|
10
|
-
import sidpy
|
|
11
|
-
import matplotlib
|
|
12
|
-
import matplotlib.pyplot as plt
|
|
13
|
-
|
|
14
|
-
import matplotlib.patches as patches
|
|
15
|
-
from matplotlib.widgets import RectangleSelector, SpanSelector
|
|
16
|
-
|
|
17
|
-
import h5py # TODO: needs to go
|
|
18
|
-
|
|
19
|
-
from IPython.display import display
|
|
20
|
-
import ipywidgets
|
|
21
|
-
|
|
22
|
-
from pyTEMlib import eels_tools as eels
|
|
23
|
-
from pyTEMlib import file_tools as ft
|
|
24
|
-
|
|
25
|
-
major_edges = ['K1', 'L3', 'M5', 'N5']
|
|
26
|
-
all_edges = ['K1', 'L1', 'L2', 'L3', 'M1', 'M2', 'M3', 'M4', 'M5', 'N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'O1', 'O2',
|
|
27
|
-
'O3', 'O4', 'O5', 'O6', 'O7', 'P1', 'P2', 'P3']
|
|
28
|
-
first_close_edges = ['K1', 'L3', 'M5', 'M3', 'N5', 'N3']
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class RegionSelector(object):
|
|
33
|
-
"""Selects fitting region and the regions that are excluded for each edge.
|
|
34
|
-
|
|
35
|
-
Select a region with a spanSelector and then type 'a' for all the fitting region or a number for the edge
|
|
36
|
-
you want to define the region excluded from the fit (solid state effects).
|
|
37
|
-
|
|
38
|
-
see Chapter4 'CH4-Working_with_X-Sections,ipynb' notebook
|
|
39
|
-
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(self, ax):
|
|
43
|
-
self.ax = ax
|
|
44
|
-
self.regions = {}
|
|
45
|
-
self.rect = None
|
|
46
|
-
self.xmin = 0
|
|
47
|
-
self.width = 0
|
|
48
|
-
|
|
49
|
-
self.span = SpanSelector(ax, self.on_select1,
|
|
50
|
-
direction="horizontal",
|
|
51
|
-
interactive=True,
|
|
52
|
-
props=dict(facecolor='blue', alpha=0.2))
|
|
53
|
-
self.cid = ax.figure.canvas.mpl_connect('key_press_event', self.click)
|
|
54
|
-
self.draw = ax.figure.canvas.mpl_connect('draw_event', self.onresize)
|
|
55
|
-
|
|
56
|
-
def on_select1(self, xmin, xmax):
|
|
57
|
-
self.xmin = xmin
|
|
58
|
-
self.width = xmax - xmin
|
|
59
|
-
|
|
60
|
-
def onresize(self, event):
|
|
61
|
-
self.update()
|
|
62
|
-
|
|
63
|
-
def delete_region(self, key):
|
|
64
|
-
if key in self.regions:
|
|
65
|
-
if 'Rect' in self.regions[key]:
|
|
66
|
-
self.regions[key]['Rect'].remove()
|
|
67
|
-
self.regions[key]['Text'].remove()
|
|
68
|
-
del (self.regions[key])
|
|
69
|
-
|
|
70
|
-
def update(self):
|
|
71
|
-
|
|
72
|
-
y_min, y_max = self.ax.get_ylim()
|
|
73
|
-
for key in self.regions:
|
|
74
|
-
if 'Rect' in self.regions[key]:
|
|
75
|
-
self.regions[key]['Rect'].remove()
|
|
76
|
-
self.regions[key]['Text'].remove()
|
|
77
|
-
|
|
78
|
-
xmin = self.regions[key]['xmin']
|
|
79
|
-
width = self.regions[key]['width']
|
|
80
|
-
height = y_max - y_min
|
|
81
|
-
alpha = self.regions[key]['alpha']
|
|
82
|
-
color = self.regions[key]['color']
|
|
83
|
-
self.regions[key]['Rect'] = patches.Rectangle((xmin, y_min), width, height,
|
|
84
|
-
edgecolor=color, alpha=alpha, facecolor=color)
|
|
85
|
-
self.ax.add_patch(self.regions[key]['Rect'])
|
|
86
|
-
|
|
87
|
-
self.regions[key]['Text'] = self.ax.text(xmin, y_max, self.regions[key]['text'], verticalalignment='top')
|
|
88
|
-
|
|
89
|
-
def click(self, event):
|
|
90
|
-
if str(event.key) in ['1', '2', '3', '4', '5', '6']:
|
|
91
|
-
key = str(event.key)
|
|
92
|
-
text = 'exclude \nedge ' + key
|
|
93
|
-
alpha = 0.5
|
|
94
|
-
color = 'red'
|
|
95
|
-
elif str(event.key) in ['a', 'A', 'B', 'b', 'f', 'F']:
|
|
96
|
-
key = '0'
|
|
97
|
-
color = 'blue'
|
|
98
|
-
alpha = 0.2
|
|
99
|
-
text = 'fit region'
|
|
100
|
-
else:
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
if key not in self.regions:
|
|
104
|
-
self.regions[key] = {}
|
|
105
|
-
|
|
106
|
-
self.regions[key]['xmin'] = self.xmin
|
|
107
|
-
self.regions[key]['width'] = self.width
|
|
108
|
-
self.regions[key]['color'] = color
|
|
109
|
-
self.regions[key]['alpha'] = alpha
|
|
110
|
-
self.regions[key]['text'] = text
|
|
111
|
-
|
|
112
|
-
self.update()
|
|
113
|
-
|
|
114
|
-
def set_regions(self, region, start_x, width):
|
|
115
|
-
key = ''
|
|
116
|
-
if 'fit' in str(region):
|
|
117
|
-
key = '0'
|
|
118
|
-
if region in ['0', '1', '2', '3', '4', '5', '6']:
|
|
119
|
-
key = region
|
|
120
|
-
if region in [0, 1, 2, 3, 4, 5, 6]:
|
|
121
|
-
key = str(region)
|
|
122
|
-
|
|
123
|
-
if key not in self.regions:
|
|
124
|
-
self.regions[key] = {}
|
|
125
|
-
if key in ['1', '2', '3', '4', '5', '6']:
|
|
126
|
-
self.regions[key]['text'] = 'exclude \nedge ' + key
|
|
127
|
-
self.regions[key]['alpha'] = 0.5
|
|
128
|
-
self.regions[key]['color'] = 'red'
|
|
129
|
-
elif key == '0':
|
|
130
|
-
self.regions[key]['text'] = 'fit region'
|
|
131
|
-
self.regions[key]['alpha'] = 0.2
|
|
132
|
-
self.regions[key]['color'] = 'blue'
|
|
133
|
-
|
|
134
|
-
self.regions[key]['xmin'] = start_x
|
|
135
|
-
self.regions[key]['width'] = width
|
|
136
|
-
|
|
137
|
-
self.update()
|
|
138
|
-
|
|
139
|
-
def get_regions(self):
|
|
140
|
-
tags = {}
|
|
141
|
-
for key in self.regions:
|
|
142
|
-
if key == '0':
|
|
143
|
-
area = 'fit_area'
|
|
144
|
-
else:
|
|
145
|
-
area = key
|
|
146
|
-
tags[area] = {}
|
|
147
|
-
tags[area]['start_x'] = self.regions[key]['xmin']
|
|
148
|
-
tags[area]['width_x'] = self.regions[key]['width']
|
|
149
|
-
|
|
150
|
-
return tags
|
|
151
|
-
|
|
152
|
-
def disconnect(self):
|
|
153
|
-
for key in self.regions:
|
|
154
|
-
if 'Rect' in self.regions[key]:
|
|
155
|
-
self.regions[key]['Rect'].remove()
|
|
156
|
-
self.regions[key]['Text'].remove()
|
|
157
|
-
del self.span
|
|
158
|
-
self.ax.figure.canvas.mpl_disconnect(self.cid)
|
|
159
|
-
# self.ax.figure.canvas.mpl_disconnect(self.draw)
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class RangeSelector(RectangleSelector):
|
|
164
|
-
"""Select ranges of edge fitting interactively"""
|
|
165
|
-
def __init__(self, ax, on_select):
|
|
166
|
-
drawtype = 'box'
|
|
167
|
-
spancoords = 'data'
|
|
168
|
-
rectprops = dict(facecolor="blue", edgecolor="black", alpha=0.2, fill=True)
|
|
169
|
-
|
|
170
|
-
super().__init__(ax, on_select, drawtype=drawtype,
|
|
171
|
-
minspanx=0, minspany=0, useblit=False,
|
|
172
|
-
lineprops=None, rectprops=rectprops, spancoords=spancoords,
|
|
173
|
-
button=None, maxdist=10, marker_props=None,
|
|
174
|
-
interactive=True, state_modifier_keys=None)
|
|
175
|
-
|
|
176
|
-
self.artists = [self.to_draw, self._center_handle.artist,
|
|
177
|
-
self._edge_handles.artist]
|
|
178
|
-
|
|
179
|
-
def draw_shape(self, extents):
|
|
180
|
-
x0, x1, y0, y1 = extents
|
|
181
|
-
xmin, xmax = sorted([x0, x1])
|
|
182
|
-
# ymin, ymax = sorted([y0, y1])
|
|
183
|
-
xlim = sorted(self.ax.get_xlim())
|
|
184
|
-
ylim = sorted(self.ax.get_ylim())
|
|
185
|
-
|
|
186
|
-
xmin = max(xlim[0], xmin)
|
|
187
|
-
ymin = ylim[0]
|
|
188
|
-
xmax = min(xmax, xlim[1])
|
|
189
|
-
ymax = ylim[1]
|
|
190
|
-
|
|
191
|
-
self.to_draw.set_x(xmin)
|
|
192
|
-
self.to_draw.set_y(ymin)
|
|
193
|
-
self.to_draw.set_width(xmax - xmin)
|
|
194
|
-
self.to_draw.set_height(ymax - ymin)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def get_likely_edges(energy_scale):
|
|
198
|
-
"""get likely ionization edges within energy_scale"""
|
|
199
|
-
x_sections = eels.get_x_sections()
|
|
200
|
-
# print(energy_scale)
|
|
201
|
-
energy_origin = energy_scale[0]
|
|
202
|
-
energy_window = energy_scale[-1] - energy_origin
|
|
203
|
-
selected_edges_unsorted = {}
|
|
204
|
-
likely_edges = []
|
|
205
|
-
selected_elements = []
|
|
206
|
-
for element in range(1, 83):
|
|
207
|
-
# print(element)
|
|
208
|
-
element_z = str(eels.get_z(element))
|
|
209
|
-
|
|
210
|
-
for key in x_sections[element_z]:
|
|
211
|
-
if key in all_edges:
|
|
212
|
-
onset = x_sections[element_z][key]['onset']
|
|
213
|
-
if onset > energy_origin:
|
|
214
|
-
if onset - energy_origin < energy_window:
|
|
215
|
-
if element not in selected_edges_unsorted:
|
|
216
|
-
selected_edges_unsorted[element] = {}
|
|
217
|
-
# print(element, x_sections[element]['name'], key, x_sections[element][key]['onset'])
|
|
218
|
-
# text = f"\n {x_sections[element_z]['name']:2s}-{key}: " \
|
|
219
|
-
# f"{x_sections[element_z][key]['onset']:8.1f} eV "
|
|
220
|
-
# print(text)
|
|
221
|
-
|
|
222
|
-
selected_edges_unsorted[element][key] = {}
|
|
223
|
-
selected_edges_unsorted[element][key]['onset'] = x_sections[element_z][key]['onset']
|
|
224
|
-
|
|
225
|
-
if key in major_edges:
|
|
226
|
-
selected_edges_unsorted[element][key]['intensity'] = 'major'
|
|
227
|
-
selected_elements.append(x_sections[element_z]['name'])
|
|
228
|
-
else:
|
|
229
|
-
selected_edges_unsorted[element][key]['intensity'] = 'minor'
|
|
230
|
-
|
|
231
|
-
if element in selected_edges_unsorted:
|
|
232
|
-
for key in selected_edges_unsorted[element]:
|
|
233
|
-
if selected_edges_unsorted[element][key]['intensity'] == 'major':
|
|
234
|
-
likely_edges.append(x_sections[str(element)]['name']) # = {'z':element, 'symmetry': key}
|
|
235
|
-
|
|
236
|
-
return likely_edges
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
class SpectrumPlot(sidpy.viz.dataset_viz.CurveVisualizer):
|
|
240
|
-
def __init__(self, dset, spectrum_number=0, figure=None, **kwargs):
|
|
241
|
-
with plt.ioff():
|
|
242
|
-
self.figure = plt.figure()
|
|
243
|
-
self.figure.canvas.toolbar_position = 'right'
|
|
244
|
-
self.figure.canvas.toolbar_visible = True
|
|
245
|
-
|
|
246
|
-
super().__init__(dset, spectrum_number=spectrum_number, figure=self.figure, **kwargs)
|
|
247
|
-
try:
|
|
248
|
-
self.dataset = self.dset
|
|
249
|
-
except:
|
|
250
|
-
pass
|
|
251
|
-
self.start_cursor = ipywidgets.FloatText(value=0, description='Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
|
|
252
|
-
self.end_cursor = ipywidgets.FloatText(value=0, description='End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
|
|
253
|
-
self.panel = ipywidgets.VBox([ipywidgets.HBox([ipywidgets.Label('',layout=ipywidgets.Layout(width='100px')), ipywidgets.Label('Cursor:'),
|
|
254
|
-
self.start_cursor,ipywidgets.Label('eV'),
|
|
255
|
-
self.end_cursor, ipywidgets.Label('eV')]),
|
|
256
|
-
self.figure.canvas])
|
|
257
|
-
|
|
258
|
-
self.selector = matplotlib.widgets.SpanSelector(self.axis, self.line_select_callback,
|
|
259
|
-
direction="horizontal",
|
|
260
|
-
interactive=True,
|
|
261
|
-
props=dict(facecolor='blue', alpha=0.2))
|
|
262
|
-
|
|
263
|
-
def line_select_callback(self, x_min, x_max):
|
|
264
|
-
self.start_cursor.value = np.round(x_min, 3)
|
|
265
|
-
self.end_cursor.value = np.round(x_max, 3)
|
|
266
|
-
self.start_channel = np.searchsorted(self.dataset.energy_loss, self.start_cursor.value)
|
|
267
|
-
self.end_channel = np.searchsorted(self.dataset.energy_loss, self.end_cursor.value)
|
|
268
|
-
|
|
269
|
-
def plot(self, scale=True, additional_spectra=None):
|
|
270
|
-
self.dataset = self.dset
|
|
271
|
-
self.energy_scale = self.dataset.energy_loss.values
|
|
272
|
-
x_limit = self.axis.get_xlim()
|
|
273
|
-
y_limit = np.array(self.axis.get_ylim())
|
|
274
|
-
|
|
275
|
-
self.axis.clear()
|
|
276
|
-
|
|
277
|
-
self.axis.plot(self.energy_scale, self.dataset*self.y_scale, label='spectrum')
|
|
278
|
-
|
|
279
|
-
if additional_spectra is not None:
|
|
280
|
-
if isinstance(additional_spectra, dict):
|
|
281
|
-
for key, spectrum in additional_spectra.items():
|
|
282
|
-
self.axis.plot(self.energy_scale, spectrum*self.y_scale, label=key)
|
|
283
|
-
|
|
284
|
-
self.axis.set_xlabel(self.dataset.labels[0])
|
|
285
|
-
self.axis.set_ylabel(self.dataset.data_descriptor)
|
|
286
|
-
self.axis.ticklabel_format(style='sci', scilimits=(-2, 3))
|
|
287
|
-
if scale:
|
|
288
|
-
self.axis.set_ylim(np.array(y_limit)*self.change_y_scale)
|
|
289
|
-
|
|
290
|
-
self.change_y_scale = 1.0
|
|
291
|
-
if self.y_scale != 1.:
|
|
292
|
-
self.axis.set_ylabel('scattering probability (ppm/eV)')
|
|
293
|
-
self.selector = matplotlib.widgets.SpanSelector(self.axis, self.line_select_callback,
|
|
294
|
-
direction="horizontal",
|
|
295
|
-
interactive=True,
|
|
296
|
-
props=dict(facecolor='blue', alpha=0.2))
|
|
297
|
-
self.axis.legend()
|
|
298
|
-
self.figure.canvas.draw_idle()
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
class SIPlot(sidpy.viz.dataset_viz.SpectralImageVisualizerBase):
|
|
302
|
-
def __init__(self, dset, figure=None, horizontal=True, **kwargs):
|
|
303
|
-
if figure is None:
|
|
304
|
-
with plt.ioff():
|
|
305
|
-
self.figure = plt.figure()
|
|
306
|
-
else:
|
|
307
|
-
self.figure = figure
|
|
308
|
-
self.figure.canvas.toolbar_position = 'right'
|
|
309
|
-
self.figure.canvas.toolbar_visible = True
|
|
310
|
-
self.dset = dset
|
|
311
|
-
super().__init__(self.dset, figure=self.figure, horizontal=horizontal, **kwargs)
|
|
312
|
-
|
|
313
|
-
self.start_cursor = ipywidgets.FloatText(value=0, description='Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
|
|
314
|
-
self.end_cursor = ipywidgets.FloatText(value=0, description='End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
|
|
315
|
-
self.panel = ipywidgets.VBox([ipywidgets.HBox([ipywidgets.Label('',layout=ipywidgets.Layout(width='100px')), ipywidgets.Label('Cursor:'),
|
|
316
|
-
self.start_cursor,ipywidgets.Label('eV'),
|
|
317
|
-
self.end_cursor, ipywidgets.Label('eV')]),
|
|
318
|
-
self.figure.canvas])
|
|
319
|
-
self.axis = self.axes[-1]
|
|
320
|
-
self.selector = matplotlib.widgets.SpanSelector(self.axis, self.line_select_callback,
|
|
321
|
-
direction="horizontal",
|
|
322
|
-
interactive=True,
|
|
323
|
-
props=dict(facecolor='blue', alpha=0.2))
|
|
324
|
-
|
|
325
|
-
def line_select_callback(self, x_min, x_max):
|
|
326
|
-
self.start_cursor.value = np.round(x_min, 3)
|
|
327
|
-
self.end_cursor.value = np.round(x_max, 3)
|
|
328
|
-
self.start_channel = np.searchsorted(self.dataset.energy_loss, self.start_cursor.value)
|
|
329
|
-
self.end_channel = np.searchsorted(self.dataset.energy_loss, self.end_cursor.value)
|
|
330
|
-
|
|
331
|
-
def plot(self, scale=True, additional_spectra=None):
|
|
332
|
-
|
|
333
|
-
xlim = self.axes[1].get_xlim()
|
|
334
|
-
ylim = self.axes[1].get_ylim()
|
|
335
|
-
self.axes[1].clear()
|
|
336
|
-
self.get_spectrum()
|
|
337
|
-
if len(self.energy_scale)!=self.spectrum.shape[0]:
|
|
338
|
-
self.spectrum = self.spectrum.T
|
|
339
|
-
self.axes[1].plot(self.energy_scale, self.spectrum.compute(), label='experiment')
|
|
340
|
-
if additional_spectra is not None:
|
|
341
|
-
if isinstance(additional_spectra, dict):
|
|
342
|
-
for key, spectrum in additional_spectra.items():
|
|
343
|
-
self.axes[1].plot(self.energy_scale, spectrum, label=key)
|
|
344
|
-
|
|
345
|
-
if self.set_title:
|
|
346
|
-
self.axes[1].set_title('spectrum {}, {}'.format(self.x, self.y))
|
|
347
|
-
self.fig.tight_layout()
|
|
348
|
-
self.selector = matplotlib.widgets.SpanSelector(self.axes[1], self.line_select_callback,
|
|
349
|
-
direction="horizontal",
|
|
350
|
-
interactive=True,
|
|
351
|
-
props=dict(facecolor='blue', alpha=0.2))
|
|
352
|
-
|
|
353
|
-
self.axes[1].set_xlim(xlim)
|
|
354
|
-
self.axes[1].set_ylim(ylim)
|
|
355
|
-
self.axes[1].set_xlabel(self.xlabel)
|
|
356
|
-
self.axes[1].set_ylabel(self.ylabel)
|
|
357
|
-
|
|
358
|
-
self.fig.canvas.draw_idle()
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def get_periodic_table_widget(energy_scale=None):
|
|
362
|
-
|
|
363
|
-
if energy_scale is None:
|
|
364
|
-
energy_scale = [100., 150., 200.]
|
|
365
|
-
|
|
366
|
-
likely_edges = get_likely_edges(energy_scale)
|
|
367
|
-
|
|
368
|
-
pt_info = get_periodic_table_info()
|
|
369
|
-
table = ipywidgets.GridspecLayout(10, 18,width= '60%', grid_gap="0px")
|
|
370
|
-
for symbol, parameter in pt_info.items():
|
|
371
|
-
#print(parameter['PT_row'], parameter['PT_col'])
|
|
372
|
-
if parameter['PT_row'] > 7:
|
|
373
|
-
color = 'warning'
|
|
374
|
-
elif '*' in symbol:
|
|
375
|
-
color = 'warning'
|
|
376
|
-
else:
|
|
377
|
-
if symbol in likely_edges:
|
|
378
|
-
color = 'primary'
|
|
379
|
-
else:
|
|
380
|
-
color = 'info'
|
|
381
|
-
table[parameter['PT_row'], parameter['PT_col']] = ipywidgets.ToggleButton(description=symbol,
|
|
382
|
-
value=False,
|
|
383
|
-
button_style=color,
|
|
384
|
-
layout=ipywidgets.Layout(width='auto'),
|
|
385
|
-
style={"button_width": "30px"})
|
|
386
|
-
return table
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
class PeriodicTableWidget(object):
|
|
390
|
-
""" ipywidget to get a selection of elements.
|
|
391
|
-
|
|
392
|
-
Elements that are not having a valid cross-sections are disabled.
|
|
393
|
-
|
|
394
|
-
Parameters
|
|
395
|
-
----------
|
|
396
|
-
initial_elements: list of str
|
|
397
|
-
the elements that are already selected
|
|
398
|
-
energy_scale: list or numpy array
|
|
399
|
-
energy-scale of spectrum/spectra to determine likely edges
|
|
400
|
-
|
|
401
|
-
Returns
|
|
402
|
-
-------
|
|
403
|
-
list of strings: elements.
|
|
404
|
-
use get_output() function
|
|
405
|
-
"""
|
|
406
|
-
|
|
407
|
-
def __init__(self, initial_elements=None, energy_scale=None):
|
|
408
|
-
|
|
409
|
-
if initial_elements is None:
|
|
410
|
-
initial_elements = [' ']
|
|
411
|
-
self.elements_selected = initial_elements
|
|
412
|
-
if energy_scale is None:
|
|
413
|
-
energy_scale = [100., 150., 200.]
|
|
414
|
-
self._output = []
|
|
415
|
-
self.energy_scale = np.array(energy_scale)
|
|
416
|
-
self.pt_info = get_periodic_table_info()
|
|
417
|
-
|
|
418
|
-
self.periodic_table = get_periodic_table_widget(energy_scale)
|
|
419
|
-
self.update()
|
|
420
|
-
|
|
421
|
-
def get_output(self):
|
|
422
|
-
self.elements_selected = []
|
|
423
|
-
for symbol, parameter in self.pt_info.items():
|
|
424
|
-
if self.periodic_table[parameter['PT_row'], parameter['PT_col']].value == True: # [parameter['PT_row'], parameter['PT_col']]
|
|
425
|
-
self.elements_selected.append(self.periodic_table[parameter['PT_row'], parameter['PT_col']].description)
|
|
426
|
-
return self.elements_selected
|
|
427
|
-
|
|
428
|
-
def update(self):
|
|
429
|
-
for symbol, parameter in self.pt_info.items():
|
|
430
|
-
if str(self.periodic_table[parameter['PT_row'], parameter['PT_col']].description) in list(self.elements_selected):
|
|
431
|
-
self.periodic_table[parameter['PT_row'], parameter['PT_col']].value = True
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
def get_periodic_table_info():
|
|
437
|
-
"""Info for periodic table dialog"""
|
|
438
|
-
pt_info = \
|
|
439
|
-
{'H': {'PT_row': 0, 'PT_col': 0, 'Z': 0},
|
|
440
|
-
'He': {'PT_row': 0, 'PT_col': 17, 'Z': 2}, 'Li': {'PT_row': 1, 'PT_col': 0, 'Z': 3},
|
|
441
|
-
'Be': {'PT_row': 1, 'PT_col': 1, 'Z': 4}, 'B': {'PT_row': 1, 'PT_col': 12, 'Z': 5},
|
|
442
|
-
'C': {'PT_row': 1, 'PT_col': 13, 'Z': 6}, 'N': {'PT_row': 1, 'PT_col': 14, 'Z': 7},
|
|
443
|
-
'O': {'PT_row': 1, 'PT_col': 15, 'Z': 8}, 'F': {'PT_row': 1, 'PT_col': 16, 'Z': 9},
|
|
444
|
-
'Ne': {'PT_row': 1, 'PT_col': 17, 'Z': 10}, 'Na': {'PT_row': 2, 'PT_col': 0, 'Z': 11},
|
|
445
|
-
'Mg': {'PT_row': 2, 'PT_col': 1, 'Z': 12}, 'Al': {'PT_row': 2, 'PT_col': 12, 'Z': 13},
|
|
446
|
-
'Si': {'PT_row': 2, 'PT_col': 13, 'Z': 14}, 'P': {'PT_row': 2, 'PT_col': 14, 'Z': 15},
|
|
447
|
-
'S': {'PT_row': 2, 'PT_col': 15, 'Z': 16}, 'Cl': {'PT_row': 2, 'PT_col': 16, 'Z': 17},
|
|
448
|
-
'Ar': {'PT_row': 2, 'PT_col': 17, 'Z': 18}, 'K': {'PT_row': 3, 'PT_col': 0, 'Z': 19},
|
|
449
|
-
'Ca': {'PT_row': 3, 'PT_col': 1, 'Z': 20}, 'Sc': {'PT_row': 3, 'PT_col': 2, 'Z': 21},
|
|
450
|
-
'Ti': {'PT_row': 3, 'PT_col': 3, 'Z': 22}, 'V ': {'PT_row': 3, 'PT_col': 4, 'Z': 23},
|
|
451
|
-
'Cr': {'PT_row': 3, 'PT_col': 5, 'Z': 24}, 'Mn': {'PT_row': 3, 'PT_col': 6, 'Z': 25},
|
|
452
|
-
'Fe': {'PT_row': 3, 'PT_col': 7, 'Z': 26}, 'Co': {'PT_row': 3, 'PT_col': 8, 'Z': 27},
|
|
453
|
-
'Ni': {'PT_row': 3, 'PT_col': 9, 'Z': 28}, 'Cu': {'PT_row': 3, 'PT_col': 10, 'Z': 29},
|
|
454
|
-
'Zn': {'PT_row': 3, 'PT_col': 11, 'Z': 30}, 'Ga': {'PT_row': 3, 'PT_col': 12, 'Z': 31},
|
|
455
|
-
'Ge': {'PT_row': 3, 'PT_col': 13, 'Z': 32}, 'As': {'PT_row': 3, 'PT_col': 14, 'Z': 33},
|
|
456
|
-
'Se': {'PT_row': 3, 'PT_col': 15, 'Z': 34}, 'Br': {'PT_row': 3, 'PT_col': 16, 'Z': 35},
|
|
457
|
-
'Kr': {'PT_row': 3, 'PT_col': 17, 'Z': 36}, 'Rb': {'PT_row': 4, 'PT_col': 0, 'Z': 37},
|
|
458
|
-
'Sr': {'PT_row': 4, 'PT_col': 1, 'Z': 38}, 'Y': {'PT_row': 4, 'PT_col': 2, 'Z': 39},
|
|
459
|
-
'Zr': {'PT_row': 4, 'PT_col': 3, 'Z': 40}, 'Nb': {'PT_row': 4, 'PT_col': 4, 'Z': 41},
|
|
460
|
-
'Mo': {'PT_row': 4, 'PT_col': 5, 'Z': 42}, 'Tc': {'PT_row': 4, 'PT_col': 6, 'Z': 43},
|
|
461
|
-
'Ru': {'PT_row': 4, 'PT_col': 7, 'Z': 44}, 'Rh': {'PT_row': 4, 'PT_col': 8, 'Z': 45},
|
|
462
|
-
'Pd': {'PT_row': 4, 'PT_col': 9, 'Z': 46}, 'Ag': {'PT_row': 4, 'PT_col': 10, 'Z': 47},
|
|
463
|
-
'Cd': {'PT_row': 4, 'PT_col': 11, 'Z': 48}, 'In': {'PT_row': 4, 'PT_col': 12, 'Z': 49},
|
|
464
|
-
'Sn': {'PT_row': 4, 'PT_col': 13, 'Z': 50}, 'Sb': {'PT_row': 4, 'PT_col': 14, 'Z': 51},
|
|
465
|
-
'Te': {'PT_row': 4, 'PT_col': 15, 'Z': 52}, 'I': {'PT_row': 4, 'PT_col': 16, 'Z': 53},
|
|
466
|
-
'Xe': {'PT_row': 4, 'PT_col': 17, 'Z': 54}, 'Cs': {'PT_row': 5, 'PT_col': 0, 'Z': 55},
|
|
467
|
-
'Ba': {'PT_row': 5, 'PT_col': 1, 'Z': 56}, 'Hf': {'PT_row': 5, 'PT_col': 3, 'Z': 72},
|
|
468
|
-
'Ta': {'PT_row': 5, 'PT_col': 4, 'Z': 73}, 'W': {'PT_row': 5, 'PT_col': 5, 'Z': 74},
|
|
469
|
-
'Re': {'PT_row': 5, 'PT_col': 6, 'Z': 75}, 'Os': {'PT_row': 5, 'PT_col': 7, 'Z': 76},
|
|
470
|
-
'Ir': {'PT_row': 5, 'PT_col': 8, 'Z': 77}, 'Pt': {'PT_row': 5, 'PT_col': 9, 'Z': 78},
|
|
471
|
-
'Au': {'PT_row': 5, 'PT_col': 10, 'Z': 79}, 'Hg': {'PT_row': 5, 'PT_col': 11, 'Z': 80},
|
|
472
|
-
'Pb': {'PT_row': 5, 'PT_col': 13, 'Z': 82}, 'Bi': {'PT_row': 5, 'PT_col': 14, 'Z': 0},
|
|
473
|
-
'Po': {'PT_row': 5, 'PT_col': 15, 'Z': 0}, 'At': {'PT_row': 5, 'PT_col': 16, 'Z': 0},
|
|
474
|
-
'Rn': {'PT_row': 5, 'PT_col': 17, 'Z': 0}, 'Fr': {'PT_row': 6, 'PT_col': 0, 'Z': 0},
|
|
475
|
-
'Ra': {'PT_row': 6, 'PT_col': 1, 'Z': 0}, 'Rf': {'PT_row': 6, 'PT_col': 3, 'Z': 0},
|
|
476
|
-
'Db': {'PT_row': 6, 'PT_col': 4, 'Z': 0}, 'Sg': {'PT_row': 6, 'PT_col': 5, 'Z': 0},
|
|
477
|
-
'Bh': {'PT_row': 6, 'PT_col': 6, 'Z': 0}, 'Hs': {'PT_row': 6, 'PT_col': 7, 'Z': 0},
|
|
478
|
-
'Mt': {'PT_row': 6, 'PT_col': 8, 'Z': 0}, 'Ds': {'PT_row': 6, 'PT_col': 9, 'Z': 0},
|
|
479
|
-
'Rg': {'PT_row': 6, 'PT_col': 10, 'Z': 0}, 'La': {'PT_row': 8, 'PT_col': 3, 'Z': 57},
|
|
480
|
-
'Ce': {'PT_row': 8, 'PT_col': 4, 'Z': 58}, 'Pr': {'PT_row': 8, 'PT_col': 5, 'Z': 59},
|
|
481
|
-
'Nd': {'PT_row': 8, 'PT_col': 6, 'Z': 60}, 'Pm': {'PT_row': 8, 'PT_col': 7, 'Z': 61},
|
|
482
|
-
'Sm': {'PT_row': 8, 'PT_col': 8, 'Z': 62}, 'Eu': {'PT_row': 8, 'PT_col': 9, 'Z': 63},
|
|
483
|
-
'Gd': {'PT_row': 8, 'PT_col': 10, 'Z': 64}, 'Tb': {'PT_row': 8, 'PT_col': 11, 'Z': 65},
|
|
484
|
-
'Dy': {'PT_row': 8, 'PT_col': 12, 'Z': 66}, 'Ho': {'PT_row': 8, 'PT_col': 13, 'Z': 67},
|
|
485
|
-
'Er': {'PT_row': 8, 'PT_col': 14, 'Z': 68}, 'Tm': {'PT_row': 8, 'PT_col': 15, 'Z': 69},
|
|
486
|
-
'Yb': {'PT_row': 8, 'PT_col': 16, 'Z': 70}, 'Lu': {'PT_row': 8, 'PT_col': 17, 'Z': 71},
|
|
487
|
-
'Ac': {'PT_row': 9, 'PT_col': 3, 'Z': 0}, 'Th': {'PT_row': 9, 'PT_col': 4, 'Z': 0},
|
|
488
|
-
'Pa': {'PT_row': 9, 'PT_col': 5, 'Z': 0}, 'U': {'PT_row': 9, 'PT_col': 6, 'Z': 0},
|
|
489
|
-
'Np': {'PT_row': 9, 'PT_col': 7, 'Z': 0}, 'Pu': {'PT_row': 9, 'PT_col': 8, 'Z': 0},
|
|
490
|
-
'Am': {'PT_row': 9, 'PT_col': 9, 'Z': 0}, 'Cm': {'PT_row': 9, 'PT_col': 10, 'Z': 0},
|
|
491
|
-
'Bk': {'PT_row': 9, 'PT_col': 11, 'Z': 0}, 'Cf': {'PT_row': 9, 'PT_col': 12, 'Z': 0},
|
|
492
|
-
'Es': {'PT_row': 9, 'PT_col': 13, 'Z': 0}, 'Fm': {'PT_row': 9, 'PT_col': 14, 'Z': 0},
|
|
493
|
-
'Md': {'PT_row': 9, 'PT_col': 15, 'Z': 0}, 'No': {'PT_row': 9, 'PT_col': 16, 'Z': 0},
|
|
494
|
-
'Lr': {'PT_row': 9, 'PT_col': 17, 'Z': 0},
|
|
495
|
-
'*': {'PT_row': 5, 'PT_col': 2, 'PT_col2': 8, 'PT_row2': 2, 'Z': 0},
|
|
496
|
-
'**': {'PT_row': 6, 'PT_col': 2, 'PT_col2': 9, 'PT_row2': 2, 'Z': 0}}
|
|
497
|
-
|
|
498
|
-
return pt_info
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
class InteractiveSpectrumImage(object):
|
|
502
|
-
"""Interactive spectrum imaging plot
|
|
503
|
-
|
|
504
|
-
Attributes:
|
|
505
|
-
-----------
|
|
506
|
-
dictionary with a minimum of the following keys:
|
|
507
|
-
['image']: displayed image
|
|
508
|
-
['data']: data cube
|
|
509
|
-
['intensity_scale_ppm']: intensity scale
|
|
510
|
-
['ylabel']: intensity label
|
|
511
|
-
['spectra'] dictionary which contains dictionaries for each spectrum style ['1-2']:
|
|
512
|
-
['spectrum'] = tags['cube'][y,x,:]
|
|
513
|
-
['spectra'][f'{x}-{y}']['energy_scale'] = tags['energy_scale']
|
|
514
|
-
['intensity_scale'] = 1/tags['cube'][y,x,:].sum()*1e6
|
|
515
|
-
|
|
516
|
-
Please note the possibility to load any image for the selection of the spectrum
|
|
517
|
-
Also there is the possibility to display the survey image.
|
|
518
|
-
|
|
519
|
-
For analysis, we have the following options:
|
|
520
|
-
'fix_energy': set zero-loss peak maximum to zero !! Low loss spectra only!!
|
|
521
|
-
'fit_zero_loss': fit zero-loss peak with model function !! Low loss spectra only!!
|
|
522
|
-
'fit_low_loss': fit low-loss spectrum with model peaks !! Low loss spectra only!!
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
'fit_composition': fit core-loss spectrum with background and cross-sections!! Core loss spectra only!!
|
|
526
|
-
'fit_ELNES': fit core-loss edge with model peaks !! Core loss spectra only!!
|
|
527
|
-
"""
|
|
528
|
-
|
|
529
|
-
def __init__(self, data_source, horizontal=True):
|
|
530
|
-
|
|
531
|
-
box_layout = ipywidgets.Layout(display='flex',
|
|
532
|
-
flex_flow='row',
|
|
533
|
-
align_items='stretch',
|
|
534
|
-
width='100%')
|
|
535
|
-
|
|
536
|
-
words = ['fix_energy', 'fit_zero_loss', 'fit_low_loss', 'fit_composition', 'fit_ELNES']
|
|
537
|
-
|
|
538
|
-
self.buttons = [ipywidgets.ToggleButton(value=False, description=word, disabled=False) for word in words]
|
|
539
|
-
box = ipywidgets.Box(children=self.buttons, layout=box_layout)
|
|
540
|
-
display(box)
|
|
541
|
-
|
|
542
|
-
# MAKE Dictionary
|
|
543
|
-
|
|
544
|
-
if isinstance(data_source, dict):
|
|
545
|
-
self.tags = data_source
|
|
546
|
-
elif isinstance(data_source, h5py.Group):
|
|
547
|
-
self.tags = self.set_tags(data_source)
|
|
548
|
-
else:
|
|
549
|
-
print('Data source must be a dictionary or channel')
|
|
550
|
-
return
|
|
551
|
-
|
|
552
|
-
# Button(description='edge_quantification')
|
|
553
|
-
for button in self.buttons:
|
|
554
|
-
button.observe(self.on_button_clicked, 'value') # on_click(self.on_button_clicked)
|
|
555
|
-
|
|
556
|
-
self.figure = plt.figure()
|
|
557
|
-
self.horizontal = horizontal
|
|
558
|
-
self.x = 0
|
|
559
|
-
self.y = 0
|
|
560
|
-
|
|
561
|
-
self.extent = [0, self.tags['cube'].shape[1], self.tags['cube'].shape[0], 0]
|
|
562
|
-
self.rectangle = [0, self.tags['cube'].shape[1], 0, self.tags['cube'].shape[0]]
|
|
563
|
-
self.scaleX = 1.0
|
|
564
|
-
self.scaleY = 1.0
|
|
565
|
-
self.analysis = []
|
|
566
|
-
self.plot_legend = False
|
|
567
|
-
if 'ylabel' not in self.tags:
|
|
568
|
-
self.tags['ylabel'] = 'intensity [a.u.]'
|
|
569
|
-
self.SI = False
|
|
570
|
-
|
|
571
|
-
if horizontal:
|
|
572
|
-
self.ax1 = plt.subplot(1, 2, 1)
|
|
573
|
-
self.ax2 = plt.subplot(1, 2, 2)
|
|
574
|
-
else:
|
|
575
|
-
self.ax1 = plt.subplot(2, 1, 1)
|
|
576
|
-
self.ax2 = plt.subplot(2, 1, 2)
|
|
577
|
-
|
|
578
|
-
self.cube = self.tags['cube']
|
|
579
|
-
self.image = self.tags['cube'].sum(axis=2)
|
|
580
|
-
|
|
581
|
-
self.ax1.imshow(self.image, extent=self.extent)
|
|
582
|
-
if horizontal:
|
|
583
|
-
self.ax1.set_xlabel('distance [pixels]')
|
|
584
|
-
else:
|
|
585
|
-
self.ax1.set_ylabel('distance [pixels]')
|
|
586
|
-
self.ax1.set_aspect('equal')
|
|
587
|
-
|
|
588
|
-
self.rect = patches.Rectangle((0, 0), 1, 1, linewidth=1, edgecolor='r', facecolor='red', alpha=0.2)
|
|
589
|
-
self.ax1.add_patch(self.rect)
|
|
590
|
-
self.intensity_scale = self.tags['spectra'][f'{self.x}-{self.y}']['intensity_scale']
|
|
591
|
-
self.spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
|
|
592
|
-
self.energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
|
|
593
|
-
|
|
594
|
-
self.ax2.plot(self.energy_scale, self.spectrum)
|
|
595
|
-
self.ax2.set_title(f' spectrum {self.x},{self.y} ')
|
|
596
|
-
self.ax2.set_xlabel('energy loss [eV]')
|
|
597
|
-
self.ax2.set_ylabel(self.tags['ylabel'])
|
|
598
|
-
self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
|
|
599
|
-
|
|
600
|
-
plt.tight_layout()
|
|
601
|
-
|
|
602
|
-
def on_button_clicked(self, b):
|
|
603
|
-
# print(b['owner'].description)
|
|
604
|
-
selection = b['owner'].description
|
|
605
|
-
if b['new']:
|
|
606
|
-
if selection == 'fit_composition':
|
|
607
|
-
if 'region_tags' in self.tags and 'edges_present' in self.tags \
|
|
608
|
-
and 'acceleration_voltage' in self.tags \
|
|
609
|
-
and 'collection_angle' in self.tags:
|
|
610
|
-
pass
|
|
611
|
-
else:
|
|
612
|
-
self.buttons[3].value = False
|
|
613
|
-
return
|
|
614
|
-
elif selection in ['fix_energy', 'fit_zero_loss']:
|
|
615
|
-
if self.energy_scale[0] > 0:
|
|
616
|
-
button_index = ['fix_energy', 'fit_zero_loss'].index(selection)
|
|
617
|
-
self.buttons[button_index].value = False
|
|
618
|
-
return
|
|
619
|
-
self.analysis.append(selection)
|
|
620
|
-
self.update()
|
|
621
|
-
else:
|
|
622
|
-
|
|
623
|
-
if selection in self.analysis:
|
|
624
|
-
self.analysis.remove(selection)
|
|
625
|
-
|
|
626
|
-
def do_all(self, selection=None, verbose=True):
|
|
627
|
-
x = self.x
|
|
628
|
-
y = self.y
|
|
629
|
-
if selection is None:
|
|
630
|
-
selection = self.analysis
|
|
631
|
-
for self.x in range(self.cube.shape[1]):
|
|
632
|
-
if verbose:
|
|
633
|
-
print(f' row: {self.x}')
|
|
634
|
-
for self.y in range(self.cube.shape[0]):
|
|
635
|
-
|
|
636
|
-
if 'fit_zero_loss' in selection:
|
|
637
|
-
title = self.fit_zero_loss(plot_this=False)
|
|
638
|
-
|
|
639
|
-
elif 'fix_energy' in selection:
|
|
640
|
-
self.ax2.set_title('bn')
|
|
641
|
-
title = self.fix_energy()
|
|
642
|
-
|
|
643
|
-
elif 'fit_composition' in selection:
|
|
644
|
-
title = self.fit_quantification(plot_this=False)
|
|
645
|
-
|
|
646
|
-
self.x = x
|
|
647
|
-
self.y = y
|
|
648
|
-
|
|
649
|
-
def onclick(self, event):
|
|
650
|
-
x = int(event.xdata)
|
|
651
|
-
y = int(event.ydata)
|
|
652
|
-
|
|
653
|
-
# print(x,y)
|
|
654
|
-
if self.rectangle[0] <= x < self.rectangle[0] + self.rectangle[1]:
|
|
655
|
-
if self.rectangle[2] <= y < self.rectangle[2] + self.rectangle[3]:
|
|
656
|
-
self.x = int((x - self.rectangle[0]) / self.rectangle[1] * self.cube.shape[1])
|
|
657
|
-
self.y = int((y - self.rectangle[2]) / self.rectangle[3] * self.cube.shape[0])
|
|
658
|
-
else:
|
|
659
|
-
return
|
|
660
|
-
else:
|
|
661
|
-
return
|
|
662
|
-
|
|
663
|
-
if event.inaxes in [self.ax1]:
|
|
664
|
-
x = (self.x * self.rectangle[1] / self.cube.shape[1] + self.rectangle[0])
|
|
665
|
-
y = (self.y * self.rectangle[3] / self.cube.shape[0] + self.rectangle[2])
|
|
666
|
-
|
|
667
|
-
self.rect.set_xy([x, y])
|
|
668
|
-
self.update()
|
|
669
|
-
|
|
670
|
-
def update(self):
|
|
671
|
-
xlim = self.ax2.get_xlim()
|
|
672
|
-
ylim = self.ax2.get_ylim()
|
|
673
|
-
self.ax2.clear()
|
|
674
|
-
self.intensity_scale = self.tags['spectra'][f'{self.x}-{self.y}']['intensity_scale']
|
|
675
|
-
self.spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
|
|
676
|
-
self.energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
|
|
677
|
-
|
|
678
|
-
if 'fit_zero_loss' in self.analysis:
|
|
679
|
-
title = self.fit_zero_loss()
|
|
680
|
-
self.ax2.set_title(title)
|
|
681
|
-
elif 'fix_energy' in self.analysis:
|
|
682
|
-
self.ax2.set_title('bn')
|
|
683
|
-
title = self.fix_energy()
|
|
684
|
-
self.ax2.set_title(title)
|
|
685
|
-
|
|
686
|
-
elif 'fit_composition' in self.analysis:
|
|
687
|
-
title = self.fit_quantification()
|
|
688
|
-
self.ax2.set_title(title)
|
|
689
|
-
|
|
690
|
-
else:
|
|
691
|
-
self.ax2.set_title(f' spectrum {self.x},{self.y} ')
|
|
692
|
-
self.ax2.plot(self.energy_scale, self.spectrum, color='#1f77b4', label='experiment')
|
|
693
|
-
|
|
694
|
-
if self.plot_legend:
|
|
695
|
-
self.ax2.legend(shadow=True)
|
|
696
|
-
self.ax2.set_xlim(xlim)
|
|
697
|
-
self.ax2.set_ylim(ylim)
|
|
698
|
-
self.ax2.set_xlabel('energy loss [eV]')
|
|
699
|
-
self.ax2.set_ylabel(self.tags['ylabel'])
|
|
700
|
-
self.ax2.set_xlim(xlim)
|
|
701
|
-
|
|
702
|
-
# self.ax2.draw()
|
|
703
|
-
|
|
704
|
-
def set_tags(self, channel):
|
|
705
|
-
# TODO: change to sidpy dataset tags = ft.h5_get_dictionary(channel)
|
|
706
|
-
tags = {}
|
|
707
|
-
if tags['data_type'] == 'spectrum_image':
|
|
708
|
-
tags['image'] = tags['data']
|
|
709
|
-
tags['data'] = tags['cube'][0, 0, :]
|
|
710
|
-
if 'intensity_scale_ppm' not in channel:
|
|
711
|
-
channel['intensity_scale_ppm'] = 1
|
|
712
|
-
|
|
713
|
-
tags['ylabel'] = 'intensity [a.u.]'
|
|
714
|
-
tags['spectra'] = {}
|
|
715
|
-
for x in range(tags['spatial_size_y']):
|
|
716
|
-
for y in range(tags['spatial_size_x']):
|
|
717
|
-
tags['spectra'][f'{x}-{y}'] = {}
|
|
718
|
-
tags['spectra'][f'{x}-{y}']['spectrum'] = tags['cube'][y, x, :]
|
|
719
|
-
tags['spectra'][f'{x}-{y}']['energy_scale'] = tags['energy_scale']
|
|
720
|
-
tags['spectra'][f'{x}-{y}']['intensity_scale'] = 1 / tags['cube'][y, x, :].sum() * 1e6
|
|
721
|
-
tags['ylabel'] = 'inel. scat. int. [ppm]'
|
|
722
|
-
|
|
723
|
-
return tags
|
|
724
|
-
|
|
725
|
-
def fix_energy(self):
|
|
726
|
-
|
|
727
|
-
energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
|
|
728
|
-
spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
|
|
729
|
-
fwhm, delta_e = eels.fix_energy_scale(spectrum, energy_scale)
|
|
730
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['delta_e'] = delta_e
|
|
731
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['fwhm'] = fwhm
|
|
732
|
-
self.energy_scale = energy_scale - delta_e
|
|
733
|
-
title = f'spectrum {self.x},{self.y} fwhm: {fwhm:.2f}, dE: {delta_e:.3f}'
|
|
734
|
-
return title
|
|
735
|
-
|
|
736
|
-
def fit_zero_loss(self, plot_this=True):
|
|
737
|
-
|
|
738
|
-
energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
|
|
739
|
-
spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
|
|
740
|
-
if 'zero_loss_fit_width' not in self.tags:
|
|
741
|
-
self.tags['zero_loss_fit_width'] = .5
|
|
742
|
-
if self.tags['zero_loss_fit_width'] / (energy_scale[1] - energy_scale[0]) < 6:
|
|
743
|
-
self.tags['zero_loss_fit_width'] = (energy_scale[1] - energy_scale[0]) * 6
|
|
744
|
-
fwhm, delta_e = eels.fix_energy_scale(spectrum, energy_scale)
|
|
745
|
-
energy_scale = energy_scale - delta_e
|
|
746
|
-
z_oss, p_zl = eels.resolution_function(energy_scale, spectrum, self.tags['zero_loss_fit_width'])
|
|
747
|
-
fwhm2, delta_e2 = eels.fix_energy_scale(z_oss, energy_scale)
|
|
748
|
-
|
|
749
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['resolution_function'] = z_oss
|
|
750
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['p_zl'] = p_zl
|
|
751
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['delta_e'] = delta_e
|
|
752
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['fwhm_resolution'] = fwhm2
|
|
753
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['fwhm'] = fwhm
|
|
754
|
-
|
|
755
|
-
if plot_this:
|
|
756
|
-
self.ax2.plot(energy_scale, z_oss, label='resolution function', color='black')
|
|
757
|
-
self.ax2.plot(energy_scale, self.spectrum - z_oss, label='difference', color='orange')
|
|
758
|
-
self.ax2.axhline(linewidth=0.5, color='black')
|
|
759
|
-
self.energy_scale = energy_scale
|
|
760
|
-
title = f'spectrum {self.x},{self.y} fwhm: {fwhm:.2f}' # ', dE: {delta_e2:.5e}'
|
|
761
|
-
return title
|
|
762
|
-
|
|
763
|
-
def fit_quantification(self, plot_this=True):
|
|
764
|
-
energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
|
|
765
|
-
spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
|
|
766
|
-
edges = eels.make_edges(self.tags['edges_present'], energy_scale, self.tags['acceleration_voltage'],
|
|
767
|
-
self.tags['collection_angle'])
|
|
768
|
-
edges = eels.fit_edges(spectrum, self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale'],
|
|
769
|
-
self.tags['region_tags'], edges)
|
|
770
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['edges'] = edges.copy()
|
|
771
|
-
if plot_this:
|
|
772
|
-
self.ax2.plot(energy_scale, edges['model']['spectrum'], label='model')
|
|
773
|
-
self.ax2.plot(energy_scale, self.spectrum - edges['model']['spectrum'], label='difference')
|
|
774
|
-
self.ax2.axhline(linewidth=0.5, color='black')
|
|
775
|
-
else:
|
|
776
|
-
self.tags['spectra'][f'{self.x}-{self.y}']['do_all'] = 'done'
|
|
777
|
-
title = f'spectrum {self.x},{self.y} '
|
|
778
|
-
|
|
779
|
-
for key in edges:
|
|
780
|
-
if key.isdigit():
|
|
781
|
-
title = title + f"{edges[key]['element']}: {edges[key]['areal_density']:.2e}; "
|
|
782
|
-
|
|
783
|
-
return title
|
|
784
|
-
|
|
785
|
-
def set_legend(self, set_legend):
|
|
786
|
-
self.plot_legend = set_legend
|
|
787
|
-
|
|
788
|
-
def get_xy(self):
|
|
789
|
-
return [self.x, self.y]
|
|
790
|
-
|
|
791
|
-
def get_current_spectrum(self):
|
|
792
|
-
return self.cube[self.y, self.x, :]
|
|
793
|
-
|
|
794
|
-
def set_z_contrast_image(self, z_channel=None):
|
|
795
|
-
if z_channel is not None:
|
|
796
|
-
self.tags['Z_contrast_channel'] = z_channel
|
|
797
|
-
if 'Z_contrast_channel' not in self.tags:
|
|
798
|
-
print('add Z contrast channel group to dictionary first!')
|
|
799
|
-
return
|
|
800
|
-
|
|
801
|
-
z_tags = {} # TODO change to sidpy dataset ft.h5_get_dictionary(z_channel)
|
|
802
|
-
extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
|
|
803
|
-
self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
|
|
804
|
-
self.ax1.imshow(z_tags['data'], extent=extent, cmap='gray')
|
|
805
|
-
|
|
806
|
-
def overlay_z_contrast_image(self, z_channel=None):
|
|
807
|
-
|
|
808
|
-
if self.SI:
|
|
809
|
-
if z_channel is not None:
|
|
810
|
-
self.tags['Z_contrast_channel'] = z_channel
|
|
811
|
-
if 'Z_contrast_channel' not in self.tags:
|
|
812
|
-
print('add survey channel group to dictionary first!')
|
|
813
|
-
return
|
|
814
|
-
|
|
815
|
-
z_tags = {} # TODO: change to sidpy ft.h5_get_dictionary(self.tags['Z_contrast_channel'])
|
|
816
|
-
|
|
817
|
-
xlim = self.ax1.get_xlim()
|
|
818
|
-
ylim = self.ax1.get_ylim()
|
|
819
|
-
extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
|
|
820
|
-
self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
|
|
821
|
-
self.ax1.imshow(z_tags['data'], extent=extent, cmap='viridis', alpha=0.5)
|
|
822
|
-
self.ax1.set_ylim(ylim)
|
|
823
|
-
self.ax1.set_xlim(xlim)
|
|
824
|
-
|
|
825
|
-
def overlay_data(self, data=None):
|
|
826
|
-
|
|
827
|
-
if self.SI:
|
|
828
|
-
if data is None:
|
|
829
|
-
data = self.cube.sum(axis=2)
|
|
830
|
-
|
|
831
|
-
xlim = self.ax1.get_xlim()
|
|
832
|
-
ylim = self.ax1.get_ylim()
|
|
833
|
-
extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
|
|
834
|
-
self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
|
|
835
|
-
self.ax1.imshow(data, extent=extent, alpha=0.7, cmap='viridis')
|
|
836
|
-
self.ax1.set_ylim(ylim)
|
|
837
|
-
self.ax1.set_xlim(xlim)
|
|
838
|
-
|
|
839
|
-
def set_survey_image(self, si_channel=None):
|
|
840
|
-
|
|
841
|
-
if si_channel is not None:
|
|
842
|
-
self.tags['survey_channel'] = si_channel
|
|
843
|
-
if 'survey_channel' not in self.tags:
|
|
844
|
-
print('add survey channel group to dictionary first!')
|
|
845
|
-
return
|
|
846
|
-
si_channel = self.tags['survey_channel']
|
|
847
|
-
si_tags = {} # TODO: change to sidpy ft.h5_get_dictionary(si_channel)
|
|
848
|
-
tags2 = dict(si_channel.attrs)
|
|
849
|
-
|
|
850
|
-
self.ax1.set_aspect('equal')
|
|
851
|
-
self.scaleX = si_channel['spatial_scale_x'][()]
|
|
852
|
-
self.scaleY = si_channel['spatial_scale_y'][()]
|
|
853
|
-
|
|
854
|
-
self.ax1.imshow(si_tags['data'], extent=si_tags['extent'], cmap='gray')
|
|
855
|
-
if self.horizontal:
|
|
856
|
-
self.ax1.set_xlabel('distance [nm]')
|
|
857
|
-
else:
|
|
858
|
-
self.ax1.set_ylabel('distance [nm]')
|
|
859
|
-
|
|
860
|
-
annotation_done = []
|
|
861
|
-
for key in tags2:
|
|
862
|
-
if 'annotations' in key:
|
|
863
|
-
annotation_number = key[12]
|
|
864
|
-
if annotation_number not in annotation_done:
|
|
865
|
-
annotation_done.append(annotation_number)
|
|
866
|
-
|
|
867
|
-
if tags2['annotations_' + annotation_number + '_type'] == 'text':
|
|
868
|
-
x = tags2['annotations_' + annotation_number + '_x']
|
|
869
|
-
y = tags2['annotations_' + annotation_number + '_y']
|
|
870
|
-
text = tags2['annotations_' + annotation_number + '_text']
|
|
871
|
-
self.ax1.text(x, y, text, color='r')
|
|
872
|
-
|
|
873
|
-
elif tags2['annotations_' + annotation_number + '_type'] == 'circle':
|
|
874
|
-
radius = 20 * self.scaleX # tags['annotations'][key]['radius']
|
|
875
|
-
xy = tags2['annotations_' + annotation_number + '_position']
|
|
876
|
-
circle = patches.Circle(xy, radius, color='r', fill=False)
|
|
877
|
-
self.ax1.add_artist(circle)
|
|
878
|
-
|
|
879
|
-
elif tags2['annotations_' + annotation_number + '_type'] == 'spectrum image':
|
|
880
|
-
width = tags2['annotations_' + annotation_number + '_width']
|
|
881
|
-
height = tags2['annotations_' + annotation_number + '_height']
|
|
882
|
-
position = tags2['annotations_' + annotation_number + '_position']
|
|
883
|
-
rectangle = patches.Rectangle(position, width, height, color='r', fill=False)
|
|
884
|
-
self.rectangle = [position[0], width, position[1], height]
|
|
885
|
-
self.ax1.add_artist(rectangle)
|
|
886
|
-
self.ax1.text(position[0], position[1], 'Spectrum Image', color='r')
|
|
887
|
-
self.rect.set_width(width / self.cube.shape[1])
|
|
888
|
-
self.rect.set_height(height / self.cube.shape[0])
|
|
889
|
-
self.SI = True
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
class ElementalEdges(object):
|
|
893
|
-
""" Adds ionization edges of element z to plot with axis ax
|
|
894
|
-
|
|
895
|
-
There is an optional parameter maximum_chemical_shift which allows to change
|
|
896
|
-
the energy range in which the edges are searched.
|
|
897
|
-
|
|
898
|
-
available functions:
|
|
899
|
-
- update(): updates the drawing of ionization edges
|
|
900
|
-
- set_edge(Z) : changes atomic number and updates everything accordingly
|
|
901
|
-
- disconnect: makes everything invisible and stops drawing
|
|
902
|
-
- reconnect: undo of disconnect
|
|
903
|
-
|
|
904
|
-
usage:
|
|
905
|
-
>> fig, ax = plt.subplots()
|
|
906
|
-
>> ax.plot(energy_scale, spectrum)
|
|
907
|
-
>> Z= 42
|
|
908
|
-
>> cursor = ElementalEdges(ax, Z)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
see Chapter4 'CH4-Working_with_X-Sections' notebook
|
|
912
|
-
"""
|
|
913
|
-
|
|
914
|
-
def __init__(self, ax, z):
|
|
915
|
-
self.ax = ax
|
|
916
|
-
self.labels = None
|
|
917
|
-
self.lines = None
|
|
918
|
-
self.Z = eels.get_z(z)
|
|
919
|
-
self.color = 'black'
|
|
920
|
-
self.x_sections = eels.get_x_sections()
|
|
921
|
-
self.cid = ax.figure.canvas.mpl_connect('draw_event', self.onresize)
|
|
922
|
-
# self.update() is not necessary because of a drawing event is issued
|
|
923
|
-
|
|
924
|
-
def set_edge(self, z):
|
|
925
|
-
self.Z = eels.get_z(z)
|
|
926
|
-
if self.cid is None:
|
|
927
|
-
self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.onresize)
|
|
928
|
-
self.update()
|
|
929
|
-
|
|
930
|
-
def onresize(self, event):
|
|
931
|
-
self.update()
|
|
932
|
-
|
|
933
|
-
def update(self):
|
|
934
|
-
if self.labels is not None:
|
|
935
|
-
for label in self.labels:
|
|
936
|
-
label.remove()
|
|
937
|
-
if self.lines is not None:
|
|
938
|
-
for line in self.lines:
|
|
939
|
-
line.remove()
|
|
940
|
-
self.labels = []
|
|
941
|
-
self.lines = []
|
|
942
|
-
x_min, x_max = self.ax.get_xlim()
|
|
943
|
-
y_min, y_max = self.ax.get_ylim()
|
|
944
|
-
|
|
945
|
-
element = str(self.Z)
|
|
946
|
-
x_sections = self.x_sections
|
|
947
|
-
for key in all_edges:
|
|
948
|
-
if key in x_sections[element] and 'onset' in x_sections[element][key]:
|
|
949
|
-
x = x_sections[element][key]['onset']
|
|
950
|
-
if x_min < x < x_max:
|
|
951
|
-
if key in first_close_edges:
|
|
952
|
-
label2 = self.ax.text(x, y_max, f"{x_sections[element]['name']}-{key}",
|
|
953
|
-
verticalalignment='top', rotation=0, color=self.color)
|
|
954
|
-
else:
|
|
955
|
-
label2 = self.ax.text(x, y_max, f"\n{x_sections[element]['name']}-{key}",
|
|
956
|
-
verticalalignment='top', color=self.color)
|
|
957
|
-
line2 = self.ax.axvline(x, ymin=0, ymax=1, color=self.color)
|
|
958
|
-
|
|
959
|
-
self.labels.append(label2)
|
|
960
|
-
self.lines.append(line2)
|
|
961
|
-
|
|
962
|
-
def reconnect(self):
|
|
963
|
-
self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.onresize)
|
|
964
|
-
self.update()
|
|
965
|
-
|
|
966
|
-
def disconnect(self):
|
|
967
|
-
if self.labels is not None:
|
|
968
|
-
for label in self.labels:
|
|
969
|
-
label.remove()
|
|
970
|
-
if self.lines is not None:
|
|
971
|
-
for line in self.lines:
|
|
972
|
-
line.remove()
|
|
973
|
-
self.labels = None
|
|
974
|
-
self.lines = None
|
|
975
|
-
self.ax.figure.canvas.mpl_disconnect(self.cid)
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
class EdgesAtCursor(object):
|
|
979
|
-
"""
|
|
980
|
-
Adds a Cursor to a plot, which plots all major (possible) ionization edges at
|
|
981
|
-
the cursor location if left (right) mouse button is clicked.
|
|
982
|
-
|
|
983
|
-
Attributes
|
|
984
|
-
----------
|
|
985
|
-
ax: matplotlib axis
|
|
986
|
-
x: numpy array
|
|
987
|
-
energy_scale of spectrum
|
|
988
|
-
y: numpy array
|
|
989
|
-
intensities of spectrum
|
|
990
|
-
maximal_chemical_shift: float
|
|
991
|
-
optional parameter maximum_chemical_shift which allows to change the energy range in which the edges
|
|
992
|
-
are searched.
|
|
993
|
-
|
|
994
|
-
Example
|
|
995
|
-
-------
|
|
996
|
-
fig, ax = plt.subplots()
|
|
997
|
-
ax.plot(energy_scale, spectrum)
|
|
998
|
-
cursor = EdgesAtCursor(ax, energy_scale, spectrum)
|
|
999
|
-
|
|
1000
|
-
see Chapter4 'CH4-Working_with_X-Sections' notebook
|
|
1001
|
-
|
|
1002
|
-
"""
|
|
1003
|
-
|
|
1004
|
-
def __init__(self, ax, x, y, maximal_chemical_shift=5):
|
|
1005
|
-
self.ax = ax
|
|
1006
|
-
self.ly = ax.axvline(x[0], color='k', alpha=0.2) # the vert line
|
|
1007
|
-
self.marker, = ax.plot(x[0], y[0], marker="o", color="crimson", zorder=3)
|
|
1008
|
-
self.x = x
|
|
1009
|
-
self.y = y
|
|
1010
|
-
self.txt = ax.text(0.7, 0.9, '', verticalalignment='bottom')
|
|
1011
|
-
self.select = 0
|
|
1012
|
-
self.label = None
|
|
1013
|
-
self.line = None
|
|
1014
|
-
self.cid = ax.figure.canvas.mpl_connect('button_press_event', self.click)
|
|
1015
|
-
self.mouse_cid = ax.figure.canvas.mpl_connect('motion_notify_event', self.mouse_move)
|
|
1016
|
-
self.maximal_chemical_shift = maximal_chemical_shift
|
|
1017
|
-
|
|
1018
|
-
def click(self, event):
|
|
1019
|
-
|
|
1020
|
-
# print('click', event)
|
|
1021
|
-
if not event.inaxes:
|
|
1022
|
-
return
|
|
1023
|
-
x, y = event.xdata, event.ydata
|
|
1024
|
-
|
|
1025
|
-
index = np.searchsorted(self.x, [x])[0]
|
|
1026
|
-
x = self.x[index]
|
|
1027
|
-
y = self.y[index]
|
|
1028
|
-
self.select = x
|
|
1029
|
-
|
|
1030
|
-
y_min, y_max = self.ax.get_ylim()
|
|
1031
|
-
|
|
1032
|
-
if self.label is not None:
|
|
1033
|
-
self.label.remove()
|
|
1034
|
-
self.line.remove()
|
|
1035
|
-
if event.button == 1:
|
|
1036
|
-
self.label = self.ax.text(x, y_max, eels.find_all_edges(event.xdata, self.maximal_chemical_shift, major_edges_only=True),
|
|
1037
|
-
verticalalignment='top')
|
|
1038
|
-
self.line, = self.ax.plot([x, x], [y_min, y_max], color='black')
|
|
1039
|
-
if event.button == 3:
|
|
1040
|
-
self.line, = self.ax.plot([x, x], [y_min, y_max], color='black')
|
|
1041
|
-
self.label = self.ax.text(x, y_max, eels.find_all_edges(event.xdata, self.maximal_chemical_shift), verticalalignment='top')
|
|
1042
|
-
self.ax.set_ylim(y_min, y_max)
|
|
1043
|
-
|
|
1044
|
-
def mouse_move(self, event):
|
|
1045
|
-
if not event.inaxes:
|
|
1046
|
-
return
|
|
1047
|
-
|
|
1048
|
-
x, y = event.xdata, event.ydata
|
|
1049
|
-
index = np.searchsorted(self.x, [x])[0]
|
|
1050
|
-
x = self.x[index]
|
|
1051
|
-
y = self.y[index]
|
|
1052
|
-
self.select = x
|
|
1053
|
-
self.ly.set_xdata(x)
|
|
1054
|
-
self.marker.set_data([x], [y])
|
|
1055
|
-
self.txt.set_text(f'\n x={x:1.2f}, y={y:1.2g}\n')
|
|
1056
|
-
|
|
1057
|
-
# self.ax.text(x, y*2,find_major_edges(x))
|
|
1058
|
-
self.txt.set_position((x, y))
|
|
1059
|
-
self.ax.figure.canvas.draw_idle()
|
|
1060
|
-
|
|
1061
|
-
def del_edges(self):
|
|
1062
|
-
if self.label is not None:
|
|
1063
|
-
self.label.remove()
|
|
1064
|
-
self.line.remove()
|
|
1065
|
-
self.label = None
|
|
1066
|
-
|
|
1067
|
-
def disconnect(self):
|
|
1068
|
-
self.ly.remove()
|
|
1069
|
-
self.marker.remove()
|
|
1070
|
-
self.txt.remove()
|
|
1071
|
-
|
|
1072
|
-
self.ax.figure.canvas.mpl_disconnect(self.cid)
|
|
1073
|
-
self.ax.figure.canvas.mpl_disconnect(self.mouse_cid)
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
def make_box_layout():
|
|
1077
|
-
return ipywidgets.Layout(border='solid 1px black', margin='0px 10px 10px 0px', padding='5px 5px 5px 5px')
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
class plot_EELS(ipywidgets.HBox):
|
|
1081
|
-
def __init__(self, dataset):
|
|
1082
|
-
super().__init__()
|
|
1083
|
-
output = ipywidgets.Output()
|
|
1084
|
-
self.dataset = dataset
|
|
1085
|
-
self.spec_dim = 0
|
|
1086
|
-
initial_color = '#FF00DD'
|
|
1087
|
-
|
|
1088
|
-
with output:
|
|
1089
|
-
self.fig, self.axis = plt.subplots(constrained_layout=True, figsize=(5, 3.5))
|
|
1090
|
-
|
|
1091
|
-
self.axis.set_title(dataset.title.split('/')[-1])
|
|
1092
|
-
self.line, = self.axis.plot(dataset.dim_0.values, dataset, lw=2, label='spectrum')
|
|
1093
|
-
legend = self.axis.legend(fancybox=True, shadow=True)
|
|
1094
|
-
|
|
1095
|
-
lines = [self.line]
|
|
1096
|
-
self.line_dictionary = {} # Will map legend lines to original lines.
|
|
1097
|
-
for legend_line, original_line in zip(legend.get_lines(), lines):
|
|
1098
|
-
legend_line.set_picker(True) # Enable picking on the legend line.
|
|
1099
|
-
self.line_dictionary[legend_line] = original_line
|
|
1100
|
-
self.ax = self.axis
|
|
1101
|
-
self.fig.canvas.toolbar_position = 'bottom'
|
|
1102
|
-
self.fig.canvas.mpl_connect('pick_event', self.on_legend_pick)
|
|
1103
|
-
|
|
1104
|
-
# define widgets
|
|
1105
|
-
int_slider = ipywidgets.IntSlider(
|
|
1106
|
-
value=1,
|
|
1107
|
-
min=0,
|
|
1108
|
-
max=10,
|
|
1109
|
-
step=1,
|
|
1110
|
-
description='freq'
|
|
1111
|
-
)
|
|
1112
|
-
self.offset = ipywidgets.Text(
|
|
1113
|
-
value='0',
|
|
1114
|
-
width=5,
|
|
1115
|
-
description='offset',
|
|
1116
|
-
continuous_update=False
|
|
1117
|
-
)
|
|
1118
|
-
self.dispersion = ipywidgets.Text(
|
|
1119
|
-
value='0',
|
|
1120
|
-
width=5,
|
|
1121
|
-
description='dispersion',
|
|
1122
|
-
continuous_update=False
|
|
1123
|
-
)
|
|
1124
|
-
|
|
1125
|
-
self.exposure = ipywidgets.Text(
|
|
1126
|
-
value='0',
|
|
1127
|
-
width=5,
|
|
1128
|
-
description='exposure',
|
|
1129
|
-
continuous_update=False
|
|
1130
|
-
)
|
|
1131
|
-
|
|
1132
|
-
button_energy_scale = ipywidgets.Button(description='Cursor')
|
|
1133
|
-
button_elements_at_cursor = ipywidgets.Button(description='Elements Cursor')
|
|
1134
|
-
button_main_elements = ipywidgets.Button(description='Main Elements')
|
|
1135
|
-
|
|
1136
|
-
controls = ipywidgets.VBox([
|
|
1137
|
-
ipywidgets.HBox([self.offset, ipywidgets.Label('eV')]),
|
|
1138
|
-
ipywidgets.HBox([self.dispersion, ipywidgets.Label('eV/channel')]),
|
|
1139
|
-
ipywidgets.HBox([self.exposure, ipywidgets.Label('s')]),
|
|
1140
|
-
button_energy_scale,
|
|
1141
|
-
ipywidgets.HBox([button_elements_at_cursor, button_main_elements])
|
|
1142
|
-
])
|
|
1143
|
-
|
|
1144
|
-
controls.layout = make_box_layout()
|
|
1145
|
-
|
|
1146
|
-
out_box = ipywidgets.Box([output])
|
|
1147
|
-
output.layout = make_box_layout()
|
|
1148
|
-
|
|
1149
|
-
# observe stuff
|
|
1150
|
-
int_slider.observe(self.update, 'value')
|
|
1151
|
-
|
|
1152
|
-
self.offset.value = f'{self.dataset.dim_0.values[0]}'
|
|
1153
|
-
self.offset.observe(self.set_dimension, 'value')
|
|
1154
|
-
self.offset.value = f'{self.dataset.dim_0.values[0]}'
|
|
1155
|
-
|
|
1156
|
-
self.dispersion.observe(self.set_dimension, 'value')
|
|
1157
|
-
self.dispersion.value = f'{self.dataset.dim_0.values[1] - self.dataset.dim_0.values[0]}'
|
|
1158
|
-
self.dispersion.value = '0'
|
|
1159
|
-
self.exposure.observe(self.update_exposure, 'value')
|
|
1160
|
-
self.exposure.value = '0'
|
|
1161
|
-
|
|
1162
|
-
# add to children
|
|
1163
|
-
self.children = [controls, output]
|
|
1164
|
-
|
|
1165
|
-
def update(self):
|
|
1166
|
-
"""Draw line in plot"""
|
|
1167
|
-
self.line.set_ydata(self.dataset)
|
|
1168
|
-
self.line.set_xdata(self.dataset.dim_0.values)
|
|
1169
|
-
# self.axis.plot(self.dataset.energy_loss, self.dataset)
|
|
1170
|
-
self.fig.canvas.draw()
|
|
1171
|
-
|
|
1172
|
-
def line_color(self, change):
|
|
1173
|
-
self.line.set_color(change.new)
|
|
1174
|
-
|
|
1175
|
-
def update_exposure(self):
|
|
1176
|
-
pass
|
|
1177
|
-
|
|
1178
|
-
def update_ylabel(self, change):
|
|
1179
|
-
self.ax.set_ylabel(change.new)
|
|
1180
|
-
|
|
1181
|
-
def set_dimension(self, change):
|
|
1182
|
-
self.spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)
|
|
1183
|
-
self.spec_dim = self.spec_dim[0]
|
|
1184
|
-
old_energy_scale = self.spec_dim[1]
|
|
1185
|
-
energy_scale = np.arange(len(self.dataset.dim_0.values))*float(self.dispersion.value)+float(self.offset.value)
|
|
1186
|
-
self.dataset.set_dimension(self.spec_dim[0], sidpy.Dimension(energy_scale,
|
|
1187
|
-
name=old_energy_scale.name,
|
|
1188
|
-
dimension_type='SPECTRAL',
|
|
1189
|
-
units='eV',
|
|
1190
|
-
quantity='energy loss'))
|
|
1191
|
-
self.update()
|
|
1192
|
-
|
|
1193
|
-
def on_legend_pick(self, event):
|
|
1194
|
-
legend_line = event.artist
|
|
1195
|
-
original_line = self.line_dictionary[legend_line]
|
|
1196
|
-
visible = not original_line.get_visible()
|
|
1197
|
-
original_line.set_visible(visible)
|
|
1198
|
-
legend_line.set_alpha(1.0 if visible else 0.2)
|
|
1199
|
-
self.fig.canvas.draw()
|