Skip to article frontmatterSkip to article content

Chapter 4 Spectroscopy

Chapter 4 Spectroscopy


Quantify Spectrum

part of

MSE672: Introduction to Transmission Electron Microscopy

by Gerd Duscher, Spring 2023

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.

First we import the essential libraries

All we need here should come with the annaconda or any other package

The xml library will enable us to read the Bruker file.

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.2.3':
    print('installing pyTEMlib')
    !{sys.executable} -m pip install  --upgrade pyTEMlib -q

print('done')
C:\Users\gduscher\AppData\Local\Temp\ipykernel_9792\3399850281.py:2: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  from pkg_resources import get_distribution, DistributionNotFound
done
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np

import sys 
sys.path.insert(0, '../../pyTEMlib/')
## import the configuration files of pyTEMlib (we need access to the data folder)
import pyTEMlib

pyTEMlib.__version__
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.
Using kinematic_scattering library version 0.2022.1.0  by G.Duscher
'0.2025.09.0'

Takeoff angle and Absorption

takeoff angle

After an X-ray is emitted it may get absorbed by an other atom with a line of lower energy. In NiO the Ni-K X-ray photon can be absobed by the O-K edge and thus the oxygen signal is more prominent than the chemical composition would allow. Therefore, the path-length of the X-rays within the sample to the detector is important With a sample-thickness tt and take-off angle α\alpha, the path-length pp in the sample is:

p=tsin(α)p = \frac{t}{sin(\alpha)}

NOTE

Absorption is not important for thin specimens (< ~50nm)

The absorption coefficient is defined as :

kA=0tφB(ρt)exp(μρ]specBρtsinα)d(ρt)0tφA(ρt)exp(μρ]specAρtsinα)d(ρt)k_A = \frac{\int_0^t \varphi_B(\rho t) \exp -\left( \left. \frac{\mu}{\rho} \right]^B_{\rm spec}\rho \frac{t}{\sin \alpha} \right) d(\rho t)} {\int_0^t \varphi_A(\rho t) \exp -\left( \left. \frac{\mu}{\rho} \right]^A_{\rm spec}\rho \frac{t}{\sin \alpha} \right) d(\rho t)}

with: φB(ρt) \varphi_B(\rho t) : depth distribution of Xray production

which is the ratio of the X-ray emission from a layer of element A/B of thickness Δρt\Delta \rho t at depth tt in the specimen with density ρ\rho to the X-ray emission from an identical, but isolated film.

μρ]specA=i(Ciμρ]specA) \left. \frac{\mu}{\rho} \right]^A_{\rm spec} = \sum_i \left( \left. \frac{C_i\mu}{\rho} \right]^A_{\rm spec} \right) : the mass-absorption coefficient of X-rays from element A in the specimen (CiC_i concentration of element ii with iCi=1\sum_i C_i =1)

α\alpha : the detector take-off angle.

import os
def get_bote_salvat_dict(z=0):
    filename = os.path.join(pyTEMlib.config_path, 'Bote_Salvat.json')
    x_sections = json.load(open(filename))
    if z > 0:
        return x_sections[str(z)]
    return x_sections

def bote_salvat_xsection(x_section, subshell, acceleration_energy):
    """
    Using Tabulated Values for electrons of: 
    - D. Bote and F. Salvat, "Calculations of inner-shell ionization by electron 
      impact with the distorted-wave and plane-wave Born approximations", 
      Phys. Rev. A77, 042701 (2008).
    
    - Bote, David, et al. "Cross sections for ionization of K, L and M shells of 
      atoms by impact of electrons and positrons with energies up to 1 GeV: 
      Analytical formulas." 
      Atomic Data and Nuclear Data Tables 95.6 (2009): 871-909.

    Computes the inner sub-shell ionization cross section for energetic electrons. 
    `subshell` is 1->K, 2->L₁, 3->L₂, ..., 9->M₅
    Parameters
    ----------
    z : int
        The atomic number z in the range 1:99   
    subshell : int
        The atomic sub-shell being ionized 1->K, 2->L₁, 3->L₂, ..., 9->M₅
    acceleration_energy : float
        The kinetic energy of the incident electron in eV
    edge_energy : float
        The edge energy of the sub-shell in eV

    Returns
    -------
    float
        The ionization cross section in barns
    """
    edge_energy = x_section['edge'][subshell]
    over_voltage = acceleration_energy / edge_energy
    print(edge_energy , acceleration_energy)
    if over_voltage < 1.0:
        return
    if over_voltage <= 16:
        print('low')
        a = x_section['A'][subshell]
        opu = 1.0 / (1.0 + over_voltage)
        ffitlo = a[0] + a[1] * over_voltage + opu*(a[2] + opu**2*(a[3] + opu**2*a[4]))
        x_ion_e = (over_voltage - 1.0) * (ffitlo / over_voltage)**2
    else:
        REV = 5.10998918e5  # electron rest energy in eV
        e_0 = acceleration_energy
        beta2 = (e_0 * (e_0 + 2.0 * REV)) / ((e_0 + REV)**2)
        x = np.sqrt(e_0 * (e_0 + 2.0 * REV)) / REV
        g = x_section['G'][subshell]
        print(g)
        ffitup = (((2.0 * np.log(x)) - beta2) * (1.0 + g[0] / x)) + g[1] + g[2] * np.sqrt(REV / (e_0 + REV)) + g[3] / x
        factr = x_section['Anlj'][subshell] / beta2
        print(x_section['Anlj'][subshell])
        x_ion_e = ((factr * over_voltage) / (over_voltage + x_section['Be'][subshell])) * ffitup
        print(x_section['Be'][subshell])
    return 4.0 * np.pi * 5.291772108e-9**2 * x_ion_e  # in barns

x_section_dict = get_bote_salvat_dict(29)
for i in range(1,4):
    
    x = bote_salvat_xsection(x_section_dict, i, 200000)*1e20
    print(x)
1093.7 200000
[0.0494, 8.23, -0.173, 0.0823]
4.59e-07
1.02
0.2521903806012185
966.028 200000
[0.0401, 7.26, -0.151, 0.0813]
8.76e-07
0.959
0.42164825891918994
944.886 200000
[0.0361, 7.25, -0.0956, 0.0668]
1.8e-06
0.959
0.8696508772323364
import xraylib as xr
import re
shell_list = []
for key, item  in xr.__dict__.items():
    if '_SHELL' in key:
        shell_list.append(key.split('_')[0])

line_list = []
for key, item  in xr.__dict__.items():
    if '_LINE' in key:
        line_list.append(key.split('_')[0])
        
transition_dict = {}
regex = shell + r"[H-Z][0-9]"
for value, line in enumerate(line_list):
    if line[0] == shell:
        transition_dict[line] = -value
transition_dict







































{'KL1': 0, 'KL2': -1, 'KL3': -2, 'KM1': -3, 'KM2': -4, 'KM3': -5, 'KM4': -6, 'KM5': -7, 'KN1': -8, 'KN2': -9, 'KN3': -10, 'KN4': -11, 'KN5': -12, 'KN6': -13, 'KN7': -14, 'KO': -15, 'KO1': -16, 'KO2': -17, 'KO3': -18, 'KO4': -19, 'KO5': -20, 'KO6': -21, 'KO7': -22, 'KP': -23, 'KP1': -24, 'KP2': -25, 'KP3': -26, 'KP4': -27, 'KP5': -28, 'KA': -383, 'KB': -384, 'KA1': -387, 'KA2': -388, 'KA3': -389, 'KB1': -390, 'KB2': -391, 'KB3': -392, 'KB4': -393, 'KB5': -394}
dir (re)
['A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'Match', 'NOFLAG', 'Pattern', 'PatternError', 'RegexFlag', 'S', 'Scanner', 'U', 'UNICODE', 'VERBOSE', 'X', '_MAXCACHE', '_MAXCACHE2', '_ZeroSentinel', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_cache', '_cache2', '_casefix', '_compile', '_compile_template', '_compiler', '_constants', '_parser', '_pickle', '_special_chars_map', '_sre', '_zero_sentinel', 'compile', 'copyreg', 'enum', 'error', 'escape', 'findall', 'finditer', 'fullmatch', 'functools', 'match', 'purge', 'search', 'split', 'sub', 'subn']
In a TEM we can assume that the film is homogeneous and can set $ \varphi_B(\rho t) = 1$ 

and we get:
$$
A_{AB} = \frac{\left( \left. \frac{\mu}{\rho} \right]^A_{\rm spec}   \right)}
{ \left( \left. \frac{\mu}{\rho} \right]^B_{\rm spec}   \right)} 
\left(\frac{1 -  \exp\left( \left. \frac{\mu}{\rho} \right]^B_{\rm spec}\frac{\rho t}{\sin \alpha}    \right)}
{ 1-\exp\left( \left. \frac{\mu}{\rho} \right]^A_{\rm spec} \frac{\rho t}{\sin \alpha}    \right)}\right)
$$

> For absorption correction, we need to know desnity $\rho$ and thickness $t$ of our specimen.
  Cell In[6], line 1
    In a TEM we can assume that the film is homogeneous and can set $ \varphi_B(\rho t) = 1$
       ^
SyntaxError: invalid syntax
def get_abs_corr_cross_section(
    composition, number_of_atoms, take_off_angle, probe_area
):
    """
    Calculate absorption correction terms.

    Parameters
    ----------
    number_of_atoms: list of signal
        Stack of maps with number of atoms per pixel.
    take_off_angle: float
        X-ray take-off angle in degrees.
    """
    from exspy._misc import material

    toa_rad = np.radians(take_off_angle)
    Av = constants.Avogadro
    elements = [intensity.metadata.Sample.elements[0] for intensity in number_of_atoms]
    atomic_weights = np.array(
        [
            elements_db[element]["General_properties"]["atomic_weight"]
            for element in elements
        ]
    )

    number_of_atoms = stack(number_of_atoms, show_progressbar=False).data

    # calculate the total_mass in kg/m^2, or mass thickness.
    total_mass = np.zeros_like(number_of_atoms[0], dtype="float")
    for i, (weight) in enumerate(atomic_weights):
        total_mass += number_of_atoms[i] * weight / Av / 1e3 / probe_area / 1e-18
    # determine mass absorption coefficients and convert from cm^2/g to m^2/kg.
    to_stack = material.mass_absorption_mixture(
        weight_percent=material.atomic_to_weight(composition)
    )
    mac = stack(to_stack, show_progressbar=False) * 0.1
    acf = np.zeros_like(number_of_atoms)
    csc_toa = 1 / math.sin(toa_rad)
    # determine an absorption coeficient per element per pixel.
    for i, (weight) in enumerate(atomic_weights):
        expo = mac.data[i] * total_mass * csc_toa
        acf[i] = expo / (1 - np.exp(-expo))
    return acf

Cross-section for EDS in Transmission

For thin samples such as used in transmission electron microscopy absorption, and fluorescence, can be neglected Chapter 2: Pennycook and Nellist, 2011. If multiple scattering and channelling is avoided the EDS partial scattering cross-section for a single atom of element x is given by Macarthur et al., 2015 and Macarthur et al., 2017:

σxEDS=Ixei0τNxt\sigma_{x}^{EDS} = \frac{I_x e}{i_0 \tau N_x t}

Where:

  • NxN_x: the volumetric number density of the elemental species being detected,

  • tt: the sample thickness (same length unit as volume in N_x),

  • IxI_x: is the raw x-ray counts detected from the sample (a.u.),

  • i0i_0: is the probe current (A),

  • τ\tau is the exposure time (s),

  • ee is the electronic charge (C).

A more consistent way would be to relate the cross-section with areal density nxn_x and to use incident flux I0I_0 like in the case of EELS earlier.

σxEDS=Ixi0τ/e1Nxt=IxI01nx\sigma_{x}^{EDS} = \frac{I_x}{i_0\tau/e} \frac{1} {N_x t} = \frac{I_x}{I_0} \frac{1} {n_x}

Where:

  • nx=ρxtn_x = \rho_x *t : the areal density of the elemental species being detected,

  • I0=i0τ/eI_0 = i_0*\tau/e: incident flux: total number of electrons the sample is exposed to during the experiment

Comparison with EELS

Above formula is the same as derived for EELS earlier. The difference is that IxEELSI_x^{EELS} is the number of electrons that scattered inelastically per energy transfer (and momentum transfer).

While IxEDSI_x^{EDS} is the raw counts of X-rays that originate in the relaxation of excitation from a specific core level of element xx. This implies that the competing relaxation process of generation of Auger electrons is the same for all elements.

This is a fundamental requirement for quantification of both EDS and Auger spectroscopy.

The relationship between σxEDS\sigma_{x}^{EDS} and σxEELS\sigma_{x}^{EELS} is, therfore, a linear one. And we need to determine the efficiency of X-ray detection (not generation) per EDS system, which also has an take-off angle dependence.

However, the linear dependency is not between the EELS and EDS cross section as defined here, but the cross section of the core excitation, as introduced by Egerton. In that approach the inelastic scattering of lower-energy core-losses are approximated together with the background caused by plasmon losses.

Therefore, in the code cell below we need to subtract the background from lower-level excitations from the core-level in question.

z = 38

def get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd):
    background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [start_bgd, end_bgd], verbose=False)
    cross_section_core = Xsection- background[0]
    cross_section_core[cross_section_core < 0] = 0.0
    cross_section_core[energy_scale < end_bgd] = 0.0
    return cross_section_core
