pyTEMlib 0.2025.4.2__py3-none-any.whl → 0.2025.9.1__py3-none-any.whl

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

Potentially problematic release.


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

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