Chapter 4: Spectroscopy
Analysis of Core-Loss Spectra¶
Try this notebook in Google Colab
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2025
by Gerd Duscher
Microscopy Facilities
Institute of Advanced Materials & Manufacturing
Materials Science & Engineering
The University of Tennessee, Knoxville
Background and methods to analysis and quantification of data acquired with transmission electron microscopes.
Content¶
Quantitative determination of chemical composition from a core-loss EELS spectrum
Please cite:
as a reference of this quantification method.
Load important packages¶
Check Installed Packages¶
import sys
import os
import importlib.metadata
def test_package(package_name):
"""Test if package exists and returns version or -1"""
try:
version = importlib.metadata.version(package_name)
except importlib.metadata.PackageNotFoundError:
version = '-1'
return version
if test_package('pyTEMlib') < '0.2024.4.0':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
print('done')done
Import all relevant libraries¶
Please note that the EELS_tools package from pyTEMlib is essential.
import sys
%matplotlib ipympl
if 'google.colab' in sys.modules:
from google.colab import output
from google.colab import drive
output.enable_custom_widget_manager()
import numpy as np
import matplotlib.pylab as plt
import pyTEMlib
# For archiving reasons it is a good idea to print the version numbers out at this point
print('pyTEM version: ',pyTEMlib.__version__)
__notebook__ = 'analyse_core_loss'
__notebook_version__ = '2026_01_19'
if 'google.colab' in sys.modules:
drive.mount("/content/drive")
The autoreload extension is already loaded. To reload it, use:
%reload_ext autoreload
pyTEM version: 0.2026.1.0
Load and plot a spectrum¶
As an example we load the spectrum 1EELS Acquire (high-loss).dm3 from the example data folder.
First a dialog to select a file will apear.
Once you have selected a main datase, we can plot it in the next code cell
Please use the Energy Scale button in the Info tab to change the energy scale. When pressed the start and end energies of the selected cursor will be used to reculculate the energy scale
if 'google.colab' in sys.modules:
if not os.path.exists('./1EELS Acquire (high-loss).dm3'):
!wget https://github.com/gduscher/MSE672-Introduction-to-TEM/raw/main/example_data/1EELS Acquire (high-loss).dm3
!wget https://github.com/gduscher/MSE672-Introduction-to-TEM/raw/main/example_data/1EELS Acquire (low-loss).dm3
example_path = "."
else:
example_path = os.path.join(os.path.abspath(""), "../example_data")
file_widget = pyTEMlib.file_tools.FileWidget(dir_name=example_path)C:\Users\gduscher\OneDrive - University of Tennessee\GitHub\MSE672-Introduction-to-TEM\Spectroscopy\../example_data
pyTEMlib.file_tools.open_file(example_path+'\EDS.rto')---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[33], line 1
----> 1 pyTEMlib.file_tools.open_file(example_path+'\EDS.rto')
File ~\OneDrive - University of Tennessee\GitHub\MSE672-Introduction-to-TEM\Spectroscopy\../../pyTEMlib\pyTEMlib\file_tools.py:466, in open_file(filename, write_hdf_file, sum_frames, sum_eds, eds_stream)
463 provenance = 'SciFiReader.NionReader'
465 elif extension in ['.rto']:
--> 466 reader = SciFiReaders.BrukerReader(filename)
467 provenance = 'SciFiReader.BrukerReader'
469 elif extension in ['.mrc']:
File ~\AppData\Local\anaconda3\Lib\site-packages\SciFiReaders\readers\microscopy\em\sem\bruker_reader.py:249, in BrukerReader.__init__(self, file_path, verbose)
247 if 'rto' in self.extension:
248 try:
--> 249 self.tags = get_bruker_dictionary(file_path)
251 except IOError:
252 raise IOError(f"File {self.__filename} does not seem to be of Bruker's .rto format")
File ~\AppData\Local\anaconda3\Lib\site-packages\SciFiReaders\readers\microscopy\em\sem\bruker_reader.py:142, in get_bruker_dictionary(filename)
140 tags['spectrum'][spectrum_number]['results'][result_tag['Atom']] = {}
141 tags['spectrum'][spectrum_number]['results'][result_tag['Atom']].update(result_tag)
--> 142 tags['spectrum'][spectrum_number]['data'] = np.frombuffer(spectrum.find('./Channels').text,
143 dtype='np.int16', sep=",")
144 spectrum_number += 1
145 return tags
TypeError: data type 'np.int16' not understoodimport numpy as np
# Creating arrays with numpy.int16 and numpy.uint16 types
data_int16 = np.array([32767, -32768], dtype=np.int16)
data_uint16 = np.array([65535, 0], dtype=np.uint16)datasets = file_widget.datasets
main_dataset = file_widget.selected_dataset
main_dataset.plot()
main_dataset.energy_loss[0], main_dataset.energy_loss[1](np.float64(-50.0), np.float64(-49.75))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[14], line 1
----> 1 quantification = pyTEMlib.eels_dialog.CompositionWidget(datasets=datasets, key='Channel_000')
AttributeError: module 'pyTEMlib' has no attribute 'eels_dialog'import pyTEMlib.info_widget
eelsWidget = pyTEMlib.info_widget.EELSWidget(".")eelsWidget.dataset.metadata{'edges': {'fit_area': {'fit_start': 150.5, 'fit_end': 609.5},
'0': {'z': 5,
'symmetry': 'K1',
'element': 'B',
'onset': 188.0,
'end_exclude': 238.0,
'start_exclude': 183.0,
'all_edges': {'K1': {'onset': 188.0}},
'chemical_shift': 0.0,
'areal_density': 15370.259182290087,
'original_onset': 188.0,
'data': array([5.04702221e-09, 5.00509797e-09, 4.96317372e-09, ...,
1.62111761e-10, 1.61818311e-10, 1.61524861e-10]),
'X_section_type': 'XRPA',
'X_section_source': 'pyTEMlib'},
'1': {'z': 7,
'symmetry': 'K1',
'element': 'N',
'onset': 401.6,
'end_exclude': 451.6,
'start_exclude': 396.6,
'all_edges': {'K1': {'onset': 401.6}},
'chemical_shift': 0.0,
'areal_density': 15370.259182290087,
'original_onset': 401.6,
'data': array([2.00956373e-08, 1.99304356e-08, 1.97652339e-08, ...,
5.36017470e-10, 5.35104535e-10, 5.34191601e-10]),
'X_section_type': 'XRPA',
'X_section_source': 'pyTEMlib'},
'model': {'background': energy_loss: energy-loss (eV) of size (2048,),
'background-poly_0': 10.0,
'background-poly_1': 1.0,
'background-poly_2': 0.0,
'background-A': 480931016.6195635,
'background-r': -1.8392560877024688,
'spectrum': energy_loss: energy-loss (eV) of size (2048,),
'blurred': array([139757.54409064, 139617.83997849, 139350.37663315, ...,
658.92534137, 628.73955145, 612.56324436]),
'mask': array([0., 0., 0., ..., 0., 0., 1.]),
'fit_parameter': array([ 4.80931017e+08, -1.83925609e+00, 1.00000000e+01, 1.00000000e+00,
0.00000000e+00, 1.53702592e+04, 1.53702592e+04]),
'fit_area_start': 150.5,
'fit_area_end': 609.5,
'xsec': array([[5.04702221e-09, 5.00509797e-09, 4.96317372e-09, ...,
1.62111761e-10, 1.61818311e-10, 1.61524861e-10],
[2.00956373e-08, 1.99304356e-08, 1.97652339e-08, ...,
5.36017470e-10, 5.35104535e-10, 5.34191601e-10]])}},
'experiment': {'convergence_angle': 30,
'collection_angle': 40,
'acceleration_voltage': 60000,
'eff_beta': 33.635205259094015}}eelsWidget.core_loss.dataset.metadata['experiment'] = {'convergence_angle': 30,
'collection_angle': 40,
'acceleration_voltage': 60}Chemical Composition¶
The fit of the cross-section and background to the spectrum results in the chemical composition. If the calibration is correct this composition is given as areal density in atoms/nm
Fit of Data¶
A dialog window will open, enter the elements first (0 will open a periodic table) and press
Fit Composition button (bottom right). Adjust parameters as needed and check fit by pressing the Fit Composition button again.
Select the Region checkbox to see which parts of the spectrum you choose to fit.
Changing the multiplier value will make a simulation of your spectrum.
The InfoDialog, if open, still works to change experimental parameters and the energy scale.
found_edges = eels.auto_id_edges(quantification .dataset)
found_edges
to_delete = []
if len(found_edges) >0:
for key in quantification.edges:
if key.isdigit():
to_delete.append(key)
for key in to_delete:
del quantification.edges[key]
if '0' not in quantification.edges:
quantification.edges['0'] = {}
selected_elements = []
for key in found_edges:
selected_elements.append(key)
#view.set_elements(selected_elements)
for index, elem in enumerate(selected_elements):
print(index)
quantification.update_element(elem, index)
quantification.update()---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[18], line 1
----> 1 found_edges = eels.auto_id_edges(quantification .dataset)
2 found_edges
3 to_delete = []
NameError: name 'quantification' is not definedmain_dataset.original_metadata{'ImageData': {'Calibrations': {'Brightness': {'Origin': 0.0,
'Scale': 1.0,
'Units': 'Counts'},
'Dimension': {'0': {'Origin': -1400.0, 'Scale': 0.25, 'Units': 'eV'}},
'DisplayCalibratedUnits': 1},
'Data': 'read',
'DataType': 2,
'Dimensions': {'0': 2048},
'PixelDepth': 4},
'ImageTags': {'Acquisition': {'Device': {'Active Size (pixels)': [2048, 520],
'Camera Number': 0,
'CCD': {'Pixel Size (um)': [14.0, 14.0]},
'Configuration': {'Transpose': {'Diagonal Flip': 0,
'Horizontal Flip': 0,
'Vertical Flip': 0}},
'Name': 'QUEFINA 1',
'Source': 'QUEFINA 1'},
'Frame': {'Area': {'Transform': {'Class Name': 'cm_acquisitiontransform_list',
'Transform List': {'0': {'Binning': [1, 130],
'Class Name': 'cm_acquisitiontransform',
'Sub Area Adjust': [0, 0, 0, 0],
'Transpose': {'Diagonal Flip': 0,
'Horizontal Flip': 0,
'Vertical Flip': 0}}}}},
'CCD': {'Pixel Size (um)': [1820.0, 14.0]},
'Intensity': {'Transform': {'Class Name': 'cm_valuetransform_list',
'Transform List': {'0': {'Class Name': 'cm_valuetransform_affine',
'Offset': 250.0,
'Scale': 1.0},
'1': {'ADC Max': 65535.0,
'ADC Min': 0.0,
'Class Name': 'cm_valuetransform_adc'}}}}},
'Parameters': {'Acquisition Write Flags': 4294967295,
'Base Detector': {'Class Name': 'cm_namedcameradetectorparameterset',
'Name': 'default'},
'Detector': {'continuous': 1,
'exposure (s)': 2.0,
'hbin': 1,
'height': 260,
'left': 0,
'top': 130,
'vbin': 130,
'width': 2048},
'Environment': {'Mode Name': 'Spectroscopy'},
'High Level': {'Acquisition Buffer Size': 0,
'Antiblooming': 0,
'Binning': [1, 130],
'CCD Read Area': [130, 0, 390, 2048],
'CCD Read Ports': 1,
'Choose Number Of Frame Shutters Automatically': 1,
'Class Name': 'cm_camera_highlevelparameters',
'Continuous Readout': 1,
'Corrections': 305,
'Corrections Mask': 817,
'Exposure (s)': 2.0,
'Number Of Frame Shutters': 1,
'Processing': 'Dark Subtracted',
'Quality Level': 1,
'Read Frame Style': 0,
'Read Mode': 13,
'Secondary Shutter Post Exposure Compensation (s)': 0.0,
'Secondary Shutter Pre Exposure Compensation (s)': 0.0,
'Shutter': {'Primary Shutter States': 0,
'Primary Shutter States Mask': 0,
'Secondary Shutter States': 0,
'Secondary Shutter States Mask': 0,
'Shutter Exposure': 0,
'Shutter Index': 0},
'Shutter Post Exposure Compensation (s)': 0.0,
'Shutter Pre Exposure Compensation (s)': 0.0,
'Transform': {'Diagonal Flip': 0,
'Horizontal Flip': 0,
'Vertical Flip': 0}},
'Objects': {'0': {'Class Name': 'cm_imgproc_finalcombine',
'Frame Combine Style': 'Copy',
'Parameter 1': 1.0}},
'Parameter Set Name': 'Acquire',
'Parameter Set Tag Path': 'Spectroscopy:Acquire:Acquire',
'Version': 33947648}},
'EELS': {'Acquisition': {'Continuous mode': 0,
'Date': '12/14/2019',
'End time': '1:31:04 PM',
'Exposure (s)': 2.0,
'Integration time (s)': 20.0,
'Number of frames': 10,
'Saturation fraction': 0.21766255795955658,
'Start time': '1:30:41 PM'},
'Experimental Conditions': {'Collection semi-angle (mrad)': 33.0,
'Convergence semi-angle (mrad)': 30.0}},
'EELS Spectrometer': {'Dispersion (eV/ch)': 0.25,
'Dispersion index': 4,
'Drift tube enabled': 1,
'Drift tube voltage (V)': 400.0,
'Energy loss (eV)': 400.0,
'HT offset (V)': 0.0,
'HT offset enabled': 0.0,
'Instrument ID': 3977,
'Instrument name': 'Enfinium ER NION',
'Mode': 0,
'Prism offset (V)': -0.0,
'Prism offset enabled ': 1},
'Meta Data': {'Acquisition Mode': 'Parallel dispersive',
'Format': 'Spectrum',
'Signal': 'EELS'},
'Microscope Info': {'Cs(mm)': 0.0,
'Emission Current (A)': 0.0,
'Formatted Indicated Mag': '100x',
'Formatted Voltage': '200kV',
'Illumination Mode': 'STEM',
'Imaging Mode': 'IMAGING',
'Indicated Magnification': 100.0,
'Name': 'Unknown',
'Operation Mode': 'SCANNING',
'Probe Current (nA)': 0.0,
'Probe Size (nm)': 0.0,
'STEM Camera Length': 2.8,
'Voltage': 200000.0},
'Session Info': {'Items': {'0': {'Data Type': 20,
'Label': 'Specimen',
'Precision': 0,
'Tag path': 'Session Info:Specimen',
'Units': '',
'Value': ''},
'1': {'Data Type': 20,
'Label': 'Operator',
'Precision': 0,
'Tag path': 'Session Info:Operator',
'Units': '',
'Value': ''},
'2': {'Data Type': 20,
'Label': 'Microscope',
'Precision': 0,
'Tag path': 'Session Info:Microscope',
'Units': '',
'Value': ''}},
'Microscope': '',
'Operator': '',
'Specimen': ''}},
'Name': '01-EELS Acquire_STO',
'UniqueID': {'0': 774116825, '1': 412555428, '2': 1897604613, '3': 952377132},
'DM': {'dm_version': 3,
'file_size': 322611,
'full_file_name': 'C:\\Users\\gduscher\\Documents\\Github\\MSE672-Introduction-to-TEM\\example_data\\EELS_STO.dm3'},
'original_filename': 'C:\\Users\\gduscher\\Documents\\Github\\MSE672-Introduction-to-TEM\\example_data\\EELS_STO.dm3',
'ApplicationBounds': [0, 0, 1065, 1916],
'DocumentObjectList': {'0': {'AnnotationGroupList': {},
'AnnotationType': 20,
'BackgroundColor': [-1, -1, -1],
'BackgroundMode': 2,
'FillMode': 2,
'ForegroundColor': [-1, 0, 0],
'HasBackground': 0,
'ImageDisplayInfo': {'BackgroundColor': [-5655, -5655, -5655],
'BackgroundOn': 1,
'CalibrationSliceId': {'0': 0},
'CaptionAttributes': 7,
'CaptionColor': [0, 0, 0],
'CaptionFaceName': 'Lucida Sans Unicode',
'CaptionOn': 1,
'CaptionSize': 10,
'CursorOn': 0,
'CursorPosition': 0.0,
'DimensionLabels': {'0': ''},
'DynamicContrast': 1,
'FrameOn': 1,
'GridColor': [0, -1, -1],
'GridOn': 1,
'GroupId': 0,
'GroupList': {'0': {'DoAutoSurveyHigh': 1,
'DoAutoSurveyLow': 1,
'GroupToDisplay': {'Offset': [-0.0, -0.0],
'Scale': [0.00048828125, 4.16666489400086e-06]},
'TrackStyleX': 0,
'TrackStyleY': 0}},
'LegendOn': 0,
'MainSliceId': {'0': 0},
'NumHorizontalTicks': 1,
'NumVerticalTicks': 1,
'SliceList': {'0': {'BaseIntensity': 0.0,
'ComplexMode': 4,
'DrawFill': 1,
'DrawLine': 0,
'DrawTransparent': 0,
'FillColor': [23644, -16449, -16449],
'Horz Pos Fixed': 1,
'Horz Scale Fixed': 1,
'ImageToGroup': {'Offset': [0.0, 0.0], 'Scale': [1.0, 1.0]},
'IsVisible': 1,
'LineColor': [0, -32640, -16449],
'LineStyle': 0,
'LineThickness': 1,
'SliceGroup': 0,
'SliceId': {'0': 0},
'Transparency': 0.0,
'TransparencyStyle': 0,
'Vert Pos Fixed': 1,
'Vert Scale Fixed': 1}},
'SurveyRegion': 1},
'ImageDisplayType': 3,
'ImageSource': 0,
'IsMoveable': 1,
'IsResizable': 1,
'IsSelectable': 1,
'IsTranslatable': 1,
'IsVisible': 1,
'ObjectTags': {},
'Rectangle': [0.0, 0.0, 342.0, 669.0],
'UniqueID': 8}},
'DocumentTags': {},
'HasWindowPosition': 1,
'Image Behavior': {'DoIntegralZoom': 0,
'ImageDisplayBounds': [0.0, 0.0, 342.0, 669.0],
'IsZoomedToWindow': 1,
'UnscaledTransform': {'Offset': [0.0, 0.0], 'Scale': [1.0, 1.0]},
'ViewDisplayID': 8,
'WindowRect': [0.0, 0.0, 342.0, 669.0],
'ZoomAndMoveTransform': {'Offset': [0.0, 0.0], 'Scale': [1.0, 1.0]}},
'ImageSourceList': {'0': {'ClassName': 'ImageSource:Simple',
'Extra Slice Info': {'0': {'Id': {'0': 0}, 'Label': 'Spectrum'}},
'Id': {'0': 0},
'ImageRef': 1}},
'InImageMode': 1,
'MinVersionList': {'0': {'RequiredVersion': 50659328}},
'NextDocumentObjectID': 9,
'Page Behavior': {'DoIntegralZoom': 0,
'DrawMargins': 1,
'DrawPaper': 1,
'IsFixedInPageMode': 0,
'IsZoomedToWindow': 1,
'LayedOut': 0,
'PageTransform': {'Offset': [0.0, 0.0], 'Scale': [1.0, 1.0]},
'RestoreImageDisplayBounds': [0.0, 0.0, 342.0, 669.5],
'RestoreImageDisplayID': 8,
'TargetDisplayID': 4294967295},
'PageSetup': {'General': [1, 1000, 8500, 11000, 1000, 1000, -1000, -1000],
'Win32': b'\x04\x00\x00\x004!\x00\x00\xf8*\x00\x00M\x01\x00\x00M\x01\x00\x00\xfa\x00\x00\x00\xfa\x00\x00\x00\xe8\x03\x00\x00\xe8\x03\x00\x00\xe8\x03\x00\x00\xe8\x03\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x18\x00',
'Win32_DevModeW': b'S\x00e\x00n\x00d\x00 \x00T\x00o\x00 \x00O\x00n\x00e\x00N\x00o\x00t\x00e\x00 \x002\x000\x001\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x06\xdc\x00\x0c\x03\x03\xff\x00\x00\x01\x00\x01\x00\xea\no\x08d\x00\x01\x00\x0f\x00X\x02\x02\x00\x01\x00X\x02\x02\x00\x00\x00L\x00e\x00t\x00t\x00e\x00r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00DINU"\x00\xd0\x00\x0c\x03\x00\x00\xc2\xac\x90Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd0\x00\x00\x00SMTJ\x00\x00\x00\x00\x10\x00\xc0\x00S\x00e\x00n\x00d\x00 \x00T\x00o\x00 \x00M\x00i\x00c\x00r\x00o\x00s\x00o\x00f\x00t\x00 \x00O\x00n\x00e\x00N\x00o\x00t\x00e\x00 \x002\x000\x001\x000\x00 \x00D\x00r\x00i\x00v\x00e\x00r\x00\x00\x00RESDLL\x00UniresDLL\x00PaperSize\x00LETTER\x00Orientation\x00PORTRAIT\x00Resolution\x00DPI600\x00ColorMode\x0024bpp\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'Win32_DevNamesW': b'\x04\x00*\x00?\x00\x00\x00S\x00e\x00n\x00d\x00 \x00T\x00o\x00 \x00M\x00i\x00c\x00r\x00o\x00s\x00o\x00f\x00t\x00 \x00O\x00n\x00e\x00N\x00o\x00t\x00e\x00 \x002\x000\x001\x000\x00 \x00D\x00r\x00i\x00v\x00e\x00r\x00\x00\x00S\x00e\x00n\x00d\x00 \x00T\x00o\x00 \x00O\x00n\x00e\x00N\x00o\x00t\x00e\x00 \x002\x000\x001\x000\x00\x00\x00n\x00u\x00l\x00:\x00\x00\x00'},
'SentinelList': {},
'Thumbnails': {'0': {'ImageIndex': 0, 'SourceSize_Pixels': [669, 342]}},
'WindowPosition': [410, 8, 752, 677]}plt.figure()
plt.plot(quantification.dataset.metadata['edges']['0']['data'])
plt.plot(quantification.dataset.metadata['edges']['1']['data'])
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[19], line 2
1 plt.figure()
----> 2 plt.plot(quantification.dataset.metadata['edges']['0']['data'])
3 plt.plot(quantification.dataset.metadata['edges']['1']['data'])
NameError: name 'quantification' is not definedview.update()
view.find_elements(1)datasets['Channel_000'].metadata['edges'] = {'0': {}, 'model': {}}
datasets['Channel_000'].metadata['edges']['use_low_loss']=False
composition = ieels.CompositionDialog(datasets)
view = composition.figure---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[48], line 1
----> 1 datasets['Channel_000'].metadata['edges'] = {'0': {}, 'model': {}}
2 datasets['Channel_000'].metadata['edges']['use_low_loss']=False
4 composition = ieels.CompositionDialog(datasets)
NameError: name 'datasets' is not definedOutput of Results¶
main_dataset = quantification.dataset
edges = main_dataset.metadata['edges']
element = []
areal_density = []
for key, edge in edges.items():
if key.isdigit():
element.append(edge['element'])
areal_density.append(edge['areal_density'])
print('Relative chemical composition of ', main_dataset.title)
for i in range(len(element)):
print(f'{element[i]}: {areal_density[i]/np.sum(areal_density)*100:.1f} %')
saved_edges_metadata = edges
Relative chemical composition of 2EELS Acquire (high-loss)
B: 23.5 %
N: 76.5 %
edges{'model': {'background': array([-4.04651023e+14, -4.06877535e+14, -4.09110155e+14, ...,
-1.77235423e+16, -1.77382597e+16, -1.77529832e+16]),
'background-poly_0': -44450023942.34783,
'background-poly_1': -132.73805028510603,
'background-poly_2': 0.12179848936383898,
'background-A': 44450059487.20357,
'background-r': -43.130456550694994,
'spectrum': array([108808.06931201, 107956.87904715, 107105.70551897, ...,
2137.48357259, 2140.30517608, 2143.14351626]),
'blurred': array([139757.55 , 139617.84 , 139350.38 , ..., 658.92535,
628.73956, 612.56323], dtype=float32),
'mask': array([0., 0., 0., ..., 0., 0., 1.]),
'fit_parameter': array([ 4.44500595e+10, -4.31304566e+01, -4.44500239e+10, -1.32738050e+02,
1.21798489e-01, 1.08102056e+12, 2.93671332e+12]),
'fit_area_start': 150.0,
'fit_area_end': 611.75},
'use_low_loss': False,
'fit_area': {'fit_start': 150.0, 'fit_end': 611.75},
'0': {'z': 5,
'symmetry': 'K1',
'element': 'B',
'onset': 188.0,
'end_exclude': 238.0,
'start_exclude': 183.0,
'all_edges': {'K1': {'onset': 188.0}},
'chemical_shift': 0.0,
'areal_density': 1081020558242.3635,
'original_onset': 188.0,
'data': array([6.65067724e-09, 6.58530109e-09, 6.51992495e-09, ...,
1.69707960e-10, 1.69447463e-10, 1.69186967e-10]),
'X_section_type': 'XRPA',
'X_section_source': 'pyTEMlib'},
'1': {'z': 7,
'symmetry': 'K1',
'element': 'N',
'onset': 401.6,
'end_exclude': 451.6,
'start_exclude': 386.6,
'all_edges': {'K1': {'onset': 401.6}},
'chemical_shift': 0.0,
'areal_density': 2936713317469.3164,
'original_onset': 401.6,
'data': array([2.64344212e-08, 2.61784122e-08, 2.59224032e-08, ...,
5.65933272e-10, 5.65105461e-10, 5.64277650e-10]),
'X_section_type': 'XRPA',
'X_section_source': 'pyTEMlib'}}2936713317469/ 1081020558242
2.716611904444078B_areal_density = edges['0']['areal_density'] /main_dataset.metadata['experiment']['flux_ppm']*1e-6
N_areal_density = edges['1']['areal_density'] /main_dataset.metadata['experiment']['flux_ppm']*1e-6
#the B atom areal density of a single layer of h-BN (18.2 nm−2)
print(f" B areal density is {B_areal_density:.0f} atoms per square nm, which equates {abs(B_areal_density)/18.2:.1f} atomic layers")
print(f" N areal density is {N_areal_density:.0f} atoms per square nm, which equates {abs(N_areal_density)/18.2:.1f} atomic layers")Log Data¶
We write all the data to the hdf5 file associated with our dataset.
In our case that is only the metadata, in which we stored the experimental parameters and the fitting parameters and result.
main_dataset.view_metadata()ELNES¶
The electron energy-loss near edge structure is determined by fititng the spectrum after quantification model subtraction.
First smooth the spectrum (2 iterations are ususally sufficient) and then find the number of peaks you want (Can be repeated as oftern as one wants).
def smooth(dataset, iterations, advanced_present):
"""Gaussian mixture model (non-Bayesian)
Fit lots of Gaussian to spectrum and let the program sort it out
We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
"""
# TODO: add sensitivity to dialog and the two functions below
peaks = dataset.metadata['peak_fit']
if advanced_present and iterations > 1:
peak_model, peak_out_list = advanced_eels_tools.smooth(dataset, peaks['fit_start'],
peaks['fit_end'], iterations=iterations)
else:
peak_model, peak_out_list = eels.find_peaks(dataset, peaks['fit_start'], peaks['fit_end'])
peak_out_list = [peak_out_list]
flat_list = [item for sublist in peak_out_list for item in sublist]
new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
area = np.sqrt(2 * np.pi) * np.abs(new_list[:, 1]) * np.abs(new_list[:, 2] / np.sqrt(2 * np.log(2)))
arg_list = np.argsort(area)[::-1]
area = area[arg_list]
peak_out_list = new_list[arg_list]
number_of_peaks = np.searchsorted(area * -1, -np.average(area))
return peak_model, peak_out_list, number_of_peaksimport ipywidgets
import matplotlib
import sidpy
import matplotlib.patches as patches
def get_sidebar():
side_bar = ipywidgets.GridspecLayout(16, 3, width='auto', grid_gap="0px")
row = 0
side_bar[row, :3] = ipywidgets.Button(description='Fit Area',
layout=ipywidgets.Layout(width='auto', grid_area='header'),
style=ipywidgets.ButtonStyle(button_color='lightblue'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=7.5,description='Fit Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Fit End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='20px'))
row += 1
side_bar[row, :3] = ipywidgets.Button(description='Peak Finding',
layout=ipywidgets.Layout(width='auto', grid_area='header'),
style=ipywidgets.ButtonStyle(button_color='lightblue'))
row += 1
side_bar[row, :2] = ipywidgets.Dropdown(
options=[('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4)],
value=0,
description='Peaks:',
disabled=False,
layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.Button(
description='Smooth',
disabled=False,
button_style='', # 'success', 'info', 'warning', 'danger' or ''
tooltip='Do Gaussian Mixing',
layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Number:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.Button(
description='Find',
disabled=False,
button_style='', # 'success', 'info', 'warning', 'danger' or ''
tooltip='Find first peaks from Gaussian mixture',
layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :3] = ipywidgets.Button(description='Peaks',
layout=ipywidgets.Layout(width='auto', grid_area='header'),
style=ipywidgets.ButtonStyle(button_color='lightblue'))
row += 1
side_bar[row, :2] = ipywidgets.Dropdown(
options=[('Peak 1', 0), ('Add Peak', -1)],
value=0,
description='Peaks:',
disabled=False,
layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.Dropdown(
options=[ 'Gauss', 'Lorentzian', 'Drude', 'Zero-Loss'],
value='Gauss',
description='Symmetry:',
disabled=False,
layout=ipywidgets.Layout(width='200px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Position:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Amplitude:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Width FWHM:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="eV", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.FloatText(value=0.1, description='Asymmetry:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value="a.u.", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :3] = ipywidgets.Button(description='Analysis',
layout=ipywidgets.Layout(width='auto', grid_area='header'),
style=ipywidgets.ButtonStyle(button_color='lightblue'))
row += 1
side_bar[row, :2] = ipywidgets.Dropdown(
options=[('None', 0)],
value=0,
description='White-Line Ratio:',
disabled=False,
layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
row += 1
side_bar[row, :2] = ipywidgets.Dropdown(
options=[('None', 0)],
value=0,
description='White-Line Sum:',
disabled=False,
layout=ipywidgets.Layout(width='200px'))
side_bar[row, 2] = ipywidgets.widgets.Label(value=" ", layout=ipywidgets.Layout(width='100px'))
return side_bar
class PeakFitWidget(object):
def __init__(self, datasets=None):
self.datasets = datasets
if not isinstance(datasets, dict):
raise TypeError('dataset or first item inhas to be a sidpy dataset')
self.sidebar = get_sidebar()
self.key = list(self.datasets)[0]
self.dataset = datasets[self.key]
if not isinstance(self.dataset, sidpy.Dataset):
raise TypeError('dataset or first item inhas to be a sidpy dataset')
self.spec_dim = ft.get_dimensions_by_type('spectral', self.dataset)
if len(self.spec_dim) != 1:
raise TypeError('We need exactly one SPECTRAL dimension')
self.spec_dim = self.spec_dim[0]
#self.energy_scale = self.dataset._axes[self.spec_dim]
self.energy_scale = self.spec_dim[1]
self.model = np.array([])
self.y_scale = 1.0
self.change_y_scale = 1.0
self.spectrum_ll = None
self.low_loss_key = None
self.peaks = {}
self.show_regions = False
with plt.ioff():
self.fig = plt.figure()
self.fig.canvas.toolbar_position = 'right'
self.fig.canvas.toolbar_visible = True
#self.set_dataset()
self.set_action()
self.y_scale = 1.0
self.change_y_scale = 1.0
self.plot(scale=False)
if 'peak_fit' not in self.dataset.metadata:
self.dataset.metadata['peak_fit'] = {}
if 'edges' in self.dataset.metadata:
if 'fit_area' in self.dataset.metadata['edges']:
self.dataset.metadata['peak_fit']['fit_start'] = \
self.dataset.metadata['edges']['fit_area']['fit_start']
self.dataset.metadata['peak_fit']['fit_end'] = self.dataset.metadata['edges']['fit_area']['fit_end']
self.dataset.metadata['peak_fit']['peaks'] = {'0': {'position': self.energy_scale[1],
'amplitude': 1000.0, 'width': 1.0,
'type': 'Gauss', 'asymmetry': 0}}
self.peaks = self.dataset.metadata['peak_fit']
if 'fit_start' not in self.peaks:
self.peaks['fit_start'].value = self.energy_scale[1]
self.peaks['fit_end'].value = self.energy_scale[-2]
if 'peak_model' in self.peaks:
self.peak_model = self.peaks['peak_model']
self.model = self.peak_model
if 'edge_model' in self.peaks:
self.model = self.model + self.peaks['edge_model']
else:
self.model = np.array([])
self.peak_model = np.array([])
if 'peak_out_list' in self.peaks:
self.peak_out_list = self.peaks['peak_out_list']
self.set_peak_list()
# check whether a core loss analysis has been done previously
if not hasattr(self, 'core_loss') and 'edges' in self.dataset.metadata:
self.core_loss = True
else:
self.core_loss = False
self.update()
self.selector = matplotlib.widgets.SpanSelector(self.fig.gca(), self.line_select_callback,
direction="horizontal",
interactive=True,
props=dict(facecolor='blue', alpha=0.2))
self.start_cursor = ipywidgets.FloatText(value=0, description='Start:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
self.end_cursor = ipywidgets.FloatText(value=0, description='End:', disabled=False, color='black', layout=ipywidgets.Layout(width='200px'))
self.panel = ipywidgets.VBox([ipywidgets.HBox([ipywidgets.Label('',layout=ipywidgets.Layout(width='100px')), ipywidgets.Label('Cursor:'),
self.start_cursor,ipywidgets.Label('eV'),
self.end_cursor, ipywidgets.Label('eV')]),
self.fig.canvas])
self.app_layout = ipywidgets.AppLayout(
left_sidebar=self.sidebar,
center=self.panel,
footer=None,#message_bar,
pane_heights=[0, 10, 0],
pane_widths=[4, 10, 0],
)
display(self.app_layout)
def line_select_callback(self, x_min, x_max):
self.start_cursor.value = np.round(x_min,3)
self.end_cursor.value = np.round(x_max, 3)
self.start_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.start_cursor.value)
self.end_channel = np.searchsorted(self.datasets[self.key].energy_loss, self.end_cursor.value)
def set_peak_list(self):
self.peak_list = []
if 'peaks' not in self.peaks:
self.peaks['peaks'] = {}
key = 0
for key in self.peaks['peaks']:
if key.isdigit():
self.peak_list.append((f'Peak {int(key) + 1}', int(key)))
self.peak_list.append((f'add peak', -1))
self.sidebar[7, 0].options = self.peak_list
self.sidebar[7, 0].value = 0
def plot(self, scale=True):
ylim = self.fig.gca().get_ylim()
ax = self.fig.gca()
ax.clear()
ax.plot(self.energy_scale, self.datasets[self.key]*self.y_scale, label=self.datasets[self.key].title)
ax.set_xlabel(self.datasets[self.key].labels[0])
ax.set_ylabel(self.datasets[self.key].data_descriptor)
ax.ticklabel_format(style='sci', scilimits=(-2, 3))
if scale:
ax.set_ylim(np.array(ylim)*self.change_y_scale)
self.change_y_scale = 1.0
if self.y_scale != 1.:
ax.set_ylabel('scattering probability (ppm/eV)')
self.selector = matplotlib.widgets.SpanSelector(self.fig.gca(), self.line_select_callback,
direction="horizontal",
interactive=True,
props=dict(facecolor='blue', alpha=0.2))
if len(self.model) > 1:
ax.plot(self.energy_scale, self.model*self.y_scale, label='model')
difference_spec = self.datasets[self.key] - self.model
ax.plot(self.energy_scale, difference_spec*self.y_scale, label='difference')
# axis.plot(self.energy_scale, (self.datasets[key] - self.model) / np.sqrt(self.datasets[key])*self.y_scale, label='Poisson')
if 'peaks' in self.peaks:
for index, peak in self.peaks['peaks'].items():
p = [peak['position'], peak['amplitude'], peak['width']]
ax.plot(self.energy_scale, eels.gauss(self.energy_scale, p))
ax.legend()
def set_dataset(self, index=0):
if 'edges' not in self.dataset.metadata or self.dataset.metadata['edges'] == {}:
self.dataset.metadata['edges'] = {'0': {}, 'model': {}, 'use_low_loss': False}
self.edges = self.dataset.metadata['edges']
if '0' not in self.edges:
self.edges['0'] = {}
if 'fit_area' not in self.edges:
self.edges['fit_area'] = {}
if 'fit_start' not in self.edges['fit_area']:
self.sidebar[1,0].value = np.round(self.energy_scale[50], 3)
self.edges['fit_area']['fit_start'] = self.sidebar[1,0].value
else:
self.sidebar[1,0].value = np.round(self.edges['fit_area']['fit_start'],3)
if 'fit_end' not in self.edges['fit_area']:
self.sidebar[2,0].value = np.round(self.energy_scale[-2], 3)
self.edges['fit_area']['fit_end'] = self.sidebar[2,0].value
else:
self.sidebar[2,0].value = np.round(self.edges['fit_area']['fit_end'],3)
if self.dataset.data_type.name == 'SPECTRAL_IMAGE':
if 'SI_bin_x' not in self.dataset.metadata['experiment']:
self.dataset.metadata['experiment']['SI_bin_x'] = 1
self.dataset.metadata['experiment']['SI_bin_y'] = 1
bin_x = self.dataset.metadata['experiment']['SI_bin_x']
bin_y = self.dataset.metadata['experiment']['SI_bin_y']
# self.dataset.view.set_bin([bin_x, bin_y])
self.update()
def set_fit_area(self, value):
"""
if self.sidebar[1,0].value > self.sidebar[2,0].value:
self.sidebar[1,0].value = self.sidebar[2,0].value - 1.0
if float(self.sidebar[1,0].value) < self.energy_scale[0]:
self.sidebar[1,0].value = self.energy_scale[0]
if self.sidebar[2,0].value > self.energy_scale[-1]:
self.sidebar[2,0].value = self.energy_scale[-1]
"""
self.peaks['fit_start'] = self.sidebar[1, 0].value
self.peaks['fit_end'] = self.sidebar[2, 0].value
self.plot()
def set_y_scale(self, value):
self.change_y_scale = 1/self.y_scale
if self.sidebar[12, 0].value:
dispersion = self.energy_scale[1] - self.energy_scale[0]
self.y_scale = 1/self.dataset.metadata['experiment']['flux_ppm'] * dispersion
else:
self.y_scale = 1.0
self.change_y_scale *= self.y_scale
self.update()
self.plot()
def update(self, index=0):
# self.setWindowTitle('update')
self.sidebar[1, 0].value = self.peaks['fit_start']
self.sidebar[2, 0].value = self.peaks['fit_end']
peak_index = self.sidebar[7, 0].value
self.peak_index = self.sidebar[7, 0].value
if str(peak_index) not in self.peaks['peaks']:
self.peaks['peaks'][str(peak_index)] = {'position': self.energy_scale[1], 'amplitude': 1000.0,
'width': 1.0, 'type': 'Gauss', 'asymmetry': 0}
self.sidebar[8, 0].value = self.peaks['peaks'][str(peak_index)]['type']
if 'associated_edge' in self.peaks['peaks'][str(peak_index)]:
self.sidebar[7, 2].value = (self.peaks['peaks'][str(peak_index)]['associated_edge'])
else:
self.sidebar[7, 2].value = ''
self.sidebar[9, 0].value = self.peaks['peaks'][str(peak_index)]['position']
self.sidebar[10, 0].value = self.peaks['peaks'][str(peak_index)]['amplitude']
self.sidebar[11, 0].value = self.peaks['peaks'][str(peak_index)]['width']
if 'asymmetry' not in self.peaks['peaks'][str(peak_index)]:
self.peaks['peaks'][str(peak_index)]['asymmetry'] = 0.
self.sidebar[12, 0].value = self.peaks['peaks'][str(peak_index)]['asymmetry']
def do_fit(self, value=0):
if 'experiment' in self.dataset.metadata:
exp = self.dataset.metadata['experiment']
if 'convergence_angle' not in exp:
raise ValueError('need a convergence_angle in experiment of metadata dictionary ')
alpha = exp['convergence_angle']
beta = exp['collection_angle']
beam_kv = exp['acceleration_voltage']
else:
raise ValueError('need a experiment parameter in metadata dictionary')
eff_beta = eels.effective_collection_angle(self.energy_scale, alpha, beta, beam_kv)
if self.edges['use_low_loss']:
low_loss = self.spectrum_ll / self.spectrum_ll.sum()
else:
low_loss = None
edges = eels.make_cross_sections(self.edges, np.array(self.energy_scale), beam_kv, eff_beta, low_loss)
if self.dataset.data_type == sidpy.DataType.SPECTRAL_IMAGE:
spectrum = self.dataset.view.get_spectrum()
else:
spectrum = self.dataset
self.edges = eels.fit_edges2(spectrum, self.energy_scale, edges)
areal_density = []
elements = []
for key in edges:
if key.isdigit(): # only edges have numbers in that dictionary
elements.append(edges[key]['element'])
areal_density.append(edges[key]['areal_density'])
areal_density = np.array(areal_density)
out_string = '\nRelative composition: \n'
for i, element in enumerate(elements):
out_string += f'{element}: {areal_density[i] / areal_density.sum() * 100:.1f}% '
self.model = self.edges['model']['spectrum']
self.update()
self.plot()
def find_associated_edges(self):
onsets = []
edges = []
if 'edges' in self.dataset.metadata:
for key, edge in self.dataset.metadata['edges'].items():
if key.isdigit():
element = edge['element']
for sym in edge['all_edges']: # TODO: Could be replaced with exclude
onsets.append(edge['all_edges'][sym]['onset'] + edge['chemical_shift'])
# if 'sym' == edge['symmetry']:
edges.append([key, f"{element}-{sym}", onsets[-1]])
for key, peak in self.peaks['peaks'].items():
if key.isdigit():
distance = self.energy_scale[-1]
index = -1
for ii, onset in enumerate(onsets):
if onset < peak['position'] < onset+50:
if distance > np.abs(peak['position'] - onset):
distance = np.abs(peak['position'] - onset) # TODO: check whether absolute is good
distance_onset = peak['position'] - onset
index = ii
if index >= 0:
peak['associated_edge'] = edges[index][1] # check if more info is necessary
peak['distance_to_onset'] = distance_onset
def find_white_lines(self):
if 'edges' in self.dataset.metadata:
white_lines = {}
for index, peak in self.peaks['peaks'].items():
if index.isdigit():
if 'associated_edge' in peak:
if peak['associated_edge'][-2:] in ['L3', 'L2', 'M5', 'M4']:
if peak['distance_to_onset'] < 10:
area = np.sqrt(2 * np.pi) * peak['amplitude'] * np.abs(peak['width']/np.sqrt(2 * np.log(2)))
if peak['associated_edge'] not in white_lines:
white_lines[peak['associated_edge']] = 0.
if area > 0:
white_lines[peak['associated_edge']] += area # TODO: only positive ones?
white_line_ratios = {}
white_line_sum = {}
for sym, area in white_lines.items():
if sym[-2:] in ['L2', 'M4', 'M2']:
if area > 0 and f"{sym[:-1]}{int(sym[-1]) + 1}" in white_lines:
if white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"] > 0:
white_line_ratios[f"{sym}/{sym[-2]}{int(sym[-1]) + 1}"] = area / white_lines[
f"{sym[:-1]}{int(sym[-1]) + 1}"]
white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] = (
area + white_lines[f"{sym[:-1]}{int(sym[-1]) + 1}"])
areal_density = 1.
if 'edges' in self.dataset.metadata:
for key, edge in self.dataset.metadata['edges'].items():
if key.isdigit():
if edge['element'] == sym.split('-')[0]:
areal_density = edge['areal_density']
break
white_line_sum[f"{sym}+{sym[-2]}{int(sym[-1]) + 1}"] /= areal_density
self.peaks['white_lines'] = white_lines
self.peaks['white_line_ratios'] = white_line_ratios
self.peaks['white_line_sums'] = white_line_sum
self.wl_list = []
self.wls_list = []
if len(self.peaks['white_line_ratios']) > 0:
for key in self.peaks['white_line_ratios']:
self.wl_list.append(key)
for key in self.peaks['white_line_sums']:
self.wls_list.append(key)
self.sidebar[14, 0].options = self.wl_list
self.sidebar[14, 0].value = 0
self.sidebar[14, 0].value = f"{self.peaks['white_line_ratios'][self.wl_list[0]]:.2f}"
self.sidebar[15, 0].options = self.wls_list
self.sidebar[15, 0].value = 0
self.sidebar[15, 0].value = f"{self.peaks['white_line_sums'][self.wls_list[0]]*1e6:.4f} ppm"
else:
self.wl_list.append('Ratio')
self.wls_list.append('Sum')
sself.sidebar[14, 0].options = self.wl_list
self.sidebar[14, 0].value = 0
self.sidebar[14, 0].value = ' '
self.sidebar[15, 0].options = self.wls_list
self.sidebar[15, 0].value = 0
self.sidebar[15, 0].value = ' '
def find_peaks(self, value=0):
number_of_peaks = int(self.sidebar[5, 0].value)
# is now sorted in smooth function
# flat_list = [item for sublist in self.peak_out_list for item in sublist]
# new_list = np.reshape(flat_list, [len(flat_list) // 3, 3])
# arg_list = np.argsort(np.abs(new_list[:, 1]))
self.peak_list = []
self.peaks['peaks'] = {}
for i in range(number_of_peaks):
self.peak_list.append((f'Peak {i+1}', i))
p = self.peak_out_list[i]
self.peaks['peaks'][str(i)] = {'position': p[0], 'amplitude': p[1], 'width': p[2], 'type': 'Gauss',
'asymmetry': 0}
self.peak_list.append((f'add peak', -1))
self.sidebar[7, 0].options = self.peak_list
self.sidebar[7, 0].value = 0
self.find_associated_edges()
self.find_white_lines()
self.update()
self.plot()
def smooth(self, value=0):
"""Fit lots of Gaussian to spectrum and let the program sort it out
We sort the peaks by area under the Gaussians, assuming that small areas mean noise.
"""
iterations = self.sidebar[4, 0].value
self.sidebar[5, 0].value = 0
advanced_present=False
self.peak_model, self.peak_out_list, number_of_peaks = smooth(self.dataset, iterations, advanced_present)
spec_dim = ft.get_dimensions_by_type('SPECTRAL', self.dataset)[0]
if spec_dim[1][0] > 0:
self.model = self.dataset.metadata['edges']['model']['spectrum']
elif 'model' in self.dataset.metadata:
self.model = self.dataset.metadata['model']
else:
self.model = np.zeros(len(spec_dim[1]))
self.sidebar[5, 0].value = number_of_peaks
self.dataset.metadata['peak_fit']['edge_model'] = self.model
self.model = self.model + self.peak_model
self.dataset.metadata['peak_fit']['peak_model'] = self.peak_model
self.dataset.metadata['peak_fit']['peak_out_list'] = self.peak_out_list
self.update()
self.plot()
def set_action(self):
self.sidebar[1, 0].observe(self.set_fit_area, names='value')
self.sidebar[2, 0].observe(self.set_fit_area, names='value')
self.sidebar[4, 2].on_click(self.smooth)
self.sidebar[7,0].observe(self.update)
self.sidebar[5,2].on_click(self.find_peaks)
pass
#self.sidebar[11,0].on_click(self.do_fit)
#self.sidebar[12,2].observe(self.plot)
#self.sidebar[4,2].observe(self.plot)
#self.sidebar[12,0].observe(self.set_y_scale)
# ieels.Qt_available = False
view = PeakFitWidget(file_widget.datasets)---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[10], line 437
428 pass
429 #self.sidebar[11,0].on_click(self.do_fit)
430 #self.sidebar[12,2].observe(self.plot)
431 #self.sidebar[4,2].observe(self.plot)
(...) 435
436 # ieels.Qt_available = False
--> 437 view = PeakFitWidget()
Cell In[10], line 5, in PeakFitWidget.__init__(self, datasets)
3 self.datasets = datasets
4 if not isinstance(datasets, dict):
----> 5 raise TypeError('dataset or first item inhas to be a sidpy dataset')
7 self.sidebar = get_sidebar()
8 self.key = list(self.datasets)[0]
TypeError: dataset or first item inhas to be a sidpy datasetview.update()view.sidebar[9, 0].value, view.peaks['peaks'][str(view.peak_index)]['position']
view.peak_index, view.peaks['peaks'][str(3)]['position'](2, 196.39897203557885)view.find_peaks()view.peaks['peaks']{'0': {'position': 205.29198729248733,
'amplitude': -164537.92738566975,
'width': 16.489991004528846,
'type': 'Gaussian',
'asymmetry': 0},
'1': {'position': 209.51854898890775,
'amplitude': 128179.22805573599,
'width': 11.327214665710333,
'type': 'Gaussian',
'asymmetry': 0},
'2': {'position': 201.89290094322763,
'amplitude': 125596.26392103347,
'width': 9.143299311315088,
'type': 'Gaussian',
'asymmetry': 0},
'3': {'position': 196.39897203557885,
'amplitude': 66282.27401083642,
'width': 3.2456895770650784,
'type': 'Gaussian',
'asymmetry': 0}}view.sidebar[2,0].value, view.energy_scale[0](600.0, 100.0)peak_dialog = PeakWidget(main_dataset)---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In [10], line 1
----> 1 peak_dialog = PeakWidget(main_dataset)
NameError: name 'PeakWidget' is not definedpeak_dialog = ieels.PeakFitDialog(file_widget.datasets['Channel_000'])Output¶
areas = []
for p, peak in peak_dialog.peaks['peaks'].items():
area = np.sqrt(2* np.pi)* peak['amplitude'] * np.abs(peak['width'] / np.sqrt(2 *np.log(2)))
areas.append(area)
if 'associated_edge' not in peak:
peak['associated_edge']= ''
print(f"peak {p}: position: {peak['position']:7.1f}, area: {area:12.3f} associated edge: {peak['associated_edge']}")
#print(f'\n M4/M5 peak 2 to peak 1 ratio: {(areas[1])/areas[0]:.2f}')Log Data¶
main_dataset.view_metadata()f = ft.save_dataset(datasets)
f.file.close()Navigation¶
- Tian, M., Dyck, O., Ge, J., & Duscher, G. (2019). Measuring the areal density of nanomaterials by electron energy-loss spectroscopy. Ultramicroscopy, 196, 154–160. 10.1016/j.ultramic.2018.10.009