energy_scale = np.arange(10,2000)
Xsection = pyTEMlib.eels_tools.xsec_xrpa(energy_scale, 200, z, 3000. )
edge_info = pyTEMlib.eels_tools.get_x_sections(z)
if 'K1' in edge_info:
    start_bgd = edge_info['K1']['onset']*.8
    end_bgd = edge_info['K1']['onset']-5
    K_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
    
if 'L3' in edge_info:
    start_bgd = edge_info['L3']['onset']*.8
    end_bgd = edge_info['L3']['onset']-5
    L_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
if 'M5' in edge_info:
    start_bgd = edge_info['L3']['onset']*.8
    end_bgd = edge_info['L3']['onset']-5
    M5_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)

background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [80, 95], verbose=False)
plt.figure()
plt.plot(energy_scale, np.log(1+Xsection) , label='EELS X-section')
plt.plot(energy_scale, np.log(1+background[0]), label='L-core background')

plt.plot(energy_scale, np.log(1+L_eds_xsection), label='L-level X-section')

plt.plot(energy_scale, np.log(1+K_eds_xsection), label='K-level X-section')
plt.xlim(20,30000)
# plt.ylim(0, 10)
plt.xlabel ('energy (eV)')
plt.ylabel('cross section (barns)')
plt.legend();
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[2], line 15
     13     start_bgd = edge_info['K1']['onset']*.8
     14     end_bgd = edge_info['K1']['onset']-5
---> 15     K_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
     17 if 'L3' in edge_info:
     18     start_bgd = edge_info['L3']['onset']*.8

Cell In[2], line 4, in get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
      3 def get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd):
----> 4     background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [start_bgd, end_bgd], verbose=False)
      5     cross_section_core = Xsection- background[0]
      6     cross_section_core[cross_section_core < 0] = 0.0

File ~\AppData\Local\anaconda3\Lib\site-packages\pyTEMlib\eels_tools.py:1625, in power_law_background(spectrum, energy_scale, fit_area, verbose)
   1622     err = yy - power_law(xx, pp[0], pp[1])
   1623     return err
-> 1625 [p, _] = leastsq(bgdfit, p0, args=(y, x), maxfev=2000)
   1627 background_difference = y - power_law(x, p[0], p[1])
   1628 background_noise_level = std_dev = np.std(background_difference)

File ~\AppData\Local\anaconda3\Lib\site-packages\scipy\optimize\_minpack_py.py:430, in leastsq(func, x0, args, Dfun, full_output, col_deriv, ftol, xtol, gtol, maxfev, epsfcn, factor, diag)
    427 m = shape[0]
    429 if n > m:
--> 430     raise TypeError(f"Improper input: func input vector length N={n} must"
    431                     f" not exceed func output vector length M={m}")
    433 if epsfcn is None:
    434     epsfcn = finfo(dtype).eps

TypeError: Improper input: func input vector length N=2 must not exceed func output vector length M=0

Cross-section for EDS

Of course we do not need the dependence of the cross section on energy but the sum.

The relevant functions will then be:

def get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd):
    background = pyTEMlib.eels_tools.power_law_background(Xsection, energy_scale, [start_bgd, end_bgd], verbose=False)
    cross_section_core = Xsection- background[0]
    cross_section_core[cross_section_core < 0] = 0.0
    cross_section_core[energy_scale < end_bgd] = 0.0
    return cross_section_core


def get_eds_line_strength(z, acceleration_voltage, max_kV=60000 ):

    keV = acceleration_voltage /1000.
    energy_scale = np.arange(10, max_kV, 1)
    edge_info = pyTEMlib.eels_tools.get_x_sections(z)
    eds_cross_sections = {'_element': {'atomic_weight': edge_info['atomic_weight'],
                                      'name': edge_info['name'],
                                      'nominal_density': edge_info['nominal_density']}}
    
    Xsection = pyTEMlib.eels_tools.xsec_xrpa(energy_scale, keV, z, 3000. )
    
    if 'K1' in edge_info:
        start_bgd = edge_info['K1']['onset'] * 0.8
        if edge_info['K1']['onset'] - start_bgd >100:
            start_bgd = edge_info['K1']['onset'] - 100
        end_bgd = edge_info['K1']['onset'] - 5
        K_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
        eds_cross_sections['K'] = {'x-section': K_eds_xsection[int(end_bgd) : int(end_bgd)+200].sum(),
                                   'strength':  K_eds_xsection[int(end_bgd) : int(end_bgd)+200].sum()}
        if 'fluorescent_yield' in edge_info:
             eds_cross_sections['K']['fluorescent_yield'] = edge_info['fluorescent_yield']['K']
             eds_cross_sections['K']['strength'] *= edge_info['fluorescent_yield']['K']
    if 'L3' in edge_info:
        start_bgd = edge_info['L3']['onset'] * 0.8
        if edge_info['L3']['onset'] - start_bgd >100:
            start_bgd = edge_info['L3']['onset'] - 100
        end_bgd = edge_info['L3']['onset'] - 5
        L_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
        eds_cross_sections['L'] = {'x-section': L_eds_xsection[int(end_bgd) : int(end_bgd)+200].sum(),
                                   'strength':  L_eds_xsection[int(end_bgd) : int(end_bgd)+200].sum()}
        if 'fluorescent_yield' in edge_info:
            if 'L' in edge_info['fluorescent_yield']:
                eds_cross_sections['L']['fluorescent_yield'] = edge_info['fluorescent_yield']['L']
                eds_cross_sections['L']['strength'] *= edge_info['fluorescent_yield']['L'] 
            else:
                eds_cross_sections['L']['strength'] *= 0.
                
    if 'M5' in edge_info:
        if(edge_info['M5']['onset']) >100:
            start_bgd = edge_info['M5']['onset'] * 0.8
            if edge_info['M5']['onset'] - start_bgd >100:
                start_bgd = edge_info['M5']['onset'] - 100
            
            end_bgd = edge_info['M5']['onset'] - 5
            M_eds_xsection = get_eds_xsection(Xsection, energy_scale, start_bgd, end_bgd)
            eds_cross_sections['M'] = {'x-section': M_eds_xsection[int(end_bgd) : int(end_bgd)+300].sum(),
                                       'strength':  M_eds_xsection[int(end_bgd) : int(end_bgd)+300].sum()}
            if 'fluorescent_yield' in edge_info:
                if 'M' in edge_info['fluorescent_yield']:
                    eds_cross_sections['M']['fluorescent_yield'] = edge_info['fluorescent_yield']['M']
                    # eds_cross_sections['M']['strength'] *= edge_info['fluorescent_yield']['M']
                else:
                    eds_cross_sections['M']['strength'] *= 0.
    return eds_cross_sections

Comparison to k-factors to Cliff Lorimer

  • k-factors a weight% cross-sections in atom%

energy_scale = np.linspace(10, 20000, 20000)
keV = 200000
ref_z = 14
z = 29


def get_line_weight(lines, line_family='K'):
    line_weight = 0 
    for key, item in lines.items():
        if line_family in key:
            line_weight += item['weight']
    return line_weight

x_section_dict = get_bote_salvat_dict(ref_z)
Si_K1 = bote_salvat_xsection(x_section_dict, 0, keV)*1e20
edge_info = pyTEMlib.eels_tools.get_x_sections(ref_z)
Si_yield = edge_info['fluorescent_yield']['K']
Si_amu = edge_info['atomic_weight']
Si_line_weight = get_line_weight(edge_info['lines'])


edge_info = pyTEMlib.eels_tools.get_x_sections(z)
x_section_dict = get_bote_salvat_dict(z)
x_K1 = bote_salvat_xsection(x_section_dict, 0, keV)*1e23
yield_K1 = edge_info['fluorescent_yield']['K']
xK_line_weight = get_line_weight(edge_info['lines'], 'K')



if 'L' in edge_info['fluorescent_yield'].keys():
    xL = 0
    for i in  range (1,4):
        x_L = bote_salvat_xsection(x_section_dict, i, keV)*1e23
    yield_L3 = edge_info['fluorescent_yield']['L']
    xL_line_weight = get_line_weight(edge_info['lines'], 'L')

x_amu = edge_info['atomic_weight']
edge_info['name']
1828.5 200000
[0.0621, 7.14, -0.207, 0.0953]
3.74e-07
1.14
8950.25 200000
[0.302, 6.26, -1.02, 0.454]
6.49e-08
0.765
1093.7 200000
[0.0494, 8.23, -0.173, 0.0823]
4.59e-07
1.02
966.028 200000
[0.0401, 7.26, -0.151, 0.0813]
8.76e-07
0.959
944.886 200000
[0.0361, 7.25, -0.0956, 0.0668]
1.8e-06
0.959
'Cu'
for i in range(1,4):
    print(i)
1
2
3
k_a = x_K1 * yield_K1 * xK_line_weight / x_amu 
l_a = x_L * yield_L3 * 
/ x_amu
ref_k = Si_K1 * Si_yield  * Si_line_weight / Si_amu
print(f"{k_a:.4f}, {l_a:.4f}, {ref_k:.4f}") 
print(f" k-factor {k_a/ref_k:.4f}  L: {l_a/ref_k:.4f}")
print('k_a/l_a' , k_a/l_a)

print(f"{yield_K1:.2f}, {yield_L3:.3f}, {ref_yield:.3f} ")
print(f"{xK_line_weight:.2f}, {xL_line_weight:.3f}, {Si_line_weight:.3f} ")
print(f"x-sec  {x_K1*1000:.2f}, {x_L3*1000:.2f}, {ref_K1*1000:.2f}")
print( x_K1/yield_K1, x_L3/yield_L3)

'k_a/l_a', (5.13247e-01/ 7.07008e-01), 'k-factor', (5.13247e-01 / 3.24726e-01),  (x_amu/Si_amu) * 4.7841 /5,  5/8.261455
0.2958, 0.3304, 0.0004
 k-factor 772.1603  L: 862.3140
k_a/l_a 0.895451408808201
0.42, 0.008, 0.040 
1.89, 2.951, 1.532 
x-sec  23492.02, 869.65, 174.53
55.623490445748104 106.31428817021227
('k_a/l_a', 0.725942280709695, 'k-factor', 1.5805540671212037, 2.164892336614979, 0.6052202668900333)
'K': np.float64(2.192963366430227e-22),
 'L1': np.float64(2.5054476181469093e-21),
 'L2': np.float64(4.181630548806971e-21),
 'L3': np.float64(8.647677139867e-21),
  Cell In[434], line 1
    'K': np.float64(2.192963366430227e-22),
    ^
