pyTEMlib 0.2020.11.0__py3-none-any.whl → 0.2024.8.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyTEMlib might be problematic. Click here for more details.

Files changed (59) hide show
  1. pyTEMlib/__init__.py +11 -11
  2. pyTEMlib/animation.py +631 -0
  3. pyTEMlib/atom_tools.py +240 -222
  4. pyTEMlib/config_dir.py +57 -29
  5. pyTEMlib/core_loss_widget.py +658 -0
  6. pyTEMlib/crystal_tools.py +1255 -0
  7. pyTEMlib/diffraction_plot.py +756 -0
  8. pyTEMlib/dynamic_scattering.py +293 -0
  9. pyTEMlib/eds_tools.py +609 -0
  10. pyTEMlib/eels_dialog.py +749 -486
  11. pyTEMlib/{interactive_eels.py → eels_dialog_utilities.py} +1199 -1524
  12. pyTEMlib/eels_tools.py +2031 -1731
  13. pyTEMlib/file_tools.py +1276 -491
  14. pyTEMlib/file_tools_qt.py +193 -0
  15. pyTEMlib/graph_tools.py +1166 -450
  16. pyTEMlib/graph_viz.py +449 -0
  17. pyTEMlib/image_dialog.py +158 -0
  18. pyTEMlib/image_dlg.py +146 -0
  19. pyTEMlib/image_tools.py +1399 -956
  20. pyTEMlib/info_widget.py +933 -0
  21. pyTEMlib/interactive_image.py +1 -0
  22. pyTEMlib/kinematic_scattering.py +1196 -0
  23. pyTEMlib/low_loss_widget.py +176 -0
  24. pyTEMlib/microscope.py +61 -78
  25. pyTEMlib/peak_dialog.py +1047 -350
  26. pyTEMlib/peak_dlg.py +286 -248
  27. pyTEMlib/probe_tools.py +653 -202
  28. pyTEMlib/sidpy_tools.py +153 -129
  29. pyTEMlib/simulation_tools.py +104 -87
  30. pyTEMlib/version.py +6 -3
  31. pyTEMlib/xrpa_x_sections.py +20972 -0
  32. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/LICENSE +21 -21
  33. pyTEMlib-0.2024.8.4.dist-info/METADATA +93 -0
  34. pyTEMlib-0.2024.8.4.dist-info/RECORD +37 -0
  35. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/WHEEL +6 -5
  36. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/entry_points.txt +0 -1
  37. pyTEMlib/KinsCat.py +0 -2685
  38. pyTEMlib/__version__.py +0 -2
  39. pyTEMlib/data/TEMlibrc +0 -68
  40. pyTEMlib/data/edges_db.csv +0 -189
  41. pyTEMlib/data/edges_db.pkl +0 -0
  42. pyTEMlib/data/fparam.txt +0 -103
  43. pyTEMlib/data/microscopes.csv +0 -7
  44. pyTEMlib/data/microscopes.xml +0 -167
  45. pyTEMlib/data/path.txt +0 -1
  46. pyTEMlib/defaults_parser.py +0 -86
  47. pyTEMlib/dm3_reader.py +0 -609
  48. pyTEMlib/edges_db.py +0 -76
  49. pyTEMlib/eels_dlg.py +0 -240
  50. pyTEMlib/hdf_utils.py +0 -481
  51. pyTEMlib/image_tools1.py +0 -2194
  52. pyTEMlib/info_dialog.py +0 -227
  53. pyTEMlib/info_dlg.py +0 -205
  54. pyTEMlib/nion_reader.py +0 -293
  55. pyTEMlib/nsi_reader.py +0 -165
  56. pyTEMlib/structure_tools.py +0 -316
  57. pyTEMlib-0.2020.11.0.dist-info/METADATA +0 -20
  58. pyTEMlib-0.2020.11.0.dist-info/RECORD +0 -42
  59. {pyTEMlib-0.2020.11.0.dist-info → pyTEMlib-0.2024.8.4.dist-info}/top_level.txt +0 -0
