Chapter 4: Spectroscopy


4.6. Analysis of Core-Loss Spectra#

Try this notebook in Google Colab

Download

Open In Colab

part of

MSE672: Introduction to Transmission Electron Microscopy

Spring 2024

Gerd Duscher Khalid Hattar
Microscopy Facilities Tennessee Ion Beam Materials Laboratory
Materials Science & Engineering Nuclear Engineering
Institute of Advanced Materials & Manufacturing
The University of Tennessee, Knoxville

Background and methods to analysis and quantification of data acquired with transmission electron microscopes.

4.6.1. Content#

Quantitative determination of chemical composition from a core-loss EELS spectrum

Please cite:

M. Tian et al. Measuring the areal density of nanomaterials by electron energy-loss spectroscopy Ultramicroscopy Volume 196, 2019, pages 154-160

as a reference of this quantification method.

4.6.2. Load important packages#

4.6.2.1. Check Installed Packages#

import sys
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')
installing pyTEMlib
done

4.6.2.2. 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 sys
sys.path.insert(0, '../../pyTEMlib')
# Import libraries from pyTEMlib
import pyTEMlib
from  pyTEMlib import file_tools     # File input/ output library
import pyTEMlib.image_tools as it
import pyTEMlib.eels_tools  as eels        # EELS methods 
%load_ext autoreload
%autoreload 2
import pyTEMlib.info_widget
import pyTEMlib.eels_dialog

# 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__ = '2023_05_05'

if 'google.colab' in sys.modules:
    drive.mount("/content/drive")
You don't have igor2 installed.     If you wish to open igor files, you will need to install it     (pip install igor2) before attempting.
You don't have gwyfile installed.     If you wish to open .gwy files, you will need to      install it (pip install gwyfile) before attempting.
Symmetry functions of spglib enabled
pyTEM version:  0.2024.04.0

4.6.3. 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, the dataset will be plotted on the right.

Then select the Info tab, in which we set the experimental parameters.

Select the cursor on known features in the spectrum and eneter the known energies of the features on the top of the spectrum.

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
        
info = pyTEMlib.info_widget.InfoWidget()
info.datasets.keys()
dict_keys(['Channel_000', '_relationship'])
info.sd0, info.sds
(3, 1)
w = file_tools.FileWidget()
v = w.selected_dataset.plot()
w.selected_dataset.energy_loss[0],w.selected_dataset.energy_loss[1]
(100.0, 100.25)
quantification = pyTEMlib.eels_dialog.CompositionWidget(datasets=info.datasets, key='Channel_000')

4.6.4. 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\(^2\)

4.6.4.1. 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()
0
main_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'])
[<matplotlib.lines.Line2D at 0x21e27ad2f90>]
view.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 defined

4.6.4.2. Output 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.716611904444078
B_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")

4.6.4.3. 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()

4.6.5. 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_peaks
import 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 = ieels.PeakfITWidget(file_widget.datasets)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 436
    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)
    432 
    433         #self.sidebar[12,0].observe(self.set_y_scale)
--> 436 ieels.Qt_available = False              
    437 view = ieels.PeakfITWidget(file_widget.datasets)

NameError: name 'ieels' is not defined
view.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 defined
peak_dialog = ieels.PeakFitDialog(file_widget.datasets['Channel_000'])

4.6.5.1. 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}')

4.6.5.2. Log Data#

main_dataset.view_metadata()

4.6.6. Save to File#

Save the datasets to a file.

File needs to be closed to be used with other notebooks

f = ft.save_dataset(datasets)
f.file.close()