SyntaxError: illegal target for annotation
"KL2": {
    "energy": 1.7394,
    "cs": 2.4079062124253643e-23
},
"KL3": {
    "energy": 1.74,
    "cs": 4.7841761325214666e-23

"29": {
    "KL2": {
        "energy": 8.0279,
        "cs": 2.5681370026132807e-23
    },
    "KL3": {
        "energy": 8.0478,
        "cs": 5.003801989880983e-23
    },
edge_info['total_fluorescent_yield']
{'K': 0.43381, 'L1': 0.009138, 'L2': 0.009302, 'L3': 0.008804}
print(x_K1/yield_K1, x_L3/yield_L3)
print( (yield_K1/x_K1) / (yield_L3/ x_L3))

print(1/((yield_K1/x_K1) / (ref_yield/ reference))*(fe_amu/ref_weight) )
print((fe_amu/ref_weight))

k_a = 1/x_K1 * yield_K1 / ref_weight * xK_line_weight
k_b = ref_K1 * ref_yield / fe_amu * ref_line_weight

l_a = x_L3* yield_L3/ref_weight * xL_line_weight
print(xL_line_weight, xK_line_weight)

print(k_a/k_b , k_a, k_b, l_a/k_a, k_a/ l_a)

print((x_K1 * yield_K1 * xK_line_weight) / (ref_K1* ref_yield  * ref_line_weight) * (ref_weight /fe_amu ))
1(5.13247e-01 / 3.24726e-01) 
0.05562349044574811 106.31428817021227
1911.319971440933
2.1241189051165168e+17
2.262591016716811
0.33886075083341954 0.5277930544545206
4681.15961157362 0.33784949408754095 7.217217999835929e-05 0.0002540470048098375 3936.27943281021
0.5046406596868588
0.632689523757567
weight = 0 
for key, item in edge_info['lines'].items():
    if "K" in key:
        weight 
(1.894682, 0.5277930544545206)
def get_k_factors(z, keV):
    
    Si_eds_cross_sections = pyTEMlib.eds_tools.get_eds_line_strength(14, keV)
    z_eds_cross_sections = pyTEMlib.eds_tools.get_eds_line_strength(z, keV)
    
    if 'K' in z_eds_cross_sections:
        z_eds_cross_sections['K']['k_factor'] = z_eds_cross_sections['K']['strength'] / Si_eds_cross_sections['K'] ['strength']
    if 'L' in z_eds_cross_sections:
        z_eds_cross_sections['L']['k_factor'] =  z_eds_cross_sections['L']['strength'] / Si_eds_cross_sections['K'] ['strength']
    if 'M' in z_eds_cross_sections:
        z_eds_cross_sections['M']['k_factor'] = z_eds_cross_sections['M']['strength'] / Si_eds_cross_sections['K'] ['strength']
        
    return z_eds_cross_sections

k_factors = pyTEMlib.eds_tools.load_k_factors()
z = 47
x = []
y = []
y1 = []
y2 = []
y3 = []

mult = 17
added = .5
print(1/3/11.4)
mult = 11.4
added = 0.02915
nx_sections = get_k_factors(14, 200)
si_k = nx_sections['K']['fluorescent_yield']/nx_sections['K']['x-section']  + added
si_kk = float(k_factors.get('Si', {}).get('Ka1', 1))
for z in range(10, 40, 2):
    nx_sections = get_k_factors(z, 200)
    
    # print(As_eds_cross_sections)
    # print(f"ration of Si to {z} for K lines intensities {nx_sections['K']['k_factor']}")
    #print(f"ration of Si to {z} for L lines intensities {nx_sections['L']['k_factor']}")
    yy = nx_sections['K']['x-section'] * nx_sections['K']['fluorescent_yield']
    if yy > 0:
        x.append(z)
        y.append(nx_sections['K']['fluorescent_yield']/nx_sections['K']['x-section']  + added)
        y1.append(nx_sections['K']['x-section'])
        y2.append(nx_sections['K']['fluorescent_yield'])
        key = nx_sections['_element']['name'] 
        y3.append(float(k_factors.get(key, {}).get('Ka1', 1)))
    # print(f"{nx_sections['K']['x-section']:.2f}, {nx_sections['K']['fluorescent_yield']:.3f}, {nx_sections['K']['x-section'] * nx_sections['K']['fluorescent_yield']:.3f}")
plt.figure()
plt.plot(x, np.array(y)*10, label='kk')
plt.plot(x, np.array(y1)/20000, label='X-sec')
plt.plot(x, np.array(y2)/100, label = 'flu')
plt.plot(x, np.array(y3), label = 'k')

plt.plot(x, np.array(y3)/si_kk- np.array(y/si_k))
plt.legend()
p =  np.array(y3)/si_kk- np.array(y/si_k)
print(f"{p.min():2f}, {p.max():.3f}, {p.std():.5f}")
Loading...

added *  np.array(y2)
array([0.0003729, 0.0006999, 0.0012075, 0.0029451, 0.004233 , 0.005799 , 0.0076032, 0.0095775, 0.0116367, 0.0136923, 0.0156675, 0.0175038, 0.0191658, 0.0206376])
x = []
y = []
y1 = []
y2 = []
y3 = []

added = 40448*0
mult = 1e3
nx_sections = get_k_factors(14, 200)
si_k = nx_sections['K']['x-section'] /nx_sections['K']['fluorescent_yield'] + 0.02915
si_kk = float(k_factors.get('Si', {}).get('Ka1', 1))
for z in range(26, 60, 2):
    nx_sections = get_k_factors(z, 200)
    
    # print(As_eds_cross_sections)
    # print(f"ration of Si to {z} for K lines intensities {nx_sections['K']['k_factor']}")
    #print(f"ration of Si to {z} for L lines intensities {nx_sections['L']['k_factor']}")
    yy = nx_sections['L']['fluorescent_yield'] / nx_sections['L']['x-section']
    if yy > 0:
        x.append(z)
        y.append(((nx_sections['L']['fluorescent_yield'] / nx_sections['L']['x-section'] )*mult) )
        y1.append(nx_sections['L']['x-section'])
        y2.append(nx_sections['L']['fluorescent_yield'])
        key = nx_sections['_element']['name'] 
        y3.append(float(k_factors.get(key, {}).get('La1', 1)))
    # print(f"{nx_sections['K']['x-section']:.2f}, {nx_sections['K']['fluorescent_yield']:.3f}, {nx_sections['K']['x-section'] * nx_sections['K']['fluorescent_yield']:.3f}")
plt.figure()
plt.plot(x, np.array(y), label='kk')
#plt.plot(x, np.array(y1)/20000, label='X-sec')
#plt.plot(x, np.array(y2)/100, label = 'flu')
plt.plot(x, np.array(y3), label = 'k')
#plt.plot(x, np.array(y3)/si_kk- np.array(y/si_k))
p =  np.array(y3)/si_kk- np.array(y/si_k)
print(f"{p.min():2f}, {p.max():.3f}, {p.std():.5f}")
plt.legend()
y, si_k, y3, added
Loading...
added/mult
0.029239766081871343
{'_element': {'atomic_weight': 140.12, 'name': 'Ce', 'nominal_density': 6.637}, 'K': {'x-section': np.float64(0.5937873827996667), 'strength': np.float64(0.541540030987124), 'fluorescent_yield': 0.91201, 'k_factor': np.float64(0.01323890388139376)}, 'L': {'x-section': np.float64(174.80969013623059), 'strength': np.float64(20.88800987437819), 'fluorescent_yield': 0.11949, 'k_factor': np.float64(0.5106443460817243)}, 'M': {'x-section': np.float64(31818.63679637004), 'strength': np.float64(144.77479742348368), 'fluorescent_yield': 0.00455, 'k_factor': np.float64(3.539275986752169)}}
k_factors = pyTEMlib.eds_tools.load_k_factors()
k_factors
{'': {}, 'B': {'Ka1': '1.00000e+00'}, 'C': {'Ka1': '5.34908e-01'}, 'N': {'Ka1': '4.23635e-01'}, 'O': {'Ka1': '3.71908e-01'}, 'F': {'Ka1': '3.85149e-01'}, 'Ne': {'Ka1': '3.46539e-01'}, 'Na': {'Ka1': '3.45547e-01'}, 'Mg': {'Ka1': '3.27781e-01'}, 'Al': {'Ka1': '3.30728e-01'}, 'Si': {'Ka1': '3.24726e-01'}, 'P': {'Ka1': '3.34291e-01'}, 'S': {'Ka1': '3.21669e-01'}, 'Cl': {'Ka1': '3.34284e-01'}, 'Ar': {'Ka1': '3.58291e-01', 'Kb1': '3.58291e-01'}, 'K': {'Ka1': '3.36238e-01', 'Kb1': '3.36238e-01'}, 'Ca': {'Ka1': '3.33218e-01', 'Kb1': '3.33218e-01'}, 'Sc': {'Ka1': '3.63890e-01', 'Kb1': '3.63890e-01', 'La1': '1.63109e+00'}, 'Ti': {'Ka1': '3.79229e-01', 'Kb1': '3.79229e-01', 'La1': '1.30000e+00', 'Lb1': '1.31997e+00'}, 'V': {'Ka1': '3.97724e-01', 'Kb1': '3.97724e-01', 'La1': '1.15585e+00', 'Lb1': '1.17588e+00'}, 'Cr': {'Ka1': '4.02650e-01', 'Kb1': '4.02650e-01', 'La1': '1.05303e+00', 'Lb1': '1.07281e+00'}, 'Mn': {'Ka1': '4.24546e-01', 'Kb1': '4.24546e-01', 'La1': '9.95024e-01', 'Lb1': '1.01531e+00'}, 'Fe': {'Ka1': '4.32916e-01', 'Kb1': '4.32916e-01', 'La1': '8.76707e-01', 'Lb1': '8.95690e-01'}, 'Co': {'Ka1': '4.60642e-01', 'Kb1': '4.60642e-01', 'La1': '8.14528e-01', 'Lb1': '8.33085e-01'}, 'Ni': {'Ka1': '4.65132e-01', 'Kb1': '4.65132e-01', 'La1': '7.25953e-01', 'Lb1': '7.43277e-01'}, 'Cu': {'Ka1': '5.13247e-01', 'Kb1': '5.13247e-01', 'La1': '7.07008e-01', 'Lb1': '7.24974e-01'}, 'Zn': {'Ka1': '5.42706e-01', 'Kb1': '5.42706e-01', 'La1': '6.67212e-01', 'Lb1': '6.85234e-01'}, 'Ga': {'Ka1': '5.99894e-01', 'Kb1': '5.99894e-01', 'La1': '6.58445e-01', 'Lb1': '6.77436e-01'}, 'Ge': {'Ka1': '6.55539e-01', 'Kb1': '6.55539e-01', 'La1': '6.39731e-01', 'Lb1': '6.59345e-01'}, 'As': {'Ka1': '7.18892e-01', 'Kb1': '7.18892e-01', 'La1': '6.17577e-01', 'Lb1': '6.37511e-01'}, 'Se': {'Ka1': '8.17924e-01', 'Kb1': '8.17924e-01', 'La1': '6.13103e-01', 'Lb1': '6.33918e-01'}, 'Br': {'Ka1': '9.05321e-01', 'Kb1': '9.05321e-01', 'La1': '5.85224e-01', 'Lb1': '6.06287e-01'}, 'Kr': {'Ka1': '1.05287e+00', 'Kb1': '1.05287e+00', 'La1': '5.99310e-01', 'Lb1': '6.22023e-01'}, 'Rb': {'Ka1': '1.20424e+00', 'Kb1': '1.20424e+00', 'La1': '5.80512e-01', 'Lb1': '6.03815e-01'}, 'Sr': {'Ka1': '1.39900e+00', 'Kb1': '1.39900e+00', 'La1': '5.69435e-01', 'Lb1': '5.93524e-01'}, 'Y': {'Ka1': '1.62293e+00', 'Kb1': '1.62293e+00', 'La1': '5.57761e-01', 'Lb1': '5.82556e-01', 'Mz1': '1.02442e+00'}, 'Zr': {'Ka1': '1.91568e+00', 'Kb1': '1.91568e+00', 'La1': '5.45923e-01', 'Lb1': '5.71390e-01', 'Mz1': '8.61843e-01'}, 'Nb': {'Ka1': '2.25478e+00', 'Kb1': '2.25478e+00', 'La1': '5.30983e-01', 'Lb1': '5.56977e-01', 'Mz1': '7.24052e-01'}, 'Mo': {'Ka1': '2.69449e+00', 'Kb1': '2.69449e+00', 'La1': '5.25675e-01', 'Lb1': '5.52708e-01', 'Mz1': '6.16939e-01'}, 'Tc': {'Ka1': '3.22822e+00', 'La1': '5.16718e-01', 'Lb1': '5.44536e-01', 'Mz1': '5.47504e-01'}, 'Ru': {'Ka1': '3.88953e+00', 'La1': '5.13871e-01', 'Lb1': '5.42901e-01', 'Mz1': '5.05635e-01'}, 'Rh': {'Ka1': '4.63368e+00', 'La1': '5.05700e-01', 'Lb1': '5.35561e-01', 'Mz1': '4.67148e-01'}, 'Pd': {'La1': '5.06174e-01', 'Lb1': '5.37481e-01', 'Mz1': '4.44887e-01'}, 'Ag': {'La1': '4.98260e-01', 'Lb1': '5.30439e-01', 'Mz1': '4.30077e-01'}, 'Cd': {'La1': '5.05865e-01', 'Lb1': '5.39947e-01', 'Mz1': '4.25568e-01'}, 'In': {'La1': '5.04319e-01', 'Lb1': '5.39780e-01', 'Mz1': '4.17840e-01', 'Mg1': '6.57558e-01'}, 'Sn': {'La1': '5.09935e-01', 'Lb1': '5.47270e-01', 'Mz1': '4.21105e-01', 'Mg1': '6.49928e-01'}, 'Sb': {'La1': '5.12226e-01', 'Lb1': '5.51281e-01', 'Mz1': '4.21279e-01', 'Mg1': '6.39031e-01'}, 'Te': {'La1': '5.26569e-01', 'Lb1': '5.68362e-01', 'Mz1': '4.32137e-01', 'Mg1': '6.44967e-01'}, 'I': {'La1': '5.14608e-01', 'Lb1': '5.57163e-01', 'Mz1': '4.22139e-01', 'Mg1': '6.19924e-01'}, 'Xe': {'La1': '5.24463e-01', 'Lb1': '5.69649e-01', 'Mz1': '4.68719e-01', 'Mg1': '6.78135e-01'}, 'Cs': {'La1': '5.23529e-01', 'Lb1': '5.70196e-01', 'Mz1': '4.64699e-01', 'Mg1': '6.62293e-01'}, 'Ba': {'La1': '5.34069e-01', 'Lb1': '5.83511e-01', 'Mz1': '4.67297e-01', 'Mg1': '6.57819e-01'}, 'La': {'La1': '5.33425e-01', 'Lb1': '5.84777e-01', 'Mz1': '4.58435e-01', 'Mg1': '6.39833e-01'}, 'Ce': {'La1': '5.31928e-01', 'Lb1': '5.85111e-01'}, 'Pr': {'La1': '5.28869e-01', 'Lb1': '5.83851e-01', 'Ma1': '4.38386e-01', 'Mz1': '4.49639e-01', 'Mg1': '6.18119e-01'}, 'Nd': {'La1': '5.35635e-01', 'Lb1': '5.93536e-01', 'Ma1': '4.11091e-01', 'Mz1': '4.22006e-01', 'Mg1': '5.76148e-01'}, 'Pm': {'La1': '5.33684e-01', 'Lb1': '5.93634e-01', 'Ma1': '3.99029e-01', 'Mz1': '4.10431e-01', 'Mg1': '5.56685e-01'}, 'Sm': {'La1': '5.49178e-01', 'Lb1': '6.13232e-01', 'Ma1': '4.00913e-01', 'Mz1': '4.12353e-01'}, 'Eu': {'La1': '5.51261e-01', 'Lb1': '6.18013e-01', 'Ma1': '3.91117e-01', 'Mz1': '4.03409e-01'}, 'Gd': {'La1': '5.67189e-01', 'Lb1': '6.38476e-01', 'Ma1': '3.91704e-01', 'Mz1': '4.04381e-01'}, 'Tb': {'La1': '5.70624e-01', 'Lb1': '6.45045e-01', 'Ma1': '3.83551e-01', 'Mz1': '3.96090e-01'}, 'Dy': {'La1': '5.85460e-01', 'Lb1': '6.64663e-01', 'Ma1': '3.77862e-01', 'Mz1': '3.91055e-01'}, 'Ho': {'La1': '5.88659e-01', 'Lb1': '6.71274e-01', 'Ma1': '3.71586e-01', 'Mz1': '3.84865e-01'}, 'Er': {'La1': '5.96254e-01', 'Lb1': '6.83067e-01', 'Ma1': '3.64371e-01', 'Mz1': '3.78093e-01'}, 'Tm': {'La1': '6.02152e-01', 'Lb1': '6.93047e-01', 'Ma1': '3.55756e-01', 'Mz1': '3.69489e-01'}, 'Yb': {'La1': '6.17735e-01', 'Lb1': '7.14360e-01', 'Ma1': '3.55218e-01', 'Mz1': '3.68860e-01'}, 'Lu': {'La1': '6.25766e-01', 'Lb1': '7.27232e-01', 'Ma1': '3.54027e-01'}, 'Hf': {'La1': '6.42767e-01', 'Lb1': '7.50714e-01', 'Ma1': '3.51486e-01', 'Na1': '4.52786e+00'}, 'Ta': {'La1': '6.56343e-01', 'Lb1': '7.70329e-01', 'Ma1': '3.46374e-01', 'Na1': '4.35880e+00'}, 'W': {'La1': '6.73991e-01', 'Lb1': '7.95195e-01', 'Ma1': '3.41710e-01', 'Na1': '4.29405e+00'}, 'Re': {'La1': '6.88316e-01', 'Lb1': '8.16463e-01', 'Ma1': '3.38218e-01'}, 'Os': {'La1': '7.12581e-01', 'Lb1': '8.49883e-01', 'Ma1': '3.36600e-01'}, 'Ir': {'La1': '7.32378e-01', 'Lb1': '8.78311e-01', 'Ma1': '3.30316e-01', 'Na1': '3.96275e+00'}, 'Pt': {'La1': '7.59814e-01', 'Lb1': '9.16413e-01', 'Ma1': '3.25640e-01', 'Na1': '3.89910e+00'}, 'Au': {'La1': '7.85243e-01', 'Lb1': '9.52694e-01', 'Ma1': '3.19403e-01', 'Na1': '3.83686e+00'}, 'Hg': {'La1': '8.16157e-01', 'Lb1': '9.96008e-01', 'Ma1': '3.16574e-01', 'Na1': '3.85492e+00'}, 'Tl': {'La1': '8.54296e-01', 'Lb1': '1.04880e+00', 'Ma1': '3.14438e-01'}, 'Pb': {'La1': '8.94557e-01', 'Lb1': '1.10531e+00', 'Ma1': '3.10504e-01', 'Na1': '3.97231e+00'}, 'Bi': {'La1': '9.30226e-01', 'Lb1': '1.15668e+00', 'Ma1': '3.05023e-01', 'Na1': '3.97923e+00'}, 'Po': {'La1': '9.62629e-01', 'Lb1': '1.20508e+00', 'Ma1': '2.97722e-01'}, 'At': {'La1': '1.00547e+00', 'Lb1': '1.26703e+00', 'Ma1': '2.91770e-01'}, 'Rn': {'La1': '1.10732e+00', 'Lb1': '1.40473e+00', 'Ma1': '3.00835e-01'}, 'Fr': {'La1': '1.16189e+00', 'Lb1': '1.48454e+00', 'Ma1': '2.94695e-01'}, 'Ra': {'La1': '1.23236e+00', 'Lb1': '1.58621e+00', 'Ma1': '2.90890e-01'}, 'Ac': {'La1': '1.29927e+00', 'Lb1': '1.68479e+00', 'Ma1': '2.85144e-01'}, 'Th': {'La1': '1.39632e+00', 'Lb1': '1.82458e+00', 'Ma1': '2.84257e-01'}, 'Pa': {'La1': '1.46450e+00', 'Lb1': '1.92861e+00', 'Ma1': '2.75479e-01'}, 'U': {'La1': '1.56935e+00', 'Lb1': '2.08401e+00', 'Ma1': '2.76069e-01'}, 'Np': {'La1': '1.67460e+00', 'Lb1': '2.24244e+00', 'Ma1': '2.68009e-01'}, 'Pu': {'La1': '1.77254e+00', 'Lb1': '2.39411e+00', 'Ma1': '2.68562e-01'}, 'Am': {'La1': '1.92571e+00', 'Lb1': '2.62418e+00', 'Ma1': '2.60301e-01'}, 'Cm': {'La1': '2.07864e+00', 'Lb1': '2.85895e+00', 'Ma1': '2.57507e-01'}}
(.02409*2850)/(.04025*1016.), (.68792*7)/  (.04025*1016)


(1.6788893236171565, 0.1177541937692571)
from pyTEMlib.xrpa_x_sections import x_sections

{'name': 'Sr', 'barns': 1454970000000.0002, 'NumEdges': 12, 'atomic_weight': 87.62, 'nominal_density': 2.54, 'photoabs_to_sigma': 145.497, 'lines': {'K-L3': {'weight': 1.0, 'position': 14165.000000000002}, 'K-L2': {'weight': 0.564, 'position': 14097.800000000003}, 'K-M3': {'weight': 0.144, 'position': 15835.500000000002}, 'K-N3': {'weight': 0.0328, 'position': 16084.700000000003}, 'K-M2': {'weight': 0.0731, 'position': 15824.800000000003}, 'K-M5': {'weight': 0.000368, 'position': 15971.500000000002}, 'L3-M5': {'weight': 1.0, 'position': 1806.5}, 'L3-M4': {'weight': 0.114, 'position': 1804.6}, 'L3-M1': {'weight': 0.0412, 'position': 1582.1}, 'L2-N3': {'weight': 0.00524, 'position': 1986.9}, 'L2-M4': {'weight': 0.499, 'position': 1871.8000000000002}, 'L2-M3': {'weight': 0.000116, 'position': 1737.7000000000003}, 'L2-N1': {'weight': 0.00187, 'position': 1969.1000000000001}, 'L2-M1': {'weight': 0.0261, 'position': 1649.3000000000002}, 'L1-M3': {'weight': 0.0274, 'position': 1947.1999999999998}, 'L1-M2': {'weight': 0.0394, 'position': 1936.4999999999998}, 'L1-N2': {'weight': 0.000191, 'position': 2196.3999999999996}, 'L1-N3': {'weight': 0.00341, 'position': 2196.3999999999996}}, 'fluorescent_yield': {'K': 0.68792, 'L': 0.02409}, 'total_fluorescent_yield': {'K': 0.68568, 'L1': 0.026337, 'L2': 0.028651, 'L3': 0.026561}, 'N3': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 19.900000000000002, 'factor': 1.0, 'twin': 'N2'}, 'N2': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 19.900000000000002, 'factor': 0.5}, 'N1': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 37.699999999999996, 'factor': 1.0}, 'M5': {'filename': 'Sr.M5', 'excl before': 5, 'excl after': 50, 'onset': 133.1, 'factor': 1.0, 'twin': 'M4', 'shape': 'delayed'}, 'M4': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 135.0, 'factor': 0.6666666666666666}, 'M3': {'filename': 'Sr.M3', 'excl before': 5, 'excl after': 50, 'onset': 269.1, 'factor': 1.0, 'twin': 'M2'}, 'M2': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 279.8, 'factor': 0.5}, 'M1': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 357.5, 'factor': 1.0}, 'L3': {'filename': 'Sr.L3', 'excl before': 5, 'excl after': 50, 'onset': 1939.6, 'factor': 1.0, 'twin': 'L2', 'shape': 'white_line'}, 'L2': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 2006.8000000000002, 'factor': 0.5}, 'L1': {'filename': 'Sr.L1', 'excl before': 5, 'excl after': 50, 'onset': 2216.2999999999997, 'factor': 1.0}, 'K1': {'filename': 'None', 'excl before': 5, 'excl after': 50, 'onset': 16104.600000000002, 'factor': 1.0}, 'ene': array([1.069000e+01, 1.142761e+01, 1.221612e+01, 1.305903e+01, 1.396010e+01, 1.492335e+01, 1.595306e+01, 1.705382e+01, 1.823053e+01, 1.948844e+01, 1.950200e+01, 1.980050e+01, 1.988010e+01, 1.999950e+01, 2.029800e+01, 2.083314e+01, 2.227063e+01, 2.380730e+01, 2.545001e+01, 2.720606e+01, 2.908327e+01, 3.109002e+01, 3.323523e+01, 3.552846e+01, 3.694600e+01, 3.751150e+01, 3.766230e+01, 3.788850e+01, 3.797993e+01, 3.845400e+01, 4.060054e+01, 4.340198e+01, 4.639671e+01, 4.959809e+01, 5.302035e+01, 5.667876e+01, 6.058959e+01, 6.477028e+01, 6.923942e+01, 7.401695e+01, 7.912411e+01, 8.458368e+01, 9.041995e+01, 9.665893e+01, 1.033284e+02, 1.104581e+02, 1.180797e+02, 1.262272e+02, 1.304380e+02, 1.323000e+02, 1.324345e+02, 1.329669e+02, 1.337655e+02, 1.343250e+02, 1.349368e+02, 1.356750e+02, 1.357620e+02, 1.377000e+02, 1.442475e+02, 1.542005e+02, 1.648404e+02, 1.762144e+02, 1.883732e+02, 2.013709e+02, 2.152655e+02, 2.301188e+02, 2.459970e+02, 2.629708e+02, 2.637180e+02, 2.677545e+02, 2.688309e+02, 2.704455e+02, 2.742040e+02, 2.744820e+02, 2.784010e+02, 2.795202e+02, 2.811158e+02, 2.811990e+02, 2.853960e+02, 3.005128e+02, 3.212482e+02, 3.434143e+02, 3.503500e+02, 3.557125e+02, 3.571425e+02, 3.592875e+02, 3.646500e+02, 3.671099e+02, 3.924405e+02, 4.195189e+02, 4.484657e+02, 4.794098e+02, 5.124891e+02, 5.478508e+02, 5.856525e+02, 6.260625e+02, 6.692609e+02, 7.154399e+02, 7.648052e+02, 8.175768e+02, 8.739896e+02, 9.342948e+02, 9.987612e+02, 1.067676e+03, 1.141345e+03, 1.220098e+03, 1.304285e+03, 1.394281e+03, 1.490486e+03, 1.593329e+03, 1.703269e+03, 1.820795e+03, 1.900808e+03, 1.929902e+03, 1.937660e+03, 1.946430e+03, 1.949298e+03, 1.966664e+03, 1.978392e+03, 1.996766e+03, 2.004793e+03, 2.016834e+03, 2.046936e+03, 2.080733e+03, 2.171974e+03, 2.205219e+03, 2.214084e+03, 2.224304e+03, 2.227382e+03, 2.260626e+03, 2.377781e+03, 2.541848e+03, 2.717235e+03, 2.904724e+03, 3.105150e+03, 3.319406e+03, 3.548445e+03, 3.793288e+03, 4.055024e+03, 4.334821e+03, 4.633924e+03, 4.953664e+03, 5.295467e+03, 5.660855e+03, 6.051453e+03, 6.469004e+03, 6.915365e+03, 7.392525e+03, 7.902609e+03, 8.447890e+03, 9.030794e+03, 9.653919e+03, 1.032004e+04, 1.103212e+04, 1.179334e+04, 1.260708e+04, 1.347697e+04, 1.440688e+04, 1.540095e+04, 1.578251e+04, 1.602408e+04, 1.608850e+04, 1.618512e+04, 1.642669e+04, 1.646362e+04, 1.759961e+04, 1.881398e+04, 2.011215e+04, 2.149988e+04, 2.298338e+04, 2.456923e+04, 2.626450e+04, 2.807676e+04, 3.001405e+04, 3.208502e+04, 3.429889e+04, 3.666551e+04, 3.919543e+04, 4.189992e+04, 4.479101e+04, 4.788159e+04, 5.118542e+04, 5.471721e+04, 5.849270e+04, 6.252870e+04, 6.684318e+04, 7.145536e+04, 7.638578e+04, 8.165640e+04, 8.729069e+04, 9.331374e+04, 9.975239e+04]), 'dat': array([0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 6.54052664e+17, 6.18653244e+17, 5.61080081e+17, 4.36258205e+17, 3.29361559e+17, 2.41103079e+17, 1.73068682e+17, 1.23000254e+17, 8.72341813e+16, 6.21403137e+16, 4.46908585e+16, 3.70624508e+16, 3.45133434e+16, 3.38731566e+16, 3.37349344e+16, 3.33770118e+16, 3.16019484e+16, 2.51287869e+16, 1.94325793e+16, 1.54605112e+16, 1.26602760e+16, 1.06583827e+16, 9.20210326e+15, 8.11960558e+15, 7.29449210e+15, 6.64775793e+15, 6.12600569e+15, 5.69300662e+15, 5.32548119e+15, 5.00742475e+15, 4.72588806e+15, 4.47199579e+15, 4.23934609e+15, 4.02371954e+15, 3.82147870e+15, 3.72632367e+15, 3.68587550e+15, 3.68296556e+15, 3.67161680e+15, 6.51491917e+15, 6.60978321e+15, 6.71745099e+15, 8.77448758e+15, 8.80169552e+15, 9.44624723e+15, 1.22594317e+16, 1.85217681e+16, 2.64077055e+16, 3.48130672e+16, 4.25316830e+16, 4.85625337e+16, 5.23338159e+16, 5.37465918e+16, 5.30540261e+16, 5.07173443e+16, 5.05893069e+16, 4.98705517e+16, 4.96726758e+16, 5.34643276e+16, 5.27586672e+16, 5.27048333e+16, 5.19409740e+16, 5.17183636e+16, 5.31107699e+16, 5.30947652e+16, 5.22552476e+16, 4.91430667e+16, 4.48523602e+16, 4.04758104e+16, 3.91794322e+16, 3.82031473e+16, 3.79470726e+16, 3.92187164e+16, 3.82628011e+16, 3.78335849e+16, 3.37014701e+16, 2.98676242e+16, 2.63596915e+16, 2.31849470e+16, 2.03346607e+16, 1.77884632e+16, 1.55201650e+16, 1.35076505e+16, 1.17315686e+16, 1.01699493e+16, 8.80125903e+15, 7.60571018e+15, 6.56409716e+15, 5.65896032e+15, 4.87385851e+15, 4.14288158e+15, 3.50094881e+15, 2.95329811e+15, 2.49483706e+15, 2.11072498e+15, 1.78699415e+15, 1.51418728e+15, 1.28488401e+15, 1.09175129e+15, 9.83632468e+14, 9.48189399e+14, 9.39052188e+14, 4.05791133e+15, 4.04161567e+15, 3.94486016e+15, 3.88142347e+15, 3.78466796e+15, 3.74334682e+15, 5.16848993e+15, 4.96610360e+15, 4.75513295e+15, 4.25040386e+15, 4.08657424e+15, 4.04438011e+15, 4.59755970e+15, 4.58170053e+15, 4.41525196e+15, 3.89655516e+15, 3.31122073e+15, 2.80678263e+15, 2.37814846e+15, 2.00436667e+15, 1.68325479e+15, 1.41474008e+15, 1.19007816e+15, 9.97483783e+14, 8.32970325e+14, 6.94588128e+14, 5.78961662e+14, 4.82686298e+14, 4.02502901e+14, 3.35952573e+14, 2.80663713e+14, 2.34643012e+14, 1.96290003e+14, 1.64353411e+14, 1.37711456e+14, 1.14753484e+14, 9.47301868e+13, 7.79441979e+13, 6.41932764e+13, 5.29172589e+13, 4.36651047e+13, 3.60643414e+13, 2.98137903e+13, 2.46704713e+13, 2.30205353e+13, 2.20529803e+13, 2.18056354e+13, 1.60366793e+14, 1.53455686e+14, 1.52437207e+14, 1.26289941e+14, 1.05998929e+14, 8.92987838e+13, 7.50779070e+13, 6.28183298e+13, 5.24996825e+13, 4.38557057e+13, 3.65066523e+13, 3.03564941e+13, 2.52451845e+13, 2.08642698e+13, 1.72384846e+13, 1.42445928e+13, 1.17708528e+13, 9.72632895e+12, 8.03623580e+12, 6.63640916e+12, 5.48087199e+12, 4.52670266e+12, 3.73898191e+12, 3.08846482e+12, 2.54707048e+12, 2.09413832e+12, 1.72035653e+12, 1.41009873e+12, 1.15590092e+12, 9.47563762e+11])}
I_a = (1)
k = As_eds_cross_sections['K']['k_factor']
weight =  As_eds_cross_sections['_element']['atomic_weight']
I_a*k*weight/(1*28+I_a*k*weight), (k/I_a)*weight/(1*28+k/I_a*weight), 1*28/(1*28+(1/1.3)*0.2703*56)
(np.float64(0.9335227933722225), np.float64(0.06685662799596748), 0.706291426708682)