@@ -1,1524 +1,1199 @@
1
-
2
- import numpy as np
3
-
4
- from PyQt5 import QtWidgets, QtCore, QtGui
5
- import sidpy
6
- import matplotlib.patches as patches
7
- from matplotlib.widgets import RectangleSelector, SpanSelector
8
-
9
- import h5py # TODO: needs to go
10
- import matplotlib.pyplot as plt
11
-
12
- from IPython.display import display
13
- import ipywidgets as widgets
14
-
15
- from pyTEMlib import eels_tools as eels
16
- from pyTEMlib import eels_dialog
17
- from pyTEMlib import info_dialog
18
- from pyTEMlib import peak_dialog
19
-
20
- major_edges = ['K1', 'L3', 'M5', 'N5']
21
- all_edges = ['K1', 'L1', 'L2', 'L3', 'M1', 'M2', 'M3', 'M4', 'M5', 'N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'O1', 'O2',
22
- 'O3', 'O4', 'O5', 'O6', 'O7', 'P1', 'P2', 'P3']
23
- first_close_edges = ['K1', 'L3', 'M5', 'M3', 'N5', 'N3']
24
-
25
- CompositionDialog = eels_dialog.EELSDialog
26
- InfoDialog = info_dialog.InfoDialog
27
- PeakFitDialog = peak_dialog.PeakFitDialog
28
-
29
-
30
- class PeriodicTableDialog(QtWidgets.QDialog):
31
- """
32
- Modal dialog to get a selection of elements.
33
-
34
- Elements that are not having a valid cross sections are disabled.
35
-
36
- optional input is:
37
- initial_elements: list of str
38
- the elements that are already selected
39
- energy_scale: list or numpy array
40
- energy-scale of spectrum/spectra to determine likely edges
41
-
42
-
43
- Returns:
44
- list of strings: elements.
45
- Usage:
46
- >> PT_dialog = periodic_table_dialog(None, ['Mn', 'O'])
47
- >> if PT_dialog.exec_() == periodic_table_dialog.Accepted:
48
- >> selected_elements = PT_dialog.get_output()
49
- >> print(selected_elements)
50
- """
51
-
52
- signal_selected = QtCore.pyqtSignal(list)
53
-
54
- def __init__(self, initial_elements=None, energy_scale=None, parent=None):
55
- super(PeriodicTableDialog, self).__init__(None, QtCore.Qt.WindowStaysOnTopHint)
56
-
57
- if initial_elements is None:
58
- initial_elements = [' ']
59
- if energy_scale is None:
60
- energy_scale = [100., 150., 200.]
61
- self.parent = parent
62
- self._output = []
63
- self.elements_selected = initial_elements
64
- self.energy_scale = np.array(energy_scale)
65
-
66
- self.setWindowTitle("Periodic Table")
67
- likely_edges = get_likely_edges(self.energy_scale)
68
- self.likely_edges = likely_edges
69
-
70
- # GD:font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.BOLD)
71
- self.buttons1 = []
72
- self.button = []
73
- self.pt_info = get_periodic_table_info()
74
- self.init_ui()
75
-
76
- for button in self.button:
77
- if button.text() in initial_elements:
78
- button.toggle()
79
- pass
80
-
81
- def on_close(self):
82
- self.get_output()
83
- self.signal_selected[list].emit(self._output)
84
- self.accept()
85
-
86
- def get_output(self):
87
- self._output = []
88
- for btn in self.button:
89
- if btn.isChecked():
90
- self._output.append(btn.text())
91
-
92
- def exec_(self):
93
- super(PeriodicTableDialog, self).exec_()
94
- return self._output
95
-
96
- def init_ui(self):
97
-
98
- v_sizer = QtWidgets.QVBoxLayout()
99
- g_sizer = QtWidgets.QGridLayout()
100
-
101
- main_group = QtWidgets.QWidget()
102
-
103
- color1 = "background-color: lightblue;\n"
104
- color1l = "background-color: dodgerblue;\n"
105
- color2 = "background-color: coral;\n"
106
-
107
- for symbol, parameter in self.pt_info.items():
108
- self.button.append(QtWidgets.QPushButton(symbol))
109
- if parameter['PT_row'] > 7:
110
- self.button[-1].setStyleSheet(color2)
111
- elif '*' in symbol:
112
- self.button[-1].setStyleSheet(color2)
113
- else:
114
- if symbol in self.likely_edges:
115
- self.button[-1].setStyleSheet(color1l)
116
- else:
117
- self.button[-1].setStyleSheet(color1)
118
- if parameter['Z'] == 0:
119
- self.button[-1].setEnabled(False)
120
- self.button[-1].setFixedWidth(50)
121
- self.button[-1].setCheckable(True)
122
- g_sizer.addWidget(self.button[-1], parameter['PT_row'], parameter['PT_col'])
123
- main_group.setLayout(g_sizer)
124
-
125
- v_sizer.addWidget(main_group)
126
- self.setLayout(v_sizer)
127
-
128
- ok_button = QtWidgets.QPushButton('OK')
129
- ok_button.clicked.connect(self.on_close)
130
-
131
- v_sizer.addWidget(ok_button)
132
- self.setLayout(v_sizer)
133
-
134
-
135
- class EnergySelector(QtWidgets.QDialog):
136
- signal_selected = QtCore.pyqtSignal(bool)
137
-
138
- def __init__(self, dset=None):
139
- super(EnergySelector, self).__init__(None, QtCore.Qt.WindowStaysOnTopHint)
140
-
141
- if not isinstance(dset, sidpy.Dataset):
142
- return
143
- if dset is None:
144
- return
145
- if dset.view is None:
146
- return
147
- self.dataset = dset
148
-
149
- if hasattr(dset.view, 'axis'):
150
- self.axis = dset.view.axis
151
- self.setWindowTitle('p')
152
- elif hasattr(dset.view, 'axes'):
153
- self.axis = dset.view.axes[1]
154
- else:
155
- return
156
-
157
- self.spec_dim = -1
158
- for dim, axis in self.dataset._axes.items():
159
- if axis.dimension_type == sidpy.DimensionTypes.SPECTRAL:
160
- self.spec_dim = dim
161
- if self.spec_dim < 0:
162
- raise TypeError('We need at least one SPECTRAL dimension')
163
-
164
- self.energy_scale = self.dataset._axes[self.spec_dim].values
165
- self.dispersion = self.energy_scale[1] - self.energy_scale[0]
166
- self.offset = self.energy_scale[0]
167
- self.spectrum = np.zeros(2)
168
-
169
- self.change = 0
170
-
171
- self.x_min = self.energy_scale[int(len(self.energy_scale)/4)]
172
- self.x_max = self.energy_scale[int(len(self.energy_scale) / 4*3)]
173
- self.setWindowTitle("Select Energy")
174
-
175
- valid_float = QtGui.QDoubleValidator()
176
-
177
- layout = QtWidgets.QGridLayout()
178
- layout.setVerticalSpacing(2)
179
- self.label1 = QtWidgets.QLabel('Start:')
180
- self.edit1 = QtWidgets.QLineEdit('0')
181
- self.edit1.setValidator(valid_float)
182
- self.unit1 = QtWidgets.QLabel('eV')
183
-
184
- self.label2 = QtWidgets.QLabel('End:')
185
- self.edit2 = QtWidgets.QLineEdit('0')
186
- self.edit2.setValidator(valid_float)
187
- self.unit2 = QtWidgets.QLabel('eV')
188
-
189
- self.label3 = QtWidgets.QLabel('Dispersion:')
190
- self.edit3 = QtWidgets.QLineEdit('0')
191
- self.edit3.setValidator(valid_float)
192
- self.unit3 = QtWidgets.QLabel('eV')
193
-
194
- self.edit1.editingFinished.connect(self.on_enter)
195
- self.edit2.editingFinished.connect(self.on_enter)
196
- self.edit3.editingFinished.connect(self.on_enter)
197
-
198
- layout.addWidget(self.label1, 0, 0)
199
- layout.addWidget(self.edit1, 0, 1)
200
- layout.addWidget(self.unit1, 0, 2)
201
-
202
- layout.addWidget(self.label2, 1, 0)
203
- layout.addWidget(self.edit2, 1, 1)
204
- layout.addWidget(self.unit2, 1, 2)
205
-
206
- layout.addWidget(self.label3, 2, 0)
207
- layout.addWidget(self.edit3, 2, 1)
208
- layout.addWidget(self.unit3, 2, 2)
209
-
210
- self.ok_button = QtWidgets.QPushButton('OK')
211
- self.ok_button.clicked.connect(self.on_close)
212
- self.cancel_button = QtWidgets.QPushButton('Cancel')
213
- self.cancel_button.clicked.connect(self.on_close)
214
-
215
- layout.addWidget(self.ok_button, 3, 0)
216
- layout.addWidget(self.cancel_button, 3, 2)
217
-
218
- self.setLayout(layout)
219
- self.edit1.setFocus()
220
- self.plot()
221
-
222
- self.selector = RangeSelector(self.axis, self.line_select_callback)
223
- self.edit1.setText(f'{self.x_min:.4f}')
224
- self.edit2.setText(f'{self.x_max:.4f}')
225
- self.edit3.setText(f'{self.dispersion:.4f}')
226
- self.update()
227
-
228
- def line_select_callback(self, eclick, erelease):
229
- y_min, y_max = self.axis.get_ylim()
230
- self.x_min = self.selector.extents[0]
231
- self.x_max = self.selector.extents[1]
232
- self.selector.extents = (self.x_min, self.x_max, y_min, y_max)
233
-
234
- self.edit1.setText(f'{self.x_min:.3f}')
235
- self.edit2.setText(f'{self.x_max:.3f}')
236
-
237
- def on_enter(self):
238
- sender = self.sender()
239
-
240
- if sender == self.edit1:
241
- value = float(str(sender.displayText()).strip())
242
- if value == self.x_min:
243
- return
244
- self.change = value - self.x_min
245
- self.x_min += self.change
246
- self.x_max += self.change
247
- self.offset += self.change
248
-
249
- self.edit1.setText(f"{self.x_min:.2f}")
250
- self.edit2.setText(f"{self.x_max:.2f}")
251
-
252
- self.energy_scale = np.arange(len(self.energy_scale)) * self.dispersion + self.offset
253
-
254
- self.update()
255
- # self.axis.draw()
256
- self.setWindowTitle(f'shift, {self.change}, {self.x_min}')
257
-
258
- elif sender == self.edit2:
259
- value = float(str(sender.displayText()).strip())
260
- if value == self.x_max:
261
- return
262
- start_channel = np.searchsorted(self.energy_scale, self.x_min)
263
- end_channel = np.searchsorted(self.energy_scale, self.x_max)
264
-
265
- self.x_max = value
266
-
267
- if end_channel - start_channel != 0:
268
- self.dispersion = (self.x_max - self.x_min) / (end_channel - start_channel)
269
- self.offset = self.x_min - start_channel * self.dispersion
270
- self.edit2.setText(f"{self.x_max:.2f}")
271
- self.edit3.setText(f"{self.dispersion:.2f}")
272
- self.energy_scale = np.arange(len(self.energy_scale)) * self.dispersion + self.offset
273
-
274
- self.update()
275
- # self.axis.draw()
276
- self.setWindowTitle(f'range, {self.change}, {self.dispersion}')
277
-
278
- elif sender == self.edit3:
279
- value = float(str(sender.displayText()).strip())
280
- if self.dispersion == value:
281
- return
282
-
283
- start_channel = np.searchsorted(self.energy_scale, self.x_min)
284
- end_channel = np.searchsorted(self.energy_scale, self.x_max)
285
- self.dispersion = value
286
- self.energy_scale = np.arange(len(self.energy_scale)) * self.dispersion + self.offset
287
- self.x_min = self.energy_scale[start_channel]
288
- self.x_max = self.energy_scale[end_channel]
289
- self.update()
290
- # self.axis.draw()
291
- self.edit3.setText(f"{self.dispersion:.3f}")
292
- self.change = 0
293
-
294
- def on_close(self):
295
- sender = self.sender()
296
- if sender == self.ok_button:
297
- pass
298
- self.dataset.set_dimension(self.spec_dim, sidpy.Dimension(self.energy_scale, name='energy_scale',
299
- units='eV', quantity='energy loss',
300
- dimension_type='spectral'))
301
- else:
302
- pass
303
- self.selector.set_active(False)
304
- self.signal_selected[bool].emit(True)
305
- self.accept()
306
-
307
- def plot(self):
308
- if self.dataset.data_type == sidpy.DataTypes.SPECTRAL_IMAGE:
309
- self.spectrum = self.dataset.view.get_spectrum()
310
- else:
311
- self.spectrum = np.array(self.dataset)
312
- x_limit = self.axis.get_xlim()
313
- y_limit = self.axis.get_ylim()
314
-
315
- self.axis.clear()
316
- self.cplot = self.axis.plot(self.energy_scale, self.spectrum, label='spectrum')
317
- self.axis.set_xlim(x_limit)
318
- self.axis.set_ylim(y_limit)
319
-
320
- self.axis.figure.canvas.draw()
321
-
322
- def update(self):
323
- x_limit = self.axis.get_xlim()
324
- y_limit = self.axis.get_ylim()
325
- self.selector.extents = (self.x_min, self.x_max, y_limit[0], y_limit[1])
326
-
327
- x_limit = np.array(x_limit) + self.change
328
-
329
- self.cplot[0].set_data(self.energy_scale, self.spectrum)
330
- self.axis.set_xlim(x_limit)
331
- self.axis.set_ylim(y_limit)
332
- self.axis.figure.canvas.draw()
333
-
334
-
335
- class RegionSelector(object):
336
- """
337
- Selects fitting region and the regions that are excluded for each edge.
338
-
339
- Select a region with a spanSelector and then type 'a' for all of the fitting region or a number for the edge
340
- you want to define the region excluded from the fit (solid state effects).
341
-
342
- see Chapter4 'CH4-Working_with_X-Sections,ipynb' notebook
343
-
344
- """
345
-
346
- def __init__(self, ax):
347
- self.ax = ax
348
- self.regions = {}
349
- self.rect = None
350
- self.xmin = 0
351
- self.width = 0
352
-
353
- self.span = SpanSelector(ax, self.onselect1, 'horizontal', useblit=True,
354
- rectprops=dict(alpha=0.5, facecolor='red'), span_stays=True)
355
- self.cid = ax.figure.canvas.mpl_connect('key_press_event', self.click)
356
- self.draw = ax.figure.canvas.mpl_connect('draw_event', self.onresize)
357
-
358
- def onselect1(self, xmin, xmax):
359
- self.xmin = xmin
360
- self.width = xmax - xmin
361
-
362
- def onresize(self, event):
363
- self.update()
364
-
365
- def delete_region(self, key):
366
- if key in self.regions:
367
- if 'Rect' in self.regions[key]:
368
- self.regions[key]['Rect'].remove()
369
- self.regions[key]['Text'].remove()
370
- del (self.regions[key])
371
-
372
- def update(self):
373
-
374
- y_min, y_max = self.ax.get_ylim()
375
- for key in self.regions:
376
- if 'Rect' in self.regions[key]:
377
- self.regions[key]['Rect'].remove()
378
- self.regions[key]['Text'].remove()
379
-
380
- xmin = self.regions[key]['xmin']
381
- width = self.regions[key]['width']
382
- height = y_max - y_min
383
- alpha = self.regions[key]['alpha']
384
- color = self.regions[key]['color']
385
- self.regions[key]['Rect'] = patches.Rectangle((xmin, y_min), width, height,
386
- edgecolor=color, alpha=alpha, facecolor=color)
387
- self.ax.add_patch(self.regions[key]['Rect'])
388
-
389
- self.regions[key]['Text'] = self.ax.text(xmin, y_max, self.regions[key]['text'], verticalalignment='top')
390
-
391
- def click(self, event):
392
- if str(event.key) in ['1', '2', '3', '4', '5', '6']:
393
- key = str(event.key)
394
- text = 'exclude \nedge ' + key
395
- alpha = 0.5
396
- color = 'red'
397
- elif str(event.key) in ['a', 'A', 'B', 'b', 'f', 'F']:
398
- key = '0'
399
- color = 'blue'
400
- alpha = 0.2
401
- text = 'fit region'
402
- else:
403
- return
404
-
405
- if key not in self.regions:
406
- self.regions[key] = {}
407
-
408
- self.regions[key]['xmin'] = self.xmin
409
- self.regions[key]['width'] = self.width
410
- self.regions[key]['color'] = color
411
- self.regions[key]['alpha'] = alpha
412
- self.regions[key]['text'] = text
413
-
414
- self.update()
415
-
416
- def set_regions(self, region, start_x, width):
417
- key = ''
418
- if 'fit' in str(region):
419
- key = '0'
420
- if region in ['0', '1', '2', '3', '4', '5', '6']:
421
- key = region
422
- if region in [0, 1, 2, 3, 4, 5, 6]:
423
- key = str(region)
424
-
425
- if key not in self.regions:
426
- self.regions[key] = {}
427
- if key in ['1', '2', '3', '4', '5', '6']:
428
- self.regions[key]['text'] = 'exclude \nedge ' + key
429
- self.regions[key]['alpha'] = 0.5
430
- self.regions[key]['color'] = 'red'
431
- elif key == '0':
432
- self.regions[key]['text'] = 'fit region'
433
- self.regions[key]['alpha'] = 0.2
434
- self.regions[key]['color'] = 'blue'
435
-
436
- self.regions[key]['xmin'] = start_x
437
- self.regions[key]['width'] = width
438
-
439
- self.update()
440
-
441
- def get_regions(self):
442
- tags = {}
443
- for key in self.regions:
444
- if key == '0':
445
- area = 'fit_area'
446
- else:
447
- area = key
448
- tags[area] = {}
449
- tags[area]['start_x'] = self.regions[key]['xmin']
450
- tags[area]['width_x'] = self.regions[key]['width']
451
-
452
- return tags
453
-
454
- def disconnect(self):
455
- for key in self.regions:
456
- if 'Rect' in self.regions[key]:
457
- self.regions[key]['Rect'].remove()
458
- self.regions[key]['Text'].remove()
459
- del self.span
460
- self.ax.figure.canvas.mpl_disconnect(self.cid)
461
- # self.ax.figure.canvas.mpl_disconnect(self.draw)
462
- pass
463
-
464
-
465
- class RangeSelector(RectangleSelector):
466
- def __init__(self, ax, onselect):
467
- drawtype = 'box'
468
- spancoords = 'data'
469
- rectprops = dict(facecolor="blue", edgecolor="black", alpha=0.2, fill=True)
470
-
471
- super().__init__(ax, onselect, drawtype=drawtype,
472
- minspanx=0, minspany=0, useblit=False,
473
- lineprops=None, rectprops=rectprops, spancoords=spancoords,
474
- button=None, maxdist=10, marker_props=None,
475
- interactive=True, state_modifier_keys=None)
476
-
477
- self.artists = [self.to_draw, self._center_handle.artist,
478
- self._edge_handles.artist]
479
-
480
- def draw_shape(self, extents):
481
- x0, x1, y0, y1 = extents
482
- xmin, xmax = sorted([x0, x1])
483
- # ymin, ymax = sorted([y0, y1])
484
- xlim = sorted(self.ax.get_xlim())
485
- ylim = sorted(self.ax.get_ylim())
486
-
487
- xmin = max(xlim[0], xmin)
488
- ymin = ylim[0]
489
- xmax = min(xmax, xlim[1])
490
- ymax = ylim[1]
491
-
492
- self.to_draw.set_x(xmin)
493
- self.to_draw.set_y(ymin)
494
- self.to_draw.set_width(xmax - xmin)
495
- self.to_draw.set_height(ymax - ymin)
496
-
497
-
498
- def get_likely_edges(energy_scale):
499
- x_sections = eels.get_x_sections()
500
- # print(energy_scale)
501
- energy_origin = energy_scale[0]
502
- energy_window = energy_scale[-1] - energy_origin
503
- selected_edges_unsorted = {}
504
- likely_edges = []
505
- selected_elements = []
506
- for element in range(1, 83):
507
- # print(element)
508
- element_z = str(eels.get_z(element))
509
-
510
- for key in x_sections[element_z]:
511
- if key in all_edges:
512
- onset = x_sections[element_z][key]['onset']
513
- if onset > energy_origin:
514
- if onset - energy_origin < energy_window:
515
- if element not in selected_edges_unsorted:
516
- selected_edges_unsorted[element] = {}
517
- # print(element, x_sections[element]['name'], key, x_sections[element][key]['onset'])
518
- # text = f"\n {x_sections[element_z]['name']:2s}-{key}: " \
519
- # f"{x_sections[element_z][key]['onset']:8.1f} eV "
520
- # print(text)
521
-
522
- selected_edges_unsorted[element][key] = {}
523
- selected_edges_unsorted[element][key]['onset'] = x_sections[element_z][key]['onset']
524
-
525
- if key in major_edges:
526
- selected_edges_unsorted[element][key]['intensity'] = 'major'
527
- selected_elements.append(x_sections[element_z]['name'])
528
- else:
529
- selected_edges_unsorted[element][key]['intensity'] = 'minor'
530
-
531
- if element in selected_edges_unsorted:
532
- for key in selected_edges_unsorted[element]:
533
- if selected_edges_unsorted[element][key]['intensity'] == 'major':
534
- likely_edges.append(x_sections[str(element)]['name']) # = {'Z':element, 'symmetry': key}
535
-
536
- return likely_edges
537
-
538
-
539
- def get_periodic_table_info():
540
- pt_info = \
541
- {'H': {'PT_row': 0, 'PT_col': 0, 'Z': 0},
542
- 'He': {'PT_row': 0, 'PT_col': 17, 'Z': 2}, 'Li': {'PT_row': 1, 'PT_col': 0, 'Z': 3},
543
- 'Be': {'PT_row': 1, 'PT_col': 1, 'Z': 4}, 'B': {'PT_row': 1, 'PT_col': 12, 'Z': 5},
544
- 'C': {'PT_row': 1, 'PT_col': 13, 'Z': 6}, 'N': {'PT_row': 1, 'PT_col': 14, 'Z': 7},
545
- 'O': {'PT_row': 1, 'PT_col': 15, 'Z': 8}, 'F': {'PT_row': 1, 'PT_col': 16, 'Z': 9},
546
- 'Ne': {'PT_row': 1, 'PT_col': 17, 'Z': 10}, 'Na': {'PT_row': 2, 'PT_col': 0, 'Z': 11},
547
- 'Mg': {'PT_row': 2, 'PT_col': 1, 'Z': 12}, 'Al': {'PT_row': 2, 'PT_col': 12, 'Z': 13},
548
- 'Si': {'PT_row': 2, 'PT_col': 13, 'Z': 14}, 'P': {'PT_row': 2, 'PT_col': 14, 'Z': 15},
549
- 'S': {'PT_row': 2, 'PT_col': 15, 'Z': 16}, 'Cl': {'PT_row': 2, 'PT_col': 16, 'Z': 17},
550
- 'Ar': {'PT_row': 2, 'PT_col': 17, 'Z': 18}, 'K': {'PT_row': 3, 'PT_col': 0, 'Z': 19},
551
- 'Ca': {'PT_row': 3, 'PT_col': 1, 'Z': 20}, 'Sc': {'PT_row': 3, 'PT_col': 2, 'Z': 21},
552
- 'Ti': {'PT_row': 3, 'PT_col': 3, 'Z': 22}, 'V ': {'PT_row': 3, 'PT_col': 4, 'Z': 23},
553
- 'Cr': {'PT_row': 3, 'PT_col': 5, 'Z': 24}, 'Mn': {'PT_row': 3, 'PT_col': 6, 'Z': 25},
554
- 'Fe': {'PT_row': 3, 'PT_col': 7, 'Z': 26}, 'Co': {'PT_row': 3, 'PT_col': 8, 'Z': 27},
555
- 'Ni': {'PT_row': 3, 'PT_col': 9, 'Z': 28}, 'Cu': {'PT_row': 3, 'PT_col': 10, 'Z': 29},
556
- 'Zn': {'PT_row': 3, 'PT_col': 11, 'Z': 30}, 'Ga': {'PT_row': 3, 'PT_col': 12, 'Z': 31},
557
- 'Ge': {'PT_row': 3, 'PT_col': 13, 'Z': 32}, 'As': {'PT_row': 3, 'PT_col': 14, 'Z': 33},
558
- 'Se': {'PT_row': 3, 'PT_col': 15, 'Z': 34}, 'Br': {'PT_row': 3, 'PT_col': 16, 'Z': 35},
559
- 'Kr': {'PT_row': 3, 'PT_col': 17, 'Z': 36}, 'Rb': {'PT_row': 4, 'PT_col': 0, 'Z': 37},
560
- 'Sr': {'PT_row': 4, 'PT_col': 1, 'Z': 38}, 'Y': {'PT_row': 4, 'PT_col': 2, 'Z': 39},
561
- 'Zr': {'PT_row': 4, 'PT_col': 3, 'Z': 40}, 'Nb': {'PT_row': 4, 'PT_col': 4, 'Z': 41},
562
- 'Mo': {'PT_row': 4, 'PT_col': 5, 'Z': 42}, 'Tc': {'PT_row': 4, 'PT_col': 6, 'Z': 43},
563
- 'Ru': {'PT_row': 4, 'PT_col': 7, 'Z': 44}, 'Rh': {'PT_row': 4, 'PT_col': 8, 'Z': 45},
564
- 'Pd': {'PT_row': 4, 'PT_col': 9, 'Z': 46}, 'Ag': {'PT_row': 4, 'PT_col': 10, 'Z': 47},
565
- 'Cd': {'PT_row': 4, 'PT_col': 11, 'Z': 48}, 'In': {'PT_row': 4, 'PT_col': 12, 'Z': 49},
566
- 'Sn': {'PT_row': 4, 'PT_col': 13, 'Z': 50}, 'Sb': {'PT_row': 4, 'PT_col': 14, 'Z': 51},
567
- 'Te': {'PT_row': 4, 'PT_col': 15, 'Z': 52}, 'I': {'PT_row': 4, 'PT_col': 16, 'Z': 53},
568
- 'Xe': {'PT_row': 4, 'PT_col': 17, 'Z': 54}, 'Cs': {'PT_row': 5, 'PT_col': 0, 'Z': 55},
569
- 'Ba': {'PT_row': 5, 'PT_col': 1, 'Z': 56}, 'Hf': {'PT_row': 5, 'PT_col': 3, 'Z': 72},
570
- 'Ta': {'PT_row': 5, 'PT_col': 4, 'Z': 73}, 'W': {'PT_row': 5, 'PT_col': 5, 'Z': 74},
571
- 'Re': {'PT_row': 5, 'PT_col': 6, 'Z': 75}, 'Os': {'PT_row': 5, 'PT_col': 7, 'Z': 76},
572
- 'Ir': {'PT_row': 5, 'PT_col': 8, 'Z': 77}, 'Pt': {'PT_row': 5, 'PT_col': 9, 'Z': 78},
573
- 'Au': {'PT_row': 5, 'PT_col': 10, 'Z': 79}, 'Hg': {'PT_row': 5, 'PT_col': 11, 'Z': 80},
574
- 'Pb': {'PT_row': 5, 'PT_col': 13, 'Z': 82}, 'Bi': {'PT_row': 5, 'PT_col': 14, 'Z': 0},
575
- 'Po': {'PT_row': 5, 'PT_col': 15, 'Z': 0}, 'At': {'PT_row': 5, 'PT_col': 16, 'Z': 0},
576
- 'Rn': {'PT_row': 5, 'PT_col': 17, 'Z': 0}, 'Fr': {'PT_row': 6, 'PT_col': 0, 'Z': 0},
577
- 'Ra': {'PT_row': 6, 'PT_col': 1, 'Z': 0}, 'Rf': {'PT_row': 6, 'PT_col': 3, 'Z': 0},
578
- 'Db': {'PT_row': 6, 'PT_col': 4, 'Z': 0}, 'Sg': {'PT_row': 6, 'PT_col': 5, 'Z': 0},
579
- 'Bh': {'PT_row': 6, 'PT_col': 6, 'Z': 0}, 'Hs': {'PT_row': 6, 'PT_col': 7, 'Z': 0},
580
- 'Mt': {'PT_row': 6, 'PT_col': 8, 'Z': 0}, 'Ds': {'PT_row': 6, 'PT_col': 9, 'Z': 0},
581
- 'Rg': {'PT_row': 6, 'PT_col': 10, 'Z': 0}, 'La': {'PT_row': 8, 'PT_col': 3, 'Z': 57},
582
- 'Ce': {'PT_row': 8, 'PT_col': 4, 'Z': 58}, 'Pr': {'PT_row': 8, 'PT_col': 5, 'Z': 59},
583
- 'Nd': {'PT_row': 8, 'PT_col': 6, 'Z': 60}, 'Pm': {'PT_row': 8, 'PT_col': 7, 'Z': 61},
584
- 'Sm': {'PT_row': 8, 'PT_col': 8, 'Z': 62}, 'Eu': {'PT_row': 8, 'PT_col': 9, 'Z': 63},
585
- 'Gd': {'PT_row': 8, 'PT_col': 10, 'Z': 64}, 'Tb': {'PT_row': 8, 'PT_col': 11, 'Z': 65},
586
- 'Dy': {'PT_row': 8, 'PT_col': 12, 'Z': 66}, 'Ho': {'PT_row': 8, 'PT_col': 13, 'Z': 67},
587
- 'Er': {'PT_row': 8, 'PT_col': 14, 'Z': 68}, 'Tm': {'PT_row': 8, 'PT_col': 15, 'Z': 69},
588
- 'Yb': {'PT_row': 8, 'PT_col': 16, 'Z': 70}, 'Lu': {'PT_row': 8, 'PT_col': 17, 'Z': 71},
589
- 'Ac': {'PT_row': 9, 'PT_col': 3, 'Z': 0}, 'Th': {'PT_row': 9, 'PT_col': 4, 'Z': 0},
590
- 'Pa': {'PT_row': 9, 'PT_col': 5, 'Z': 0}, 'U': {'PT_row': 9, 'PT_col': 6, 'Z': 0},
591
- 'Np': {'PT_row': 9, 'PT_col': 7, 'Z': 0}, 'Pu': {'PT_row': 9, 'PT_col': 8, 'Z': 0},
592
- 'Am': {'PT_row': 9, 'PT_col': 9, 'Z': 0}, 'Cm': {'PT_row': 9, 'PT_col': 10, 'Z': 0},
593
- 'Bk': {'PT_row': 9, 'PT_col': 11, 'Z': 0}, 'Cf': {'PT_row': 9, 'PT_col': 12, 'Z': 0},
594
- 'Es': {'PT_row': 9, 'PT_col': 13, 'Z': 0}, 'Fm': {'PT_row': 9, 'PT_col': 14, 'Z': 0},
595
- 'Md': {'PT_row': 9, 'PT_col': 15, 'Z': 0}, 'No': {'PT_row': 9, 'PT_col': 16, 'Z': 0},
596
- 'Lr': {'PT_row': 9, 'PT_col': 17, 'Z': 0},
597
- '*': {'PT_row': 5, 'PT_col': 2, 'PT_col2': 8, 'PT_row2': 2, 'Z': 0},
598
- '**': {'PT_row': 6, 'PT_col': 2, 'PT_col2': 9, 'PT_row2': 2, 'Z': 0}}
599
-
600
- return pt_info
601
-
602
-
603
- class InteractiveSpectrumImage(object):
604
- """
605
- ### Interactive spectrum imaging plot
606
-
607
- Input tags: dictionary with a minimum of the following keys:
608
- ['image']: displayed image
609
- ['data']: data cube
610
- [if ]'intensity_scale_ppm']: intensity scale
611
- ['ylabel']: intensity label
612
- ['spectra'] dictionary which contains dictionaries for each spectrum style ['1-2']:
613
- ['spectrum'] = tags['cube'][y,x,:]
614
- ['spectra'][f'{x}-{y}']['energy_scale'] = tags['energy_scale']
615
- ['intensity_scale'] = 1/tags['cube'][y,x,:].sum()*1e6
616
-
617
- Please note the possibility to load any image for the selection of the spectrum
618
- Also there is the possibility to display the survey image.
619
-
620
- For analysis we have the following options:
621
- 'fix_energy': set zero-loss peak maximum to zero !! Low loss spectra only!!
622
- 'fit_zero_loss': fit zero-loss peak with model function !! Low loss spectra only!!
623
- 'fit_low_loss': fit low-loss spectrum with model peaks !! Low loss spectra only!!
624
-
625
-
626
- 'fit_composition': fit core-loss spectrum with background and cross sections!! Core loss spectra only!!
627
- 'fit_ELNES': fit core-loss edge with model peaks !! Core loss spectra only!!
628
- """
629
-
630
- def __init__(self, data_source, horizontal=True):
631
-
632
- box_layout = widgets.Layout(display='flex',
633
- flex_flow='row',
634
- align_items='stretch',
635
- width='100%')
636
-
637
- words = ['fix_energy', 'fit_zero_loss', 'fit_low_loss', 'fit_composition', 'fit_ELNES']
638
-
639
- self.buttons = [widgets.ToggleButton(value=False, description=word, disabled=False) for word in words]
640
- box = widgets.Box(children=self.buttons, layout=box_layout)
641
- display(box)
642
-
643
- # MAKE Dictionary
644
-
645
- if isinstance(data_source, dict):
646
- self.tags = data_source
647
- elif isinstance(data_source, h5py.Group):
648
- self.tags = self.set_tags(data_source)
649
- else:
650
- print('Data source must be a dictionary or channel')
651
- return
652
-
653
- # Button(description='edge_quantification')
654
- for button in self.buttons:
655
- button.observe(self.on_button_clicked, 'value') # on_click(self.on_button_clicked)
656
-
657
- self.figure = plt.figure()
658
- self.horizontal = horizontal
659
- self.x = 0
660
- self.y = 0
661
-
662
- self.extent = [0, self.tags['cube'].shape[1], self.tags['cube'].shape[0], 0]
663
- self.rectangle = [0, self.tags['cube'].shape[1], 0, self.tags['cube'].shape[0]]
664
- self.scaleX = 1.0
665
- self.scaleY = 1.0
666
- self.analysis = []
667
- self.plot_legend = False
668
- if 'ylabel' not in self.tags:
669
- self.tags['ylabel'] = 'intensity [a.u.]'
670
- self.SI = False
671
-
672
- if horizontal:
673
- self.ax1 = plt.subplot(1, 2, 1)
674
- self.ax2 = plt.subplot(1, 2, 2)
675
- else:
676
- self.ax1 = plt.subplot(2, 1, 1)
677
- self.ax2 = plt.subplot(2, 1, 2)
678
-
679
- self.cube = self.tags['cube']
680
- self.image = self.tags['cube'].sum(axis=2)
681
-
682
- self.ax1.imshow(self.image, extent=self.extent)
683
- if horizontal:
684
- self.ax1.set_xlabel('distance [pixels]')
685
- else:
686
- self.ax1.set_ylabel('distance [pixels]')
687
- self.ax1.set_aspect('equal')
688
-
689
- self.rect = patches.Rectangle((0, 0), 1, 1, linewidth=1, edgecolor='r', facecolor='red', alpha=0.2)
690
- self.ax1.add_patch(self.rect)
691
- self.intensity_scale = self.tags['spectra'][f'{self.x}-{self.y}']['intensity_scale']
692
- self.spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
693
- self.energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
694
-
695
- self.ax2.plot(self.energy_scale, self.spectrum)
696
- self.ax2.set_title(f' spectrum {self.x},{self.y} ')
697
- self.ax2.set_xlabel('energy loss [eV]')
698
- self.ax2.set_ylabel(self.tags['ylabel'])
699
- self.cid = self.figure.canvas.mpl_connect('button_press_event', self.onclick)
700
-
701
- plt.tight_layout()
702
-
703
- def on_button_clicked(self, b):
704
- # print(b['owner'].description)
705
- selection = b['owner'].description
706
- if b['new']:
707
- if selection == 'fit_composition':
708
- if 'region_tags' in self.tags and 'edges_present' in self.tags \
709
- and 'acceleration_voltage' in self.tags \
710
- and 'collection_angle' in self.tags:
711
- pass
712
- else:
713
- self.buttons[3].value = False
714
- return
715
- elif selection in ['fix_energy', 'fit_zero_loss']:
716
- if self.energy_scale[0] > 0:
717
- button_index = ['fix_energy', 'fit_zero_loss'].index(selection)
718
- self.buttons[button_index].value = False
719
- return
720
- self.analysis.append(selection)
721
- self.update()
722
- else:
723
-
724
- if selection in self.analysis:
725
- self.analysis.remove(selection)
726
-
727
- def do_all(self, selection=None, verbose=True):
728
- x = self.x
729
- y = self.y
730
- if selection is None:
731
- selection = self.analysis
732
- for self.x in range(self.cube.shape[1]):
733
- if verbose:
734
- print(f' row: {self.x}')
735
- for self.y in range(self.cube.shape[0]):
736
-
737
- if 'fit_zero_loss' in selection:
738
- title = self.fit_zero_loss(plot_this=False)
739
-
740
- elif 'fix_energy' in selection:
741
- self.ax2.set_title('bn')
742
- title = self.fix_energy()
743
-
744
- elif 'fit_composition' in selection:
745
- title = self.fit_quantification(plot_this=False)
746
-
747
- self.x = x
748
- self.y = y
749
-
750
- def onclick(self, event):
751
- x = int(event.xdata)
752
- y = int(event.ydata)
753
-
754
- # print(x,y)
755
- if self.rectangle[0] <= x < self.rectangle[0] + self.rectangle[1]:
756
- if self.rectangle[2] <= y < self.rectangle[2] + self.rectangle[3]:
757
- self.x = int((x - self.rectangle[0]) / self.rectangle[1] * self.cube.shape[1])
758
- self.y = int((y - self.rectangle[2]) / self.rectangle[3] * self.cube.shape[0])
759
- else:
760
- return
761
- else:
762
- return
763
-
764
- if event.inaxes in [self.ax1]:
765
- x = (self.x * self.rectangle[1] / self.cube.shape[1] + self.rectangle[0])
766
- y = (self.y * self.rectangle[3] / self.cube.shape[0] + self.rectangle[2])
767
-
768
- self.rect.set_xy([x, y])
769
- self.update()
770
-
771
- def update(self):
772
- xlim = self.ax2.get_xlim()
773
- ylim = self.ax2.get_ylim()
774
- self.ax2.clear()
775
- self.intensity_scale = self.tags['spectra'][f'{self.x}-{self.y}']['intensity_scale']
776
- self.spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
777
- self.energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
778
-
779
- if 'fit_zero_loss' in self.analysis:
780
- title = self.fit_zero_loss()
781
- self.ax2.set_title(title)
782
- elif 'fix_energy' in self.analysis:
783
- self.ax2.set_title('bn')
784
- title = self.fix_energy()
785
- self.ax2.set_title(title)
786
-
787
- elif 'fit_composition' in self.analysis:
788
- title = self.fit_quantification()
789
- self.ax2.set_title(title)
790
-
791
- else:
792
- self.ax2.set_title(f' spectrum {self.x},{self.y} ')
793
- self.ax2.plot(self.energy_scale, self.spectrum, color='#1f77b4', label='experiment')
794
-
795
- if self.plot_legend:
796
- self.ax2.legend(shadow=True)
797
- self.ax2.set_xlim(xlim)
798
- self.ax2.set_ylim(ylim)
799
- self.ax2.set_xlabel('energy loss [eV]')
800
- self.ax2.set_ylabel(self.tags['ylabel'])
801
- self.ax2.set_xlim(xlim)
802
-
803
- # self.ax2.draw()
804
-
805
- def set_tags(self, channel):
806
- # TODO: change to sidpy dataset tags = ft.h5_get_dictionary(channel)
807
- tags = {}
808
- if tags['data_type'] == 'spectrum_image':
809
- tags['image'] = tags['data']
810
- tags['data'] = tags['cube'][0, 0, :]
811
- if 'intentsity_scale_ppm' not in channel:
812
- channel['intentsity_scale_ppm'] = 1
813
-
814
- tags['ylabel'] = 'intensity [a.u.]'
815
- tags['spectra'] = {}
816
- for x in range(tags['spatial_size_y']):
817
- for y in range(tags['spatial_size_x']):
818
- tags['spectra'][f'{x}-{y}'] = {}
819
- tags['spectra'][f'{x}-{y}']['spectrum'] = tags['cube'][y, x, :]
820
- tags['spectra'][f'{x}-{y}']['energy_scale'] = tags['energy_scale']
821
- tags['spectra'][f'{x}-{y}']['intensity_scale'] = 1 / tags['cube'][y, x, :].sum() * 1e6
822
- tags['ylabel'] = 'inel. scat. int. [ppm]'
823
-
824
- return tags
825
-
826
- def fix_energy(self):
827
-
828
- energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
829
- spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
830
- fwhm, delta_e = eels.fix_energy_scale(spectrum, energy_scale)
831
- self.tags['spectra'][f'{self.x}-{self.y}']['delta_e'] = delta_e
832
- self.tags['spectra'][f'{self.x}-{self.y}']['fwhm'] = fwhm
833
- self.energy_scale = energy_scale - delta_e
834
- title = f'spectrum {self.x},{self.y} fwhm: {fwhm:.2f}, dE: {delta_e:.3f}'
835
- return title
836
-
837
- def fit_zero_loss(self, plot_this=True):
838
-
839
- energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
840
- spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
841
- if 'zero_loss_fit_width' not in self.tags:
842
- self.tags['zero_loss_fit_width'] = .5
843
- if self.tags['zero_loss_fit_width'] / (energy_scale[1] - energy_scale[0]) < 6:
844
- self.tags['zero_loss_fit_width'] = (energy_scale[1] - energy_scale[0]) * 6
845
- fwhm, delta_e = eels.fix_energy_scale(spectrum, energy_scale)
846
- energy_scale = energy_scale - delta_e
847
- z_oss, p_zl = eels.resolution_function(energy_scale, spectrum, self.tags['zero_loss_fit_width'])
848
- fwhm2, delta_e2 = eels.fix_energy_scale(z_oss, energy_scale)
849
-
850
- self.tags['spectra'][f'{self.x}-{self.y}']['resolution_function'] = z_oss
851
- self.tags['spectra'][f'{self.x}-{self.y}']['p_zl'] = p_zl
852
- self.tags['spectra'][f'{self.x}-{self.y}']['delta_e'] = delta_e
853
- self.tags['spectra'][f'{self.x}-{self.y}']['fwhm_resolution'] = fwhm2
854
- self.tags['spectra'][f'{self.x}-{self.y}']['fwhm'] = fwhm
855
-
856
- if plot_this:
857
- self.ax2.plot(energy_scale, z_oss, label='resolution function', color='black')
858
- self.ax2.plot(energy_scale, self.spectrum - z_oss, label='difference', color='orange')
859
- self.ax2.axhline(linewidth=0.5, color='black')
860
- self.energy_scale = energy_scale
861
- title = f'spectrum {self.x},{self.y} fwhm: {fwhm:.2f}' # ', dE: {delta_e2:.5e}'
862
- return title
863
-
864
- def fit_quantification(self, plot_this=True):
865
- energy_scale = self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale']
866
- spectrum = self.tags['spectra'][f'{self.x}-{self.y}']['spectrum'] * self.intensity_scale
867
- edges = eels.make_edges(self.tags['edges_present'], energy_scale, self.tags['acceleration_voltage'],
868
- self.tags['collection_angle'])
869
- edges = eels.fit_edges(spectrum, self.tags['spectra'][f'{self.x}-{self.y}']['energy_scale'],
870
- self.tags['region_tags'], edges)
871
- self.tags['spectra'][f'{self.x}-{self.y}']['edges'] = edges.copy()
872
- if plot_this:
873
- self.ax2.plot(energy_scale, edges['model']['spectrum'], label='model')
874
- self.ax2.plot(energy_scale, self.spectrum - edges['model']['spectrum'], label='difference')
875
- self.ax2.axhline(linewidth=0.5, color='black')
876
- else:
877
- self.tags['spectra'][f'{self.x}-{self.y}']['do_all'] = 'done'
878
- title = f'spectrum {self.x},{self.y} '
879
-
880
- for key in edges:
881
- if key.isdigit():
882
- title = title + f"{edges[key]['element']}: {edges[key]['areal_density']:.2e}; "
883
-
884
- return title
885
-
886
- def set_legend(self, set_legend):
887
- self.plot_legend = set_legend
888
-
889
- def get_xy(self):
890
- return [self.x, self.y]
891
-
892
- def get_current_spectrum(self):
893
- return self.cube[self.y, self.x, :]
894
-
895
- def set_Zcontrast_image(self, z_channel=None):
896
- if z_channel is not None:
897
- self.tags['Zcontrast_channel'] = z_channel
898
- if 'Zcontrast_channel' not in self.tags:
899
- print('add Z contrast channel group to dictionary first!')
900
- return
901
- # get dictionary from current channel in pyUSID file
902
- z_tags = {} # TODO change to sidpy datasetft.h5_get_dictionary(z_channel)
903
- extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
904
- self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
905
- self.ax1.imshow(z_tags['data'], extent=extent, cmap='gray')
906
-
907
- def overlay_Zcontrast_image(self, z_channel=None):
908
-
909
- if self.SI:
910
- if z_channel is not None:
911
- self.tags['Zcontrast_channel'] = z_channel
912
- if 'Zcontrast_channel' not in self.tags:
913
- print('add survey channel group to dictionary first!')
914
- return
915
-
916
- z_tags = {} # TODO: change to sidpy ft.h5_get_dictionary(self.tags['Zcontrast_channel'])
917
-
918
- xlim = self.ax1.get_xlim()
919
- ylim = self.ax1.get_ylim()
920
- extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
921
- self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
922
- self.ax1.imshow(z_tags['data'], extent=extent, cmap='viridis', alpha=0.5)
923
- self.ax1.set_ylim(ylim)
924
- self.ax1.set_xlim(xlim)
925
-
926
- def overlay_data(self, data=None):
927
-
928
- if self.SI:
929
- if data is None:
930
- data = self.cube.sum(axis=2)
931
-
932
- xlim = self.ax1.get_xlim()
933
- ylim = self.ax1.get_ylim()
934
- extent = [self.rectangle[0], self.rectangle[0] + self.rectangle[1],
935
- self.rectangle[2] + self.rectangle[3], self.rectangle[2]]
936
- self.ax1.imshow(data, extent=extent, alpha=0.7, cmap='viridis')
937
- self.ax1.set_ylim(ylim)
938
- self.ax1.set_xlim(xlim)
939
-
940
- def set_survey_image(self, si_channel=None):
941
-
942
- # get dictionary from current channel in pyUSID file
943
- if si_channel is not None:
944
- self.tags['survey_channel'] = si_channel
945
- if 'survey_channel' not in self.tags:
946
- print('add survey channel group to dictionary first!')
947
- return
948
- si_channel = self.tags['survey_channel']
949
- si_tags = {} # TODO: change to sidpy ft.h5_get_dictionary(si_channel)
950
- tags2 = dict(si_channel.attrs)
951
-
952
- self.ax1.set_aspect('equal')
953
- self.scaleX = si_channel['spatial_scale_x'][()]
954
- self.scaleY = si_channel['spatial_scale_y'][()]
955
-
956
- self.ax1.imshow(si_tags['data'], extent=si_tags['extent'], cmap='gray')
957
- if self.horizontal:
958
- self.ax1.set_xlabel('distance [nm]')
959
- else:
960
- self.ax1.set_ylabel('distance [nm]')
961
-
962
- annotation_done = []
963
- for key in tags2:
964
- if 'annotations' in key:
965
- annotation_number = key[12]
966
- if annotation_number not in annotation_done:
967
- annotation_done.append(annotation_number)
968
-
969
- if tags2['annotations_' + annotation_number + '_type'] == 'text':
970
- x = tags2['annotations_' + annotation_number + '_x']
971
- y = tags2['annotations_' + annotation_number + '_y']
972
- text = tags2['annotations_' + annotation_number + '_text']
973
- self.ax1.text(x, y, text, color='r')
974
-
975
- elif tags2['annotations_' + annotation_number + '_type'] == 'circle':
976
- radius = 20 * self.scaleX # tags['annotations'][key]['radius']
977
- xy = tags2['annotations_' + annotation_number + '_position']
978
- circle = patches.Circle(xy, radius, color='r', fill=False)
979
- self.ax1.add_artist(circle)
980
-
981
- elif tags2['annotations_' + annotation_number + '_type'] == 'spectrum image':
982
- width = tags2['annotations_' + annotation_number + '_width']
983
- height = tags2['annotations_' + annotation_number + '_height']
984
- position = tags2['annotations_' + annotation_number + '_position']
985
- rectangle = patches.Rectangle(position, width, height, color='r', fill=False)
986
- self.rectangle = [position[0], width, position[1], height]
987
- self.ax1.add_artist(rectangle)
988
- self.ax1.text(position[0], position[1], 'Spectrum Image', color='r')
989
- self.rect.set_width(width / self.cube.shape[1])
990
- self.rect.set_height(height / self.cube.shape[0])
991
- self.SI = True
992
-
993
-
994
- class ElementalEdges(object):
995
- """
996
- The necessary initialization parameter are:
997
- ax: matplotlib axis
998
-
999
-
1000
- There is an optional parameter maximum_chemical_shift which allows to change
1001
- the energy range in which the edges are searched.
1002
-
1003
- available functions:
1004
- - update(): updates the drawing of ionization edges
1005
- - set_edge(Z) : changes atomic number and updates everything accordingly
1006
- - disconnect: makes everything invisible and stops drawing
1007
- - reconnect: undo of disconnect
1008
-
1009
- usage:
1010
- >> fig, ax = plt.subplots()
1011
- >> ax.plot(energy_scale, spectrum)
1012
- >> Z= 42
1013
- >> cursor = ElementalEdges(ax, Z)
1014
-
1015
-
1016
- see Chapter4 'CH4-Working_with_X-Sections' notebook
1017
- """
1018
-
1019
- def __init__(self, ax, z):
1020
- self.ax = ax
1021
- self.labels = None
1022
- self.lines = None
1023
- self.Z = eels.get_z(z)
1024
- self.color = 'black'
1025
- self.x_sections = eels.get_x_sections()
1026
- self.cid = ax.figure.canvas.mpl_connect('draw_event', self.onresize)
1027
- # self.update() is not necessary because of a drawing event is issued
1028
-
1029
- def set_edge(self, z):
1030
- self.Z = eels.get_z(z)
1031
- if self.cid is None:
1032
- self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.onresize)
1033
- self.update()
1034
-
1035
- def onresize(self, event):
1036
- self.update()
1037
-
1038
- def update(self):
1039
- if self.labels is not None:
1040
- for label in self.labels:
1041
- label.remove()
1042
- if self.lines is not None:
1043
- for line in self.lines:
1044
- line.remove()
1045
- self.labels = []
1046
- self.lines = []
1047
- x_min, x_max = self.ax.get_xlim()
1048
- y_min, y_max = self.ax.get_ylim()
1049
-
1050
- element = str(self.Z)
1051
- x_sections = self.x_sections
1052
- for key in all_edges:
1053
- if key in x_sections[element] and 'onset' in x_sections[element][key]:
1054
- x = x_sections[element][key]['onset']
1055
- if x_min < x < x_max:
1056
- if key in first_close_edges:
1057
- label2 = self.ax.text(x, y_max, f"{x_sections[element]['name']}-{key}",
1058
- verticalalignment='top', rotation=0, color=self.color)
1059
- else:
1060
- label2 = self.ax.text(x, y_max, f"\n{x_sections[element]['name']}-{key}",
1061
- verticalalignment='top', color=self.color)
1062
- line2 = self.ax.axvline(x, ymin=0, ymax=1, color=self.color)
1063
-
1064
- self.labels.append(label2)
1065
- self.lines.append(line2)
1066
-
1067
- def reconnect(self):
1068
- self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.onresize)
1069
- self.update()
1070
-
1071
- def disconnect(self):
1072
- if self.labels is not None:
1073
- for label in self.labels:
1074
- label.remove()
1075
- if self.lines is not None:
1076
- for line in self.lines:
1077
- line.remove()
1078
- self.labels = None
1079
- self.lines = None
1080
- self.ax.figure.canvas.mpl_disconnect(self.cid)
1081
-
1082
-
1083
- class RegionSelector(object):
1084
- """
1085
- Selects fitting region and the regions that are excluded for each edge.
1086
-
1087
- Select a region with a spanSelector and then type 'a' for all of the fitting region or a number for the
1088
- edge you want to define the region excluded from the fit (solid state effects).
1089
-
1090
- see Chapter4 'CH4-Working_with_X-Sections,ipynb' notebook
1091
-
1092
- """
1093
-
1094
- def __init__(self, ax):
1095
- self.ax = ax
1096
- self.regions = {}
1097
- self.rect = None
1098
- self.xmin = 0
1099
- self.xwidth = 0
1100
-
1101
- self.span = SpanSelector(ax, self.onselect1, 'horizontal', useblit=True,
1102
- rectprops=dict(alpha=0.5, facecolor='red'), span_stays=True)
1103
- self.cid = ax.figure.canvas.mpl_connect('key_press_event', self.click)
1104
- self.draw = ax.figure.canvas.mpl_connect('draw_event', self.onresize)
1105
-
1106
- def onselect1(self, xmin, xmax):
1107
- self.xmin = xmin
1108
- self.width = xmax - xmin
1109
-
1110
- def onresize(self, event):
1111
- self.update()
1112
-
1113
- def delete_region(self, key):
1114
- if key in self.regions:
1115
- if 'Rect' in self.regions[key]:
1116
- self.regions[key]['Rect'].remove()
1117
- self.regions[key]['Text'].remove()
1118
- del (self.regions[key])
1119
-
1120
- def update(self):
1121
-
1122
- y_min, y_max = self.ax.get_ylim()
1123
- for key in self.regions:
1124
- if 'Rect' in self.regions[key]:
1125
- self.regions[key]['Rect'].remove()
1126
- self.regions[key]['Text'].remove()
1127
-
1128
- xmin = self.regions[key]['xmin']
1129
- width = self.regions[key]['width']
1130
- height = y_max - y_min
1131
- alpha = self.regions[key]['alpha']
1132
- color = self.regions[key]['color']
1133
- self.regions[key]['Rect'] = patches.Rectangle((xmin, y_min), width, height,
1134
- edgecolor=color, alpha=alpha, facecolor=color)
1135
- self.ax.add_patch(self.regions[key]['Rect'])
1136
-
1137
- self.regions[key]['Text'] = self.ax.text(xmin, y_max, self.regions[key]['text'], verticalalignment='top')
1138
-
1139
- def click(self, event):
1140
- if str(event.key) in ['1', '2', '3', '4', '5', '6']:
1141
- key = str(event.key)
1142
- text = 'exclude \nedge ' + key
1143
- alpha = 0.5
1144
- color = 'red'
1145
- elif str(event.key) in ['a', 'A', 'b', 'B', 'f', 'F']:
1146
- key = '0'
1147
- color = 'blue'
1148
- alpha = 0.2
1149
- text = 'fit region'
1150
- else:
1151
- return
1152
-
1153
- if key not in self.regions:
1154
- self.regions[key] = {}
1155
-
1156
- self.regions[key]['xmin'] = self.xmin
1157
- self.regions[key]['width'] = self.width
1158
- self.regions[key]['color'] = color
1159
- self.regions[key]['alpha'] = alpha
1160
- self.regions[key]['text'] = text
1161
-
1162
- self.update()
1163
-
1164
- def set_regions(self, region, start_x, width):
1165
- if 'fit' in str(region):
1166
- key = '0'
1167
- if region in ['0', '1', '2', '3', '4', '5', '6']:
1168
- key = region
1169
- if region in [0, 1, 2, 3, 4, 5, 6]:
1170
- key = str(region)
1171
-
1172
- if key not in self.regions:
1173
- self.regions[key] = {}
1174
- if key in ['1', '2', '3', '4', '5', '6']:
1175
- self.regions[key]['text'] = 'exclude \nedge ' + key
1176
- self.regions[key]['alpha'] = 0.5
1177
- self.regions[key]['color'] = 'red'
1178
- elif key == '0':
1179
- self.regions[key]['text'] = 'fit region'
1180
- self.regions[key]['alpha'] = 0.2
1181
- self.regions[key]['color'] = 'blue'
1182
-
1183
- self.regions[key]['xmin'] = start_x
1184
- self.regions[key]['width'] = width
1185
-
1186
- self.update()
1187
-
1188
- def get_regions(self):
1189
- tags = {}
1190
- for key in self.regions:
1191
- if key == '0':
1192
- area = 'fit_area'
1193
- else:
1194
- area = key
1195
- tags[area] = {}
1196
- tags[area]['start_x'] = self.regions[key]['xmin']
1197
- tags[area]['width_x'] = self.regions[key]['width']
1198
-
1199
- return tags
1200
-
1201
- def disconnect(self):
1202
- for key in self.regions:
1203
- if 'Rect' in self.regions[key]:
1204
- self.regions[key]['Rect'].remove()
1205
- self.regions[key]['Text'].remove()
1206
- del self.span
1207
- self.ax.figure.canvas.mpl_disconnect(self.cid)
1208
- # self.ax.figure.canvas.mpl_disconnect(self.draw)
1209
- pass
1210
-
1211
-
1212
- class EdgesatCursor(object):
1213
- """
1214
- Adds a Cursor to a plot, which plots all major (possible) ionization edges at
1215
- the cursor location if left (right) mosue button is clicked.
1216
-
1217
- The necessary initializtion parameter are:
1218
- ax: matplotlib axis
1219
- x: energy_scale of spectrum
1220
- y: intensities of spectrum
1221
-
1222
- There is an optional parameter maximum_chemical_shift which allows to change
1223
- the energy range in which the edges are searched.
1224
-
1225
- usage:
1226
- fig, ax = plt.subplots()
1227
- ax.plot(energy_scale, spectrum)
1228
- cursor = EdgesatCursor(ax, energy_scale, spectrum)
1229
-
1230
- see Chapter4 'CH4-Working_with_X-Sections' notebook
1231
-
1232
- """
1233
-
1234
- def __init__(self, ax, x, y, maximal_chemical_shift=5):
1235
- self.ax = ax
1236
- self.ly = ax.axvline(x[0], color='k', alpha=0.2) # the vert line
1237
- self.marker, = ax.plot(x[0], y[0], marker="o", color="crimson", zorder=3)
1238
- self.x = x
1239
- self.y = y
1240
- self.txt = ax.text(0.7, 0.9, '', verticalalignment='bottom')
1241
- self.select = 0
1242
- self.label = None
1243
- self.line = None
1244
- self.cid = ax.figure.canvas.mpl_connect('button_press_event', self.click)
1245
- self.mouse_cid = ax.figure.canvas.mpl_connect('motion_notify_event', self.mouse_move)
1246
- self.maximal_chemical_shift = maximal_chemical_shift
1247
-
1248
- def click(self, event):
1249
-
1250
- # print('click', event)
1251
- if not event.inaxes:
1252
- return
1253
- x, y = event.xdata, event.ydata
1254
-
1255
- indx = np.searchsorted(self.x, [x])[0]
1256
- x = self.x[indx]
1257
- y = self.y[indx]
1258
- self.select = x
1259
-
1260
- y_min, y_max = self.ax.get_ylim()
1261
-
1262
- if self.label is not None:
1263
- self.label.remove()
1264
- self.line.remove()
1265
- if event.button == 1:
1266
- self.label = self.ax.text(x, y_max, eels.find_major_edges(event.xdata, self.maximal_chemical_shift),
1267
- verticalalignment='top')
1268
- self.line, = self.ax.plot([x, x], [y_min, y_max], color='black')
1269
- if event.button == 3:
1270
- self.line, = self.ax.plot([x, x], [y_min, y_max], color='black')
1271
- self.label = self.ax.text(x, y_max, eels.find_all_edges(event.xdata, self.maximal_chemical_shift),
1272
- verticalalignment='top')
1273
- self.ax.set_ylim(y_min, y_max)
1274
-
1275
- def mouse_move(self, event):
1276
-
1277
- if not event.inaxes:
1278
- return
1279
-
1280
- x, y = event.xdata, event.ydata
1281
- indx = np.searchsorted(self.x, [x])[0]
1282
- x = self.x[indx]
1283
- y = self.y[indx]
1284
- self.select = x
1285
- self.ly.set_xdata(x)
1286
- self.marker.set_data([x], [y])
1287
- self.txt.set_text(f'\n x={x:1.2f}, y={y:1.2g}\n')
1288
-
1289
- # self.ax.text(x, y*2,find_major_edges(x))
1290
- self.txt.set_position((x, y))
1291
- self.ax.figure.canvas.draw_idle()
1292
-
1293
- def del_edges(self):
1294
- if self.label is not None:
1295
- self.label.remove()
1296
- self.line.remove()
1297
- self.label = None
1298
-
1299
- def disconnect(self):
1300
- self.ly.remove()
1301
- self.marker.remove()
1302
- self.txt.remove()
1303
-
1304
- self.ax.figure.canvas.mpl_disconnect(self.cid)
1305
- self.ax.figure.canvas.mpl_disconnect(self.mouse_cid)
1306
-
1307
-
1308
- class ElementalEdges(object):
1309
- """
1310
- The necessary initialization parameter are:
1311
- ax: matplotlib axis
1312
-
1313
-
1314
- There is an optional parameter maximum_chemical_shift which allows to change
1315
- the energy range in which the edges are searched.
1316
-
1317
- available functions:
1318
- - update(): updates the drawing of ionization edges
1319
- - set_edge(z) : changes atomic number and updates everything accordingly
1320
- - disconnect: makes everything invisible and stops drawing
1321
- - reconnect: undo of disconnect
1322
-
1323
- usage:
1324
- >> fig, ax = plt.subplots()
1325
- >> ax.plot(energy_scale, spectrum)
1326
- >> z= 42
1327
- >> cursor = ElementalEdges(ax, z)
1328
-
1329
-
1330
- see Chapter4 'CH4-Working_with_X-Sections' notebook
1331
- """
1332
-
1333
- def __init__(self, ax, z):
1334
- self.ax = ax
1335
- self.labels = None
1336
- self.lines = None
1337
- self.z = eels.get_z(z)
1338
- self.color = 'black'
1339
- self.x_sections = eels.get_x_sections()
1340
- self.cid = ax.figure.canvas.mpl_connect('draw_event', self.on_resize)
1341
- # self.update() is not necessary because of a drawing event is issued
1342
-
1343
- def set_edge(self, z):
1344
- self.z = eels.get_z(z)
1345
- if self.cid is None:
1346
- self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.on_resize)
1347
- self.update()
1348
-
1349
- def on_resize(self, event):
1350
- self.update()
1351
-
1352
- def update(self):
1353
- if self.labels is not None:
1354
- for label in self.labels:
1355
- label.remove()
1356
- if self.lines is not None:
1357
- for line in self.lines:
1358
- line.remove()
1359
- self.labels = []
1360
- self.lines = []
1361
- x_min, x_max = self.ax.get_xlim()
1362
- y_min, y_max = self.ax.get_ylim()
1363
-
1364
- element = str(self.z)
1365
- x_sections = self.x_sections
1366
- for key in all_edges:
1367
- if key in x_sections[element]:
1368
- if 'onset' in x_sections[element][key]:
1369
- x = x_sections[element][key]['onset']
1370
- if x_min < x < x_max:
1371
- if key in first_close_edges:
1372
- label2 = self.ax.text(x, y_max, f"{x_sections[element]['name']}-{key}",
1373
- verticalalignment='top', rotation=0, color=self.color)
1374
- else:
1375
- label2 = self.ax.text(x, y_max, f"\n{x_sections[element]['name']}-{key}",
1376
- verticalalignment='top', color=self.color)
1377
- line2 = self.ax.axvline(x, ymin=0, ymax=1, color=self.color)
1378
-
1379
- self.labels.append(label2)
1380
- self.lines.append(line2)
1381
-
1382
- def reconnect(self):
1383
- self.cid = self.ax.figure.canvas.mpl_connect('draw_event', self.on_resize)
1384
- self.update()
1385
-
1386
- def disconnect(self):
1387
- if self.labels is not None:
1388
- for label in self.labels:
1389
- label.remove()
1390
- if self.lines is not None:
1391
- for line in self.lines:
1392
- line.remove()
1393
- self.labels = None
1394
- self.lines = None
1395
- self.ax.figure.canvas.mpl_disconnect(self.cid)
1396
-
1397
-
1398
- class RegionSelector(object):
1399
- """
1400
- Selects fitting region and the regions that are excluded for each edge.
1401
-
1402
- Select a region with a spanSelector and then type 'a' for all of the fitting region or a number for the edge
1403
- you want to define the region excluded from the fit (solid state effects).
1404
-
1405
- see Chapter4 'CH4-Working_with_X-Sections,ipynb' notebook
1406
-
1407
- """
1408
-
1409
- def __init__(self, ax):
1410
- self.ax = ax
1411
- self.regions = {}
1412
- self.rect = None
1413
- self.xmin = 0
1414
- self.width = 0
1415
-
1416
- self.span = SpanSelector(ax, self.onselect1, 'horizontal', useblit=True,
1417
- rectprops=dict(alpha=0.5, facecolor='red'), span_stays=True)
1418
- self.cid = ax.figure.canvas.mpl_connect('key_press_event', self.click)
1419
- self.draw = ax.figure.canvas.mpl_connect('draw_event', self.on_resize)
1420
-
1421
- def onselect1(self, xmin, xmax):
1422
- self.xmin = xmin
1423
- self.width = xmax - xmin
1424
-
1425
- def on_resize(self, event):
1426
- self.update()
1427
-
1428
- def delete_region(self, key):
1429
- if key in self.regions:
1430
- if 'Rect' in self.regions[key]:
1431
- self.regions[key]['Rect'].remove()
1432
- self.regions[key]['Text'].remove()
1433
- del (self.regions[key])
1434
-
1435
- def update(self):
1436
-
1437
- y_min, y_max = self.ax.get_ylim()
1438
- for key in self.regions:
1439
- if 'Rect' in self.regions[key]:
1440
- self.regions[key]['Rect'].remove()
1441
- self.regions[key]['Text'].remove()
1442
-
1443
- xmin = self.regions[key]['xmin']
1444
- width = self.regions[key]['width']
1445
- height = y_max - y_min
1446
- alpha = self.regions[key]['alpha']
1447
- color = self.regions[key]['color']
1448
- self.regions[key]['Rect'] = patches.Rectangle((xmin, y_min), width, height,
1449
- edgecolor=color, alpha=alpha, facecolor=color)
1450
- self.ax.add_patch(self.regions[key]['Rect'])
1451
-
1452
- self.regions[key]['Text'] = self.ax.text(xmin, y_max, self.regions[key]['text'], verticalalignment='top')
1453
-
1454
- def click(self, event):
1455
- if str(event.key) in ['1', '2', '3', '4', '5', '6']:
1456
- key = str(event.key)
1457
- text = 'exclude \nedge ' + key
1458
- alpha = 0.5
1459
- color = 'red'
1460
- elif str(event.key) in ['a', 'A', 'B', 'b', 'f', 'F']:
1461
- key = '0'
1462
- color = 'blue'
1463
- alpha = 0.2
1464
- text = 'fit region'
1465
- else:
1466
- return
1467
-
1468
- if key not in self.regions:
1469
- self.regions[key] = {}
1470
-
1471
- self.regions[key]['xmin'] = self.xmin
1472
- self.regions[key]['width'] = self.width
1473
- self.regions[key]['color'] = color
1474
- self.regions[key]['alpha'] = alpha
1475
- self.regions[key]['text'] = text
1476
-
1477
- self.update()
1478
-
1479
- def set_regions(self, region, start_x, width):
1480
- if 'fit' in str(region):
1481
- key = '0'
1482
- if region in ['0', '1', '2', '3', '4', '5', '6']:
1483
- key = region
1484
- if region in [0, 1, 2, 3, 4, 5, 6]:
1485
- key = str(region)
1486
-
1487
- if key not in self.regions:
1488
- self.regions[key] = {}
1489
- if key in ['1', '2', '3', '4', '5', '6']:
1490
- self.regions[key]['text'] = 'exclude \nedge ' + key
1491
- self.regions[key]['alpha'] = 0.5
1492
- self.regions[key]['color'] = 'red'
1493
- elif key == '0':
1494
- self.regions[key]['text'] = 'fit region'
1495
- self.regions[key]['alpha'] = 0.2
1496
- self.regions[key]['color'] = 'blue'
1497
-
1498
- self.regions[key]['xmin'] = start_x
1499
- self.regions[key]['width'] = width
1500
-
1501
- self.update()
1502
-
1503
- def get_regions(self):
1504
- tags = {}
1505
- for key in self.regions:
1506
- if key == '0':
1507
- area = 'fit_area'
1508
- else:
1509
- area = key
1510
- tags[area] = {}
1511
- tags[area]['start_x'] = self.regions[key]['xmin']
1512
- tags[area]['width_x'] = self.regions[key]['width']
1513
-
1514
- return tags
1515
-
1516
- def disconnect(self):
1517
- for key in self.regions:
1518
- if 'Rect' in self.regions[key]:
1519
- self.regions[key]['Rect'].remove()
1520
- self.regions[key]['Text'].remove()
1521
- del self.span
1522
- self.ax.figure.canvas.mpl_disconnect(self.cid)
1523
- # self.ax.figure.canvas.mpl_disconnect(self.draw)
1524
- pass
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()