The above cross sections are for the excitation of a specific core-level of an atom. The sum of the experimental detected X-ray lines of one family are a fraction of those cross-sections.

Given the large discrepancy of excitation probabilities of the different core-levels, we use the lower-energy cross-sections if possible. Adding up the different families will result in sup-percent change in the total percentage of excitation probabilties.

Varambhia et al. used the linear thickness dependence of the signal to correlate the cross sections of EDS and EELS. This is an excellent way to callibrate the EDS system. And the correlation factor between EELS and EDS should be same for different X-Ray peak families of all elements. The similar method can be used to callibrate the cross section of ADF intensities.

k-Factor for relative composition

the k-factor for elements A and B is defined as:

kAB=QAωAaAϵAQBωBaBϵBk_{AB} = \frac{Q_A \omega_A a_A \epsilon_A}{Q_B \omega_B a_B \epsilon_B}
  • QQ [ionizations/e^- (atoms / cm2^2)): ionization cross section

  • ω\omega [X-rays/ ionization]: fluorescent yield = number of X-rays generated per ionization

  • AA [g/moles]: atomic weight

  • aa relative transition probability (a) of the same series of X-rays (α to β\alpha\ to\ \beta)

  • ε\varepsilon detector efficiencies for lines. If we look back to the Ch4_14 and the generation of X-rays, we see that we eliminated the variables of the sample (areal density) , because they are the same.

rewriting above equation we see that:

kAB=QAQBωAωBϵAϵBk_{AB} = \frac{Q_A}{Q_B} \frac{\omega_A}{\omega_B} \frac{\epsilon_A} {\epsilon_B}

We do not worry about the units. Since we are fitting all lines we can omit the relative transition probability.

The main uncertainties are: The cross section, and the fluorescent yield.

There are three customary reference elements for the k-factor:

  • silicon (semiconductor industrie and literature )

  • iron (materials science)

  • carbon (ThermoFisher seems to be using that)

Using silicon we need the kASik_{ASi} factors and

kAB=kASi/kBSik_{AB} = k_{ASi} / k_{BSi}

Only for thicker samples we need the density and thickness for absorption corrections.

Atomic-resolution EDS

For atomic-resolution studies, an integration of the counts over a Voronoi cell in the EDX map gives a partial cross-section for that column, by analogy with the approach for ADF cross-sections (E et al., 2013). For comparison, the popularly used ζ-factor method defined by (Watanabe et al., 2003) is:

ζxEDS=ρtIx×CxI0\zeta_x^{EDS} = \frac{\rho t}{I_x} \times C_x I_0

Where

  • IxI_x is the raw x-ray counts,

  • ρ is the sample density in kg/m3,

  • t is the sample thickness,

  • CxC_x is the weight fraction of element x.

In Eq. (2), the definition of sample density in the zeta-factor approach is in kg/m3 which makes absolute quantification of nanoparticles, in terms of “number of atoms”, cumbersome. Nonetheless, partial cross-section is very similar to the ζ-factor and can be equated to it using Eq. (4). Where M is the molar mass (in kg/mol) and NA is Avogadro’s constant.

Open a EDS spectrum file

Here the EDS.rto file in the example_data folder.

fileWidget = pyTEMlib.file_tools.FileWidget()
Loading...
datasets = fileWidget.datasets
dataset = fileWidget.selected_dataset
v = dataset.plot()
Loading...
#dataset.energy_scale+= 250
dataset.energy_scale[-1]
20220.0
dataset.view_original_metadata()
Core :
	MetadataDefinitionVersion : 7.9
	MetadataSchemaVersion : v1/2013/07
	guid : 00000000000000000000000000000000
Instrument :
	ControlSoftwareVersion : 3.21.1
	Manufacturer : FEI Company
	InstrumentId : 4018
	InstrumentClass : Titan
	InstrumentModel : Spectra
	ComputerName : TITAN52340180
Acquisition :
	AcquisitionStartDatetime :
		DateTime : 1744132943
	AcquisitionDatetime :
		DateTime : 1744132970
	BeamType : 
	SourceType : XFEG
Optics :
	GunLensSetting : 783.09402465820313
	ExtractorVoltage : 3600.03662109375
	AccelerationVoltage : 200000
	SpotIndex : 7
	C1LensIntensity : -0.45199715939082202
	C2LensIntensity : 0.19444500112822424
	C3LensIntensity : 0.3542091236880327
	ObjectiveLensIntensity : 0.82387587258637041
	IntermediateLensIntensity : 0.060336265199823894
	DiffractionLensIntensity : 0.19150135873874147
	Projector1LensIntensity : 0.28091570010789807
	Projector2LensIntensity : 0.91034079367771092
	LorentzLensIntensity : 0
	MiniCondenserLensIntensity : 0.34343568932091073
	BeamConvergence : 0.029997306394484304
	ScreenCurrent : 3.2020921599819477e-10
	LastMeasuredScreenCurrent : 3.2020921599819477e-10
	FullScanFieldOfView :
		x : 8.1985106983538468e-09
		y : 8.1985106983538468e-09
	Focus : -1.7021463355063892e-07
	StemFocus : 0
	Defocus : -1.7021463355063892e-07
	HighMagnificationMode : None
	Apertures :
		Aperture-0 :
			Name : C1
			Number : 1
			MechanismType : Motorized
			Type : Circular
			Diameter : 0.002
			Enabled : 0
		Aperture-1 :
			Name : C2
			Number : 2
			MechanismType : Motorized
			Type : Circular
			Diameter : 6.9999999999999994e-05
			Enabled : 1
		Aperture-2 :
			Name : C3
			Number : 3
			MechanismType : Motorized
			Type : Circular
			Diameter : 0.002
			Enabled : 0
		Aperture-3 :
			Name : OBJ
			Number : 4
			MechanismType : Motorized
			Type : None
		Aperture-4 :
			Name : SA
			Number : 5
			MechanismType : Motorized
			Type : None
	OperatingMode : 2
	TemOperatingSubMode : None
	ProjectorMode : 1
	EFTEMOn : false
	ObjectiveLensMode : HM
	IlluminationMode : Probe
	ProbeMode : 1
	CameraLength : 0.090999999999999998
EnergyFilter :
	EntranceApertureType : 
Stage :
	Position :
		x : -8.8828500000001313e-06
		y : -7.3590462000000029e-05
		z : -8.8422389105847642e-05
	AlphaTilt : -0.017956972000000068
	BetaTilt : 0.013796762563288212
	HolderType : FEI Double Tilt
Scan :
	ScanSize :
		width : 0
		height : 0
	MainsLockOn : false
	FrameTime : 4.7841439999999995
	ScanRotation : 0
Vacuum :
	VacuumMode : Ready
Detectors :
	Detector-0 :
		DetectorName : BF-S
		DetectorType : ScanningDetector
		Inserted : false
		Enabled : true
		Gain : 39
		Offset : 0
		CollectionAngleRange :
			begin : 0
			end : 0
	Detector-1 :
		DetectorName : BM-Ceta
		DetectorType : ImagingDetector
		ExposureMode : 
		Binning :
			width : 4
			height : 4
		ReadOutArea :
			left : 0
			top : 0
			right : 1024
			bottom : 1024
		ExposureTime : 0.10000000000000001
		Shutters :
			Shutter-0 :
				Position : PostSpecimen
				Type : Electrostatic
		DarkGainCorrectionType : 3
	Detector-2 :
		DetectorName : DF-S
		DetectorType : ScanningDetector
		Inserted : false
		Enabled : true
		Gain : 45.659520000000015
		Offset : -4.0012542384708496e-08
		CollectionAngleRange :
			begin : 0
			end : 0
	Detector-3 :
		DetectorName : EF-CCD
		DetectorType : ImagingDetector
		ExposureMode : 
		Binning :
			width : 4
			height : 4
		ReadOutArea :
			left : 0
			top : 0
			right : 1024
			bottom : 1024
		ExposureTime : 0.10000000000000001
		Shutters :
			Shutter-0 :
				Position : PostSpecimen
				Type : Electrostatic
		DarkGainCorrectionType : 3
	Detector-4 :
		DetectorName : Flucam
		DetectorType : ImagingDetector
		ExposureMode : 
		Gain : 0.69999999999999996
		Binning :
			width : 1
			height : 1
		ReadOutArea :
			left : 0
			top : 0
			right : 1024
			bottom : 1024
		ExposureTime : 0.012800000000000001
		Shutters :
			Shutter-0 :
				Position : None
				Type : Electrostatic
		DarkGainCorrectionType : 3
	Detector-5 :
		DetectorName : HAADF
		DetectorType : ScanningDetector
		Inserted : true
		Enabled : true
		Gain : 21.544316350646859
		Offset : -1.752
		CollectionAngleRange :
			begin : 0.072878546905260952
			end : 0.20000000000000001
	Detector-6 :
		DetectorName : SuperXG21
		DetectorType : AnalyticalDetector
		Inserted : true
		Enabled : true
		ElevationAngle : 0.31415926999999999
		AzimuthAngle : 0.78539816339744828
		CollectionAngle : 0.69999999999999996
		Dispersion : 5
		PulseProcessTime : 3.0000000000000001e-06
		RealTime : 26.200044074999997
		LiveTime : 25.717295459544289
		InputCountRate : 1334
		OutputCountRate : 1320
		AnalyticalDetectorShutterState : 4
		OffsetEnergy : -250
		ElectronicsNoise : 25.359999999999999
		BeginEnergy : 120
	Detector-7 :
		DetectorName : SuperXG22
		DetectorType : AnalyticalDetector
		Inserted : true
		Enabled : true
		ElevationAngle : 0.31415926999999999
		AzimuthAngle : 2.3561944901923448
		CollectionAngle : 0.69999999999999996
		Dispersion : 5
		PulseProcessTime : 3.0000000000000001e-06
		RealTime : 26.234130274999998
		LiveTime : 25.942911392788901
		InputCountRate : 242
		OutputCountRate : 240
		AnalyticalDetectorShutterState : 4
		OffsetEnergy : -250
		ElectronicsNoise : 25.170000000000002
		BeginEnergy : 120
	Detector-8 :
		DetectorName : SuperXG23
		DetectorType : AnalyticalDetector
		Inserted : true
		Enabled : true
		ElevationAngle : 0.31415926999999999
		AzimuthAngle : 3.9269908169872414
		CollectionAngle : 0.69999999999999996
		Dispersion : 5
		PulseProcessTime : 3.0000000000000001e-06
		RealTime : 26.2338126
		LiveTime : 25.204968704665891
		InputCountRate : 2978
		OutputCountRate : 2859
		AnalyticalDetectorShutterState : 4
		OffsetEnergy : -250
		ElectronicsNoise : 24.350000000000001
		BeginEnergy : 120
	Detector-9 :
		DetectorName : SuperXG24
		DetectorType : AnalyticalDetector
		Inserted : true
		Enabled : true
		ElevationAngle : 0.31415926999999999
		AzimuthAngle : 5.497787143782138
		CollectionAngle : 0.69999999999999996
		Dispersion : 5
		PulseProcessTime : 3.0000000000000001e-06
		RealTime : 26.252377599999999
		LiveTime : 25.576678845541675
		InputCountRate : 2936
		OutputCountRate : 2841
		AnalyticalDetectorShutterState : 4
		OffsetEnergy : -250
		ElectronicsNoise : 25.460000000000001
		BeginEnergy : 120
BinaryResult :
	AcquisitionUnit : 
	CompositionType : 
	DetectorIndex : 8
	Detector : SuperXG23
	Encoding : 
Sample : 
GasInjectionSystems : 
CustomProperties :
	Aperture[C1].Name :
		type : string
		value : 2000
	Aperture[C2].Name :
		type : string
		value : 70
	Aperture[C3].Name :
		type : string
		value : 2000
	Aperture[OBJ].Name :
		type : string
		value : None
	Aperture[SA].Name :
		type : string
		value : None
	Detectors[EF-CCD].CommercialName :
		type : string
		value : Continuum 1065
	Detectors[EF-CCD].ElectronCounted :
		type : bool
		value : 0
	Detectors[SuperXG21].BilatThresholdHi :
		type : double
		value : 0.0050390000000000001
	Detectors[SuperXG21].CommercialName :
		type : string
		value : Super-X G2
	Detectors[SuperXG21].DetectorConfigID :
		type : string
		value : 3101e35a-08ec-4b3d-9108-6aa5d0e49281
	Detectors[SuperXG21].DistanceToSample :
		type : double
		value : 12.42
	Detectors[SuperXG21].IncidentAngle :
		type : double
		value : 0.094596840000000001
	Detectors[SuperXG21].KMax :
		type : double
		value : 180
	Detectors[SuperXG21].KMin :
		type : double
		value : 120
	Detectors[SuperXG21].PulsePairResolutionTime :
		type : double
		value : 4.9999999999999998e-07
	Detectors[SuperXG21].SpectrumBeginEnergy :
		type : long
		value : 120
	Detectors[SuperXG22].BilatThresholdHi :
		type : double
		value : 0.0050390000000000001
	Detectors[SuperXG22].CommercialName :
		type : string
		value : Super-X G2
	Detectors[SuperXG22].DetectorConfigID :
		type : string
		value : 3101e35a-08ec-4b3d-9108-6aa5d0e49281
	Detectors[SuperXG22].DistanceToSample :
		type : double
		value : 12.42
	Detectors[SuperXG22].IncidentAngle :
		type : double
		value : 0.094596840000000001
	Detectors[SuperXG22].KMax :
		type : double
		value : 180
	Detectors[SuperXG22].KMin :
		type : double
		value : 120
	Detectors[SuperXG22].PulsePairResolutionTime :
		type : double
		value : 4.9999999999999998e-07
	Detectors[SuperXG22].SpectrumBeginEnergy :
		type : long
		value : 120
	Detectors[SuperXG23].BilatThresholdHi :
		type : double
		value : 0.0050390000000000001
	Detectors[SuperXG23].CommercialName :
		type : string
		value : Super-X G2
	Detectors[SuperXG23].DetectorConfigID :
		type : string
		value : 3101e35a-08ec-4b3d-9108-6aa5d0e49281
	Detectors[SuperXG23].DistanceToSample :
		type : double
		value : 12.42
	Detectors[SuperXG23].IncidentAngle :
		type : double
		value : 0.094596840000000001
	Detectors[SuperXG23].KMax :
		type : double
		value : 180
	Detectors[SuperXG23].KMin :
		type : double
		value : 120
	Detectors[SuperXG23].PulsePairResolutionTime :
		type : double
		value : 4.9999999999999998e-07
	Detectors[SuperXG23].SpectrumBeginEnergy :
		type : long
		value : 120
	Detectors[SuperXG24].BilatThresholdHi :
		type : double
		value : 0.0050390000000000001
	Detectors[SuperXG24].CommercialName :
		type : string
		value : Super-X G2
	Detectors[SuperXG24].DetectorConfigID :
		type : string
		value : 3101e35a-08ec-4b3d-9108-6aa5d0e49281
	Detectors[SuperXG24].DistanceToSample :
		type : double
		value : 12.42
	Detectors[SuperXG24].IncidentAngle :
		type : double
		value : 0.094596840000000001
	Detectors[SuperXG24].KMax :
		type : double
		value : 180
	Detectors[SuperXG24].KMin :
		type : double
		value : 120
	Detectors[SuperXG24].PulsePairResolutionTime :
		type : double
		value : 4.9999999999999998e-07
	Detectors[SuperXG24].SpectrumBeginEnergy :
		type : long
		value : 120
	Optics.MonoSpotSize :
		type : string
		value : <=11
	StemMagnification :
		type : double
		value : 10000000
AcquisitionSettings :
	encoding : uint16
	bincount : 4096
	StreamEncoding : uint16
	Size : 2097152
import codecs

def open_Bruker(fname):
    tree = ET.parse(fname)
    root = tree.getroot()
    spectrum_number = 0
    i=0
    image_count = 0
    o_count = 0
    tags = {}
    for neighbor in root.iter():
        #print(neighbor.attrib.keys())
        if 'Type' in neighbor.attrib:
            if 'verlay3' in neighbor.attrib['Type'] :
                semImage = neighbor
                #print(neighbor.attrib['Type'])
                if 'Name' in neighbor.attrib:
                    print('\t',neighbor)
                    print('\t',neighbor.attrib['Type'])
                    print('\t',neighbor.attrib['Name'])
                    print('\t',neighbor.find("./ClassInstance[@Type='TRTSpectrumList']"))
            #if 'TRTImageOverlay' in neighbor.attrib['Type'] : 
            if 'TRTCrossOverlayElement'in neighbor.attrib['Type'] :
                if 'Spectrum' in neighbor.attrib['Name']:
                    #print(o_count)
                    o_count+=1
                    if 'overlay' not in tags:
                        tags['overlay']= {}
                    if 'image'+str(image_count) not in tags['overlay']:
                        tags['overlay']['image'+str(image_count)] ={}
                    tags['overlay']['image'+str(image_count)][neighbor.attrib['Name']] ={}

                    over = tags['overlay']['image'+str(image_count)][neighbor.attrib['Name']]


                    for child in neighbor.iter():
                        if 'verlay' in child.tag:
                            #print(child.tag)
                            pos = child.find('./Pos')
                            if pos != None:
                                over['posX'] = int(pos.find('./PosX').text) 
                                over['posY'] = int(pos.find('./PosY').text) 
                #dd = neighbor.find('Top')
                #print('dd',dd)                       
                #print(neighbor.attrib)
            if 'TRTImageData' in neighbor.attrib['Type'] :
                #print('found image ', image_count)
                dd = neighbor.find("./ClassInstance[@Type='TRTCrossOverlayElement']")
                if dd != None:
                    print('found in image')
                image = neighbor
                if 'image' not in tags:
                    tags['image']={}
                tags['image'][str(image_count)]={}
                im = tags['image'][str(image_count)]
                im['width'] = int(image.find('./Width').text)  # in pixels
                im['height'] = int(image.find('./Height').text)  # in pixels
                im['dtype'] = 'u' + image.find('./ItemSize').text  # in bytes ('u1','u2','u4') 
                im['plane_count'] = int(image.find('./PlaneCount').text)
                im['data'] = {}
                for j in range( im['plane_count']):
                    #print(i)

                    img = image.find("./Plane" + str(i))
                    raw = codecs.decode((img.find('./Data').text).encode('ascii'),'base64')
                    array1 = np.frombuffer(raw, dtype= im['dtype'])
                    #print(array1.shape)
                    im['data'][str(j)]= np.reshape(array1,(im['height'], im['width']))
                image_count +=1     
            if 'TRTDetectorHeader' == neighbor.attrib['Type'] :
                detector = neighbor
                tags['detector'] ={}
                for child in detector:
                    if child.tag == "WindowLayers":
                        tags['detector']['window']={}
                        for child2 in child:
                            tags['detector']['window'][child2.tag]={}
                            tags['detector']['window'][child2.tag]['Z'] = child2.attrib["Atom"]
                            tags['detector']['window'][child2.tag]['thickness'] = float(child2.attrib["Thickness"])*1e-5  # stupid units
                            if 'RelativeArea' in child2.attrib:
                                tags['detector']['window'][child2.tag]['relative_area'] = float(child2.attrib["RelativeArea"])
                            #print(child2.tag,child2.attrib)
                            
                    else:
                        #print(child.tag , child.text)
                        if child.tag != 'ResponseFunction':
                            if child.text !=None:
                                tags['detector'][child.tag]=child.text
                                
                                if child.tag == 'SiDeadLayerThickness':
                                    tags['detector'][child.tag]=float(child.text)*1e-6
                                    #print(child.tag)
                                if child.tag == 'DetectorThickness':
                                    tags['detector'][child.tag]=float(child.text)*1e-1
            # ESMA could stand for Electron Scanning Microscope Analysis
            if 'TRTESMAHeader' == neighbor.attrib['Type'] :
                esma = neighbor
                tags['esma'] ={}
                for child in esma:
                    if child.tag in ['PrimaryEnergy', 'ElevationAngle',  'AzimutAngle', 'Magnification',   'WorkingDistance' ]:
                        tags['esma'][child.tag]=float(child.text)

            if 'TRTSpectrum' == neighbor.attrib['Type'] :
                if 'Name' in neighbor.attrib:
                    spectrum = neighbor

                    TRTHeader = spectrum.find('./TRTHeaderedClass')

                    if TRTHeader != None:
                        hardware_header = TRTHeader.find("./ClassInstance[@Type='TRTSpectrumHardwareHeader']")

                        spectrum_header = spectrum.find("./ClassInstance[@Type='TRTSpectrumHeader']")
                        #print(i, TRTHeader) 
                        tags[spectrum_number] =  {}
                        tags[spectrum_number]['hardware_header'] ={}
                        if hardware_header != None:
                            for child in hardware_header:
                                tags[spectrum_number]['hardware_header'][child.tag]=child.text
                        tags[spectrum_number]['detector_header'] ={}
                        tags[spectrum_number]['spectrum_header'] ={}
                        for child in spectrum_header:
                            tags[spectrum_number]['spectrum_header'][child.tag]=child.text


                        tags[spectrum_number]['data'] = np.fromstring(spectrum.find('./Channels').text,dtype='Q', sep=",")

                        spectrum_number+=1
    return tags
fname = '../example_data/EDS.rto'

tags = open_Bruker(fname)
datasets = fileWidget.datasets
dataset += datasets['Channel_001']+datasets['Channel_002']+datasets['Channel_003']
dataset.energy_scale/= 2
dataset.energy_scale-= 250
v = dataset.plot()
Loading...
spectrum1 =tags[0]['data']
spectrum2 =tags[1]['data']
offset = float(tags[0]['spectrum_header']['CalibAbs'])
scale  = float(tags[0]['spectrum_header']['CalibLin'])
energy_scale1 = np.arange(len(spectrum1))*scale+offset
offset = float(tags[1]['spectrum_header']['CalibAbs'])
scale  = float(tags[1]['spectrum_header']['CalibLin'])
energy_scale2 = np.arange(len(spectrum2))*scale+offset

plt.figure(figsize=(10,4))

ax1 = plt.subplot(1,2,1)
ax1.imshow(tags['image']['0']['data']['0'])
for key in tags['overlay']['image1']:
    d = tags['overlay']['image1'][key]
    ax1.scatter ([d['posX']], [d['posY']], marker="x", color='r')
    ax1.text(d['posX']+5, d['posY']-5, key, color='r')

ax2 = plt.subplot(1,2,2)    
plt.plot(energy_scale1,spectrum1, label = 'spectrum 1')
plt.plot(energy_scale2,spectrum2, label = 'spectrum 2')
plt.xlabel('energy [keV]')
plt.xlim(0,5)
plt.ylim(0)
plt.tight_layout()
plt.legend();
Loading...

Peak Finding

We can also use the peak finding routine of the scipy.signal to find all the maxima.

import scipy as sp 
import scipy.signal as sig
spectrum1 = dataset
energy_scale1 = dataset.energy_scale.values
start = np.searchsorted(energy_scale1, 50)
## we use half the width of the resolution for smearing
width = int(np.ceil(125*1e-3/2 /(energy_scale1[1]-energy_scale1[0])/2)*2+1)
new_spectrum =  sp.signal.savgol_filter(spectrum1[start:], width, 2) ## we use half the width of the resolution for smearing
new_energy_scale = energy_scale1[start:]
major_peaks, _  = sp.signal.find_peaks(new_spectrum, prominence=1000)  
minor_peaks, _  = sp.signal.find_peaks(new_spectrum, prominence=30)  
peaks = major_peaks

spectrum1 = np.array(spectrum1)
plt.figure()
plt.plot(energy_scale1,spectrum1, label = 'spectrum 1')
#plt.plot(energy_scale1,gaussian_filter(spectrum1, sigma=1), label = 'filtered spectrum 1')
plt.plot(new_energy_scale,new_spectrum, label = 'filtered spectrum 1')
plt.scatter( new_energy_scale[minor_peaks], new_spectrum[minor_peaks], color = 'blue')
plt.scatter( new_energy_scale[major_peaks], new_spectrum[major_peaks], color = 'red')

Loading...
plt.xlim(0,20000);

Peak identification

Here we look up all the elemetns and see whether the position of major line (K-L3, K-L2’ or ‘L3-M5’) coincides with a peak position as found above.

Then we plot all the lines of such an element with the appropriate weight.

The positions and the weight are tabulated in the ffast.pkl file introduced in the Characteristic X-Ray peaks notebook

edge_info = pyTEMlib.eels_tools.get_x_sections(38)
edge_info['lines']
{'K-L3': {'weight': 1.0, 'position': 14165.000000000002}, 'K-L2': {'weight': 0.564, 'position': 14097.800000000003}, 'K-M3': {'weight': 0.144, 'position': 15835.500000000002}, 'K-N3': {'weight': 0.0328, 'position': 16084.700000000003}, 'K-M2': {'weight': 0.0731, 'position': 15824.800000000003}, 'K-M5': {'weight': 0.000368, 'position': 15971.500000000002}, 'L3-M5': {'weight': 1.0, 'position': 1806.5}, 'L3-M4': {'weight': 0.114, 'position': 1804.6}, 'L3-M1': {'weight': 0.0412, 'position': 1582.1}, 'L2-N3': {'weight': 0.00524, 'position': 1986.9}, 'L2-M4': {'weight': 0.499, 'position': 1871.8000000000002}, 'L2-M3': {'weight': 0.000116, 'position': 1737.7000000000003}, 'L2-N1': {'weight': 0.00187, 'position': 1969.1000000000001}, 'L2-M1': {'weight': 0.0261, 'position': 1649.3000000000002}, 'L1-M3': {'weight': 0.0274, 'position': 1947.1999999999998}, 'L1-M2': {'weight': 0.0394, 'position': 1936.4999999999998}, 'L1-N2': {'weight': 0.000191, 'position': 2196.3999999999996}, 'L1-N3': {'weight': 0.00341, 'position': 2196.3999999999996}}


plt.figure()
#plt.plot(energy_scale1,spectrum2, label = 'spectrum 1')
#plt.plot(energy_scale1,gaussian_filter(spectrum1, sigma=1), label = 'filtered spectrum 1')
plt.plot(new_energy_scale,new_spectrum, label = 'filtered spectrum 1')

plt.gca().axhline(y=0,color='gray',  linewidth = 0.5);

out_tags = {}
out_tags['spectra'] = {}

out_tags['spectra'][0] = {}
out_tags['spectra'][0]['data'] = spectrum1
out_tags['spectra'][0]['energy_scale'] = energy_scale1
out_tags['spectra'][0]['energy_scale_start'] = start
out_tags['spectra'][0]['smooth_spectrum'] = new_spectrum
out_tags['spectra'][0]['smooth_energy_scale'] = new_energy_scale

out_tags['spectra'][0]['elements'] ={}


#print(ffast[6])
number_of_elements = 0

for peak in minor_peaks:
    print(peak)
    for z in range(5,82):
        edge_info  = pyTEMlib.eels_tools.get_x_sections(z)
        
        if 'lines' not in edge_info:
            pass
        elif 'K-L3' in edge_info['lines']:
            if abs(edge_info['lines']['K-L3']['position']- new_energy_scale[peak]) <10:
                out_tags['spectra'][0]['elements'][number_of_elements] = {}
                out_tags['spectra'][0]['elements'][number_of_elements]['element'] = edge_info['name']
                out_tags['spectra'][0]['elements'][number_of_elements]['found_lines'] = 'K-L3'
                out_tags['spectra'][0]['elements'][number_of_elements]['lines'] = edge_info['lines']
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_index'] = peak
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_energy'] = new_energy_scale[peak]*1e3
                number_of_elements += 1
                plt.plot([edge_info['lines']['K-L3']['position'], edge_info['lines']['K-L3']['position']], [0,new_spectrum[peak]], color = 'red')
                plt.text(new_energy_scale[peak],0, edge_info['name']+'\nK-L3', verticalalignment='top')
                for line in edge_info['lines']:
                    if 'K' in line:
                        if abs(edge_info['lines'][line]['position']-edge_info['lines']['K-L3']['position'])> 20:
                            if edge_info['lines'][line]['weight']>0.07:
                                #print(element, ffast[element]['lines'][line],new_spectrum[peak]*ffast[element]['lines'][line]['weight'])
                                plt.plot([edge_info['lines'][line]['position']/1000.,edge_info['lines'][line]['position']/1000.], [0,new_spectrum[peak]*edge_info['lines'][line]['weight']], color = 'red')
                                plt.text(edge_info['lines'][line]['position']/1000.,0, edge_info['name']+'\n'+line, verticalalignment='top')
                            
        elif 'K-L2' in edge_info['lines']:
            if abs(edge_info['lines']['K-L2']['position']- new_energy_scale[peak]) <10:
                plt.plot([new_energy_scale[peak],new_energy_scale[peak]], [0,new_spectrum[peak]], color = 'orange')
                plt.text(new_energy_scale[peak],0, ffast[element]['element']+'\nK-L2', verticalalignment='top')
                out_tags['spectra'][0]['elements'][number_of_elements] = {}
                out_tags['spectra'][0]['elements'][number_of_elements]['element'] = ffast[element]['element']
                out_tags['spectra'][0]['elements'][number_of_elements]['found_lines'] = 'K-L2'
                out_tags['spectra'][0]['elements'][number_of_elements]['lines'] = ffast[element]['lines']
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_index'] = peak
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_energy'] = new_energy_scale[peak]*1e3
                
                number_of_elements += 1
        if 'lines' not in edge_info:
            pass
        elif 'L3-M5' in edge_info['lines']:
            if abs(edge_info['lines']['L3-M5']['position']- new_energy_scale[peak]*1e3) <10:
                pass
                print('found_element', element,ffast[element]['lines']['L3-M5']['position'], new_energy_scale[peak] )
                #plt.scatter( new_energy_scale[peak], new_spectrum[peak], color = 'blue')
                out_tags['spectra'][0]['elements'][number_of_elements] = {}
                out_tags['spectra'][0]['elements'][number_of_elements]['element'] = ffast[element]['element']
                out_tags['spectra'][0]['elements'][number_of_elements]['found_lines'] = 'L3-M5'
                out_tags['spectra'][0]['elements'][number_of_elements]['lines'] = ffast[element]['lines']
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_index'] = peak
                out_tags['spectra'][0]['elements'][number_of_elements]['experimental_peak_energy'] = new_energy_scale[peak]*1e3
                
                number_of_elements += 1
for element in out_tags['spectra'][0]['elements']:                
    print(out_tags['spectra'][0]['elements'][element]['element'],out_tags['spectra'][0]['elements'][element]['found_lines'])
    
Loading...

Using that we get for all peaks in the low energy region:

from scipy.interpolate import interp1d
import scipy.constants as const

tags={'detector': { 'window': {'0': {'Z': 6,
                                     'thickness': 100}
                              },
                   'SiDeadLayerThickness': 20
                  }
      
    }


def detector_efficiency(tags, energy_scale):
    detector_Efficiency1 = np.ones(len(energy_scale))
    for key in tags['detector']['window']:
        Z = int(tags['detector']['window'][key]['Z'])
        if Z < 14:
            t = tags['detector']['window'][key]['thickness']

            ## interpolate mass absorption coefficient to our energy scale
            lin = interp1d(ffast[Z]['E']/1000.,ffast[Z]['photoabsorption'],kind='linear') 
            mu = lin(energy_scale) * ffast[Z]['nominal_density']*100. #1/cm -> 1/m
            detector_Efficiency1 = detector_Efficiency1 * np.exp(-mu * t)
            print(Z,t)
        
    t = float(tags['detector']['SiDeadLayerThickness'])*1e-6
    print(t)
    t = .30*1e-7
    print(t)
    lin = interp1d(ffast[14]['E']/1000.,ffast[14]['photoabsorption'],kind='linear') 
    mu = lin(energy_scale) * ffast[14]['nominal_density']*100. #1/cm -> 1/m
    detector_Efficiency1 = detector_Efficiency1 * np.exp(-mu * t)
    detector_thickness = float(tags['detector']['DetectorThickness'])*1e-1
    ## interpolate mass absorption coefficient to our energy scale
    mu_Si = lin(energy_scale) * ffast[14]['nominal_density']*100. #1/cm -> 1/m
    print(detector_thickness)
    
    detector_Efficiency2 = (1.0 - np.exp(-mu * detector_thickness))# * oo4pi;

    
    return detector_Efficiency1*detector_Efficiency2
detector_Efficiency = detector_efficiency(tags, new_energy_scale)    

plt.figure()
plt.plot(new_energy_scale*1000, detector_Efficiency)

plt.xlim(0,2000)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 42
     38     detector_Efficiency2 = (1.0 - np.exp(-mu * detector_thickness))# * oo4pi;
     41     return detector_Efficiency1*detector_Efficiency2
---> 42 detector_Efficiency = detector_efficiency(tags, new_energy_scale)    
     44 plt.figure()
     45 plt.plot(new_energy_scale*1000, detector_Efficiency)

Cell In[9], line 21, in detector_efficiency(tags, energy_scale)
     18 t = tags['detector']['window'][key]['thickness']
     20 ## interpolate mass absorption coefficient to our energy scale
---> 21 lin = interp1d(ffast[Z]['E']/1000.,ffast[Z]['photoabsorption'],kind='linear') 
     22 mu = lin(energy_scale) * ffast[Z]['nominal_density']*100. #1/cm -> 1/m
     23 detector_Efficiency1 = detector_Efficiency1 * np.exp(-mu * t)

NameError: name 'ffast' is not defined
Peaks = []
elements_peaks = []
intensities = []
for element in out_tags['spectra'][0]['elements']:
    el_dict = out_tags['spectra'][0]['elements'][element]
    
    position = el_dict['lines'][el_dict['found_lines']]['position']
    weight = 0
    for line in el_dict['lines']:
        if abs(position - el_dict['lines'][line]['position'])<20:
            weight += el_dict['lines'][line]['weight']
    index = np.searchsorted(new_energy_scale,el_dict['lines'][el_dict['found_lines']]['position']/1000.)  
    intensity = new_spectrum[index]/weight
    added_peaks = np.zeros(len(new_energy_scale))
    for line in el_dict['lines']: 
        if line[0] == el_dict['found_lines'][0]:
            if el_dict['lines'][line]['weight']> 0.01:
                p = getPeak(el_dict['lines'][line]['position']/1000.,new_energy_scale)*el_dict['lines'][line]['weight']
                Peaks.append(p)
                added_peaks = added_peaks + p 
    elements_peaks.append(added_peaks)        
    intensities.append(intensity)
    
plt.figure()

plt.plot(new_energy_scale,new_spectrum, label = 'filtered spectrum 1',color='red')
for i in range(len(elements_peaks)):
    plt.plot(new_energy_scale,elements_peaks[i]*intensities[i], label = f'Peak{i}')
    pass
peaks = np.array(Peaks)
plt.plot(new_energy_scale,peaks.sum(axis=0), label = f'Peaks', color = 'blue')
print(peaks.shape)
#plt.xlim(0,5)    
offset = float(tags[0]['spectrum_header']['CalibAbs'])
scale  = float(tags[0]['spectrum_header']['CalibLin'])
energy_scale1 = np.arange(len(spectrum1))*scale+offset
p = [1, 37, .3, offset, scale]
E_0= 20
E = new_energy_scale
N = detector_Efficiency * (p[0] + p[1]*(E_0-E)/E + p[2]*(E_0-E)**2/E)
N = de * (p[0] + p[1]*(E_0-E)/E + p[2]*(E_0-E)**2/E)

pp = p[0:5].copy()
for i in range(len(elements_peaks)):
    pp.append(intensities[i])

plt.plot(new_energy_scale,N, color= 'orange')

plt.xlim(0,4)

detector_Efficiency = de
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 18
     16 if line[0] == el_dict['found_lines'][0]:
     17     if el_dict['lines'][line]['weight']> 0.01:
---> 18         p = getPeak(el_dict['lines'][line]['position']/1000.,new_energy_scale)*el_dict['lines'][line]['weight']
     19         Peaks.append(p)
     20         added_peaks = added_peaks + p 

NameError: name 'getPeak' is not defined


def model(p,energy_scale):
    E = energy_scale
    
    spectrum = detector_Efficiency * (p[0] + p[1]*(E_0-E)/E + p[2]*(E_0-E)**2/E)
    
    for i in range(5,len(p)):
        spectrum = spectrum+elements_peaks[i-5]*abs(p[i])
    return spectrum
def getFWHM(E):
    return np.sqrt(2.5*(E-E_ref)+FWHM_ref**2)

def gaussian(enrgy_scale, mu, FWHM):
    sig = FWHM/2/np.sqrt(2*np.log(2))
    return np.exp(-np.power(enrgy_scale - mu, 2.) / (2 * np.power(sig, 2.)))

def getPeak(E, energy_scale):
    E_ref = 5895.0
    FWHM_ref = 136 #eV
    FWHM  = np.sqrt(2.5*(E*1e3-E_ref)+FWHM_ref**2)*1e-3
    return gaussian(energy_scale, E, FWHM)


Peaks = []
elements_peaks = []
intensities = []
for element in out_tags['spectra'][0]['elements']:
    el_dict = out_tags['spectra'][0]['elements'][element]
    
    position = el_dict['lines'][el_dict['found_lines']]['position']
    weight = 0
    for line in el_dict['lines']:
        if abs(position - el_dict['lines'][line]['position'])<20:
            weight += el_dict['lines'][line]['weight']
    index = np.searchsorted(new_energy_scale,el_dict['lines'][el_dict['found_lines']]['position']/1000.)  
    intensity = new_spectrum[index]/weight
    added_peaks = np.zeros(len(new_energy_scale))
    for line in el_dict['lines']: 
        if line[0] == el_dict['found_lines'][0]:
            if el_dict['lines'][line]['weight']> 0.01:
                p = getPeak(el_dict['lines'][line]['position']/1000.,new_energy_scale)*el_dict['lines'][line]['weight']
                Peaks.append(p)
                added_peaks = added_peaks + p 
    elements_peaks.append(added_peaks)        
    intensities.append(intensity)

    
p = [1, 37, .3, offset, scale]
E_0= 20
E = new_energy_scale
N = detector_Efficiency * (p[0] + p[1]*(E_0-E)/E + p[2]*(E_0-E)**2/E)

pp = p[0:5].copy()
for i in range(len(elements_peaks)):
    pp.append(intensities[i])

    

spectrum3 = model(pp[:-1],new_energy_scale)

plt.figure()

plt.plot(new_energy_scale,new_spectrum, label = 'filtered spectrum 1',color='red')
plt.plot(new_energy_scale, spectrum3)
plt.xlim(0,4)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 47
     43     elements_peaks.append(added_peaks)        
     44     intensities.append(intensity)
---> 47 p = [1, 37, .3, offset, scale]
     48 E_0= 20
     49 E = new_energy_scale

NameError: name 'offset' is not defined

from scipy.optimize import leastsq 

## background fitting 
def specfit(p, y, x):
    err = y - model(p,x)
    return err

p, lsq = leastsq(specfit, pp[:-1], args=(new_spectrum, new_energy_scale), maxfev=2000)
print( f'element\t end   \t   start ')
for i, element in enumerate(out_tags['spectra'][0]['elements']):
    if i<len(p)-5:
        el_dict = out_tags['spectra'][0]['elements'][element]
        print(f"{el_dict['element']:2s}: \t  {abs(p[i+5]):6.2f} \t  {pp[i+5]:.2f}")
#print(p,pp)
element	 end   	   start 
C : 	  16487.64 	  16444.49
O : 	  3267.00 	  3274.63
Mg: 	  2831.85 	  3127.80
Al: 	   84.40 	  334.63
P : 	  5267.50 	  5499.66
S : 	   82.35 	  234.96
K : 	  2526.57 	  2693.25
spectrum3 = model(p,new_energy_scale)

plt.figure()

plt.plot(new_energy_scale,new_spectrum, label = 'filtered spectrum 1',color='red')
plt.plot(new_energy_scale, spectrum3)
plt.plot(new_energy_scale, new_spectrum-spectrum3)
plt.gca().axhline(y=0,color='gray');

plt.xlim(0,4);
Loading...

Energy Scale

What happened?

Composition Basis

Castaing’s first approximation:

Ci=IiIPureC_i = \frac{I_i}{I_{Pure}}
  • 𝐶i𝐶_i = Concentration of element 𝑖𝑖

  • 𝐼𝑖𝐼_𝑖 = X-ray intensity of element 𝑖𝑖 in the sample

  • 𝐼𝑃𝑢𝑟𝑒𝐼_{𝑃𝑢𝑟𝑒} = X-ray intensity of a pure sample of the element

We call the ratio: IiIPure\frac{I_i}{I_{Pure}} k-ratio

k-ratio
  • Stainless to Fe-Ka ratio = 0.745

  • Stainless to Cr-Ka ratio = 0.208

  • Stainless to Ni-Ka ratio = 0.080

therefore:

  • Fe: 70.8 wt%

  • Cr: 17.2 wt%

  • Ni: 9.1 wt%

ZAF Correction

While the k-ratio is a good starting point, the basic approximation does not take absorption and interaction between the different elements into account. To increase the precision the following corrections are applied:

  • Z: Atomic number factor

  • A: sample Absorption

  • F: Fluorescence

Atomic Number Factor - Z

The stopping power is equal to the energy loss of the electron per unit length it travels in the material: 𝑆(𝐸)=𝑑𝐸/𝑑t𝑆(𝐸)=−𝑑𝐸/𝑑t

D.C. Joy, Microsc. Microanal. 7(2001): 159-169

S(E)ρZAEln(1.116(E+0.85J)J)S(E) \propto \rho \frac{Z}{AE} \ln \left( \frac{1.116 (E+0.85J)}{J}\right)
  • EE: electron energy

  • ρ\rho: density of material

  • ZZ: Atomic number

  • AA: atomic weight of material

  • JJ: Mean ionizaton potential

J(eV)=10.04+8.25exp(Z/11.22)J(eV) = 10.04 + 8.25 \exp(-Z/11.22)

The backscatter correction is the relation between how many photons are generated with and without backscattering.

R=11+0.008Z(1EcE0)R = \frac{1}{1+0.008Z(1-\frac{E_c}{E_0})}
  • RR: backscatter correction

  • ZZ: Mean atomic number of sample

  • E0E_0: electron energy

  • EcE_c: critical ionization energy

Absorption Correction

Absorption_Correction

The numerator describes how many X-rays are generated at a given depth and scales the number by the attenuation due to the escape path in the sample.

The denominator is the total number of X-rays generated without attenuation.

This means that A is a normalized value that describes how many of the generated X-rays actually escaped the sample.

Fluorescence Correction

Fluorescence_Correction

The high energy X-ray can originate from characteristic X-rays from elements with higher energy lines or from the bremsstrahlung. This means that even the element with the highest energy line can have a fluorescence correction.

Using ZAF

𝑊𝑒𝑖𝑔h𝑡%=𝑘𝑟𝑎𝑡𝑖𝑜𝑍𝐴𝐹100%𝑊𝑒𝑖𝑔ℎ𝑡 \% = \frac{𝑘−𝑟𝑎𝑡𝑖𝑜}{𝑍𝐴𝐹}∙100\%

ZAF assumptions

Several assumptions are made either directly or indirectly in the equations used to calculate the composition of a sample.

  • The sample is homogenous.

    • The density 𝜌 and the mass attenuation 𝜇/𝜌 is assumed to be constant.

  • The sample is flat.

    • 𝑧 is well defined for any given position and no additional absorption takes place after the X-ray reaches the sample surface.

  • The sample is infinitely thick as seen by the electron beam.

    • -The energy of the incident electron is deposited in the sample and only secondary and backscatter electrons escape.

Composition Standardless

For standard-less analysis the k-ratio is either calculated in the software or based on internal standards.

Following

D.E. Newbury, C.R. Swyt, and R.L. Myklebust, “Standardless” Quantitative Electron Probe Microanalysis with Energy-Dispersive X-ray Spectrometry: Is It Worth the Risk?, Anal. Chem. 1995, 67, 1866-1871

to calculate the mass concentration CiC_i from the intensity of a line (IchI_{ch}), we use:

Ich=ϵ(ωNAρCi/A)REcE0QidE/dsdEI_{ch} = \epsilon (\omega N_A \rho C_i / A) R \int _{E_c}^{E_0} \frac{Q_i}{dE/ds}dE
  • ω\omega : fluorescence yield

  • NAN_A: Avogadro’s number

  • ρ\rho: density

  • CiC_i: mass concentration of element ii

  • AA:atomic weight

  • RR: backscatter loss

  • QiQ_i: ionization cross section

  • dE/dsdE/ds: rate of energy loss

  • E0E_0: incident beam energy

  • EcE_c: excitation energy

  • ϵ\epsilon: EDS efficiency

where: NAρCi/AN_A * \rho * C_i/A: volume density of element ii (atoms per unit volume)

What do we know at this point?

ω;NA;ρ;A;Qi;E0;Ec;\omega; N_A; \rho; A; Q_i; E_0; E_c; and ϵ\epsilon

def BrowningEmpiricalCrossSection(elm , energy):
    """ * Computes the elastic scattering cross section for electrons of energy between
     * 0.1 and 30 keV for the specified element target. The algorithm comes from<br>
     * Browning R, Li TZ, Chui B, Ye J, Pease FW, Czyzewski Z &amp; Joy D; J Appl
     * Phys 76 (4) 15-Aug-1994 2016-2022

     * The implementation is designed to be similar to the implementation found in
     * MONSEL.
     * Copyright: Pursuant to title 17 Section 105 of the United States Code this
     * software is not subject to copyright protection and is in the public domain
     * Company: National Institute of Standards and Technology
     * @author Nicholas W. M. Ritchie
     * @version 1.0

     */
     Modified by Gerd Duscher, UTK
    """

    #/**
    #* totalCrossSection - Computes the total cross section for an electron of
    #* the specified energy.
    #* 
    # @param energy double - In keV
    # @return double - in square meters
    #*/
    e = energy  #in keV
    re = np.sqrt(e);
    return (3.0e-22 * elm**1.7) / (e + (0.005 * elm**1.7 * re) + ((0.0007 * elm**2) / re));
   

Summary

  • EDS quantification is not magic and the results will highly depend on sample preparation and correct peak identification.

  • Correct quantification with ZAF relies on the sample being:

    • Flat

    • Homogenous

    • Infinitely thick to the electron beam

  • Use of standards is not required for good quantification but it will give additional information and increase quality of the results.

  • Peak-to-background based ZAF is good for rough samples but requires better spectra than regular ZAF.

  • The best way to good EDS results:

    • Good samples

    • Thorough sample preparation

    • Exact SEM Parameters

Back: Detector Response

List of Content: Front

References
  1. Scanning Transmission Electron Microscopy. (2011). Springer New York. 10.1007/978-1-4419-7200-2
  2. MacArthur, K. E., Slater, T. J. A., Haigh, S. J., Ozkaya, D., Nellist, P. D., & Lozano-Perez, S. (2016). Quantitative Energy-Dispersive X-Ray Analysis of Catalyst Nanoparticles Using a Partial Cross Section Approach. Microscopy and Microanalysis, 22(1), 71–81. 10.1017/s1431927615015494
  3. MacArthur, K. E., Brown, H. G., Findlay, S. D., & Allen, L. J. (2017). Probing the effect of electron channelling on atomic resolution energy dispersive X-ray quantification. Ultramicroscopy, 182, 264–275. 10.1016/j.ultramic.2017.07.020
  4. Varambhia, A., Jones, L., London, A., Ozkaya, D., Nellist, P. D., & Lozano-Perez, S. (2018). Determining EDS and EELS partial cross-sections from multiple calibration standards to accurately quantify bi-metallic nanoparticles using STEM. Micron, 113, 69–82. 10.1016/j.micron.2018.06.015
  5. Joy, D. C. (2001). Fundamental Constants for Quantitative X-ray Microanalysis. Microscopy and Microanalysis, 7(2), 159–167. 10.1007/s100050010070
  6. Newbury, D. E., Swyt, C. R., & Myklebust, R. L. (1995). “Standardless” Quantitative Electron Probe Microanalysis with Energy-Dispersive X-ray Spectrometry: Is It Worth the Risk? Analytical Chemistry, 67(11), 1866–1871. 10.1021/ac00107a017