Chapter 4: Spectroscopy
4.5. Chemical Composition in Core-Loss Spectra#
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 | |
Background and methods to analysis and quantification of data acquired with transmission electron microscopes.
4.5.1. Content#
Quantitative determination of composition in a core-loss EELS spectrum
Please cite:
as a reference of the here introduced model-based quantification method.
4.5.2. Load important packages#
4.5.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.2.3':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
print('done')
installing pyTEMlib
done
4.5.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 matplotlib.pylab as plt
import numpy as np
## import the configuration files of pyTEMlib (we need access to the data folder)
import pyTEMlib
from pyTEMlib import file_tools
import pyTEMlib.eels_tools as eels
import pyTEMlib.eels_dialog_utilities as ieels
import advanced_eels_tools as aeels
from matplotlib.widgets import Cursor
from matplotlib.patches import Rectangle
from matplotlib.widgets import SpanSelector
from scipy.ndimage.filters import gaussian_filter
# For archiving reasons it is a good idea to print the version numbers out at this point
print('pyTEM version: ',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.
Symmetry functions of spglib enabled
pyTEM version: 0.2024.02.2
C:\Users\gduscher\AppData\Local\Temp\ipykernel_54220\926997833.py:24: DeprecationWarning: Please import `gaussian_filter` from the `scipy.ndimage` namespace; the `scipy.ndimage.filters` namespace is deprecated and will be removed in SciPy 2.0.0.
from scipy.ndimage.filters import gaussian_filter
4.5.3. Chemical Composition#
We discuss first the conventional method of EELS chemical compoisition determination
In this chapter we use the area under the ionization edge to determine the chemical composition of a (small) sample volume.
The equation used to determine the number of atoms per unit volume
The equation can be approximated assuming that the spectrum has not been corrected for single scattering:
\begin{equation}
I_{edge}(\beta, \Delta E) = N * I_{low-loss}(\beta,\Delta E) * \sigma_{edge}(\beta, \Delta E)
\end{equation}
where
The integration interval

Valence–loss, core–loss edges and background of SrTiO
If we cannot determine the intensity of the zero-loss peak
\begin{equation} \frac{N_a}{N_b} = \frac{I_{e_a}(\beta, \Delta E)}{I_0 \sigma_{e_a}(\beta, \Delta E)} \frac{I_0 \sigma_{e_b}(\beta, \Delta E) } {I_{e_b}(\beta, \Delta E)} \nonumber \end{equation}
\begin{equation} \frac{N_a}{N_b}= \frac{I_{e_a}(\beta, \Delta E)\sigma_{e_b}(\beta, \Delta E) } {I_{e_b}(\beta, \Delta E)\sigma_{e_a}(\beta, \Delta E) } \end{equation}
and the value
The integration windows for the two edges
\begin{equation}
\frac{N_a}{N_b} = \frac{I_{e_a}(\beta, \Delta E_a)\sigma_{e_b}(\beta, \Delta E_b) }
{I_{e_b}(\beta, \Delta E_a)\sigma_{e_a}(\beta, \Delta E_a) }
\end{equation}
Note, that the use of different integration windows usually results in a larger error of the quantification.
In order to use the above equation we first have to determine the background under the edge.
This background is then subtracted from the spectrum.
Then we integrate over the different parts of the spectrum to determine the integrals (sums) of
4.5.3.1. Background Fitting#
The core-loss edges occur usually at energy-losses higher than 100 eV, superimposed to a monotonic decreasing background. For quantification of the chemical composition we need the area under the edge of a spectrum, and therefore, need to subtract the intensity of the background under the edge. Here we discuss several methods of how to determine the intensity of the background under the edge.
The high energy tail
of the plasmon peak follows the power law
Consequently, we have to determine the parameters
The fitting of the power law
A standard technique is to match the pre–edge background
The value
In the figure below, we see the result and the output of the background fit (plus subtraction).

Background fit on a spectrum of SrTiO
]{\label{fig:background} Background fit on a spectrum of SrTiO . The and parameter together with the normalized (reduced) parameter is displayed in the {\bf Results} window.*
4.5.3.2. Background Fitting: Spatial Difference#
We can use also an experimentally determined background, if impurity or dopant atoms are present in confined areas only. Then, we can take two spectra: one next to this area and one at this area. The near by area will result in a spectrum without this edge and can be used as a background for quantification of the other spectrum. This method is highly accurate if the sample thickness does not change between these areas. The measurement of ppm levels of dopants and impurity atoms can achieved with this method. This Method will be more closely examined in Section Spatial-Difference
4.5.3.3. Background Subtraction Errors#
In addition to possible systematic errors, any fitted background to noisy data will be subject to to a random or statistical error. The signal noise ration (SNR) can be defined as:
\begin{equation} SNR = I_{edge} [var(I_{edge})]^{-1/2} = I_{edge}/(I_{edge}+ h \quad I_{background})^{1/2} \end{equation}
where the dimensionless parameter
4.5.3.4. Cross-Section#
The cross–section gives us the weight of the edge intensity to compare to different elements or to the total number of electrons (to compute areal density). Such a cross–sections is compared to an edge intensity in the figure below.

The shape of a calculated cross-section (black) is compared to the intensity of a Si-L
There are different methods of how to use cross-sections in the quantification of the chemical composition from spectra:
Egerton gives in his book a tabulated list of generalized oscillator strength (GOS) for different elements. The values for different integration windows
4.5.3.5. Calculation of the Cross–Section#
There are two methods in the literature to calculate the cross–section. One is the one where we assume s states in free atoms and is called Hydrogenic approximation and one which approximates the free atoms a little more detailed: the Hatree-Slater method.
Both methods are based on free atom calculations, because of the strong bonding of the core–electrons to the nucleus, band-structure (collective) effects can be neglected.
The figure below compares the cross–sections of these two approximations (with a background added) to an experimental spectrum.

The shape of a Hydrogenic (green) and Hatree–Slater (blue) cross-section (with a background added) is compared to an experimental (SSD-corrected) spectrum of Si.
4.5.3.6. Summary#
The power law is not really correct for any larger energy interval, which results in a change of the
A polynomial of 2
The exact slope of the extrapolated background depends on pre-edge features and noise
Generally the above described classic method of quantification is often non-reproducible, and results in errors often in excess of 100%.
In the following we will work with a model based method that reduces the artefacts, increases reproducibility and improves the error to about 3 atom % absolute.
4.5.4. Load and plot a spectrum#
As an example we load the spectrum 1EELS Acquire (high-loss).dm3 from the example data folder.
# ---- Input ------
load_example = True
# -----------------
if not load_example:
if 'google.colab' in sys.modules:
drive.mount("/content/drive")
fileWidget = file_tools.FileWidget()
# ---- Input ------
load_example = True
file_name = '1EELS Acquire (high-loss).dm3'
# -----------------
if load_example:
if 'google.colab' in sys.modules:
if not os.path.exists('./'+file_name):
!wget https://github.com/gduscher/MSE672-Introduction-to-TEM/raw/main/example_data/1EELS Acquire (high-loss).dm3
else:
datasets = file_tools.open_file('../example_data/'+file_name)
main_dataset = datasets['Channel_000']
else:
datasets = fileWidget.datasets
main_dataset = fileWidget.selected_dataset
if main_dataset.data_type.name != 'SPECTRUM':
print('NOT what we want here')
else:
main_dataset.view_metadata()
v = main_dataset.plot()
experiment :
single_exposure_time : 3.0
exposure_time : 63.0
number_of_frames : 21
collection_angle : 33.0
convergence_angle : 30.0
acceleration_voltage : 200000.0
4.5.4.1. Which elements are present#
To determine which elements are present we add a cursor to the above plot (see Working with Cross-Sections for details) and with a left (right) mouse-click, we will get the major (all) edges in the vincinity of the cursor.
In the example we note that the N-K edge of this boron nitride sample is not at 400keV. We have to adjust the energy-scale.
(THIS SHOULD NOT HAPPEN IN NORMAL SPECTRA AND IS FOR DEMONSTRATION ONLY)
maximal_chemical_shift = 5
energy_scale = main_dataset.energy_loss
cursor = ieels.EdgesAtCursor(main_dataset.view.axis, energy_scale, main_dataset, maximal_chemical_shift)
aeels.find_edge_names(400)
['N -K1']
Let’s correct the energy scale of the example spectrum.
Again a shift of the enrrgy scale is normal but not a discripancy of the dispersion.
4.5.4.2. Probability scale of y-axis#
We need to know the total amount of electrons involved in the EELS spectrum
There are three possibilities:
the intensity of the low loss will give us the counts per acquisition time
the intensity of the beam in an image
a direct measurement of the incident beam current
Here we got the low-loss spectrum. For the example please load 1EELS Acquire (low-loss).dm3 from the example data folder.
# ---- Input ------
load_example = True
# -----------------
if not load_example:
if 'google.colab' in sys.modules:
drive.mount("/content/drive")
ll_fileWidget = file_tools.FileWidget()
# ---- Input ------
load_example = True
ll_file_name = '1EELS Acquire (low-loss).dm3'
# -----------------
if load_example:
if 'google.colab' in sys.modules:
if not os.path.exists('./'+file_name):
!wget https://github.com/gduscher/MSE672-Introduction-to-TEM/raw/main/example_data/1EELS Acquire (low-loss).dm3
else:
ll_datasets = file_tools.open_file('../example_data/'+ll_file_name)
ll_dataset = ll_datasets['Channel_000']
else:
ll_datasets = ll_fileWidget.datasets
ll_dataset = ll_fileWidget.selected_dataset
ll_dataset.view_metadata()
v = ll_dataset.plot()
experiment :
single_exposure_time : 0.01
exposure_time : 63.0
number_of_frames : 21
collection_angle : 33.0
convergence_angle : 30.0
acceleration_voltage : 200000.0
4.5.4.3. Intensity to Probability Calibration#
We need to calibrate the number of counts with the integration time of the spectrum.
ll_dataset.metadata['experiment']['exposure_time'] = ll_dataset.metadata['experiment']['number_of_frames'] *ll_dataset.metadata['experiment']['single_exposure_time']
I0 = ll_dataset.sum()/ll_dataset.metadata['experiment']['exposure_time']*main_dataset.metadata['experiment']['exposure_time']
print(f"incident beam current of core--loss spectrum is {I0:.0f} counts in {ll_dataset.metadata['experiment']['exposure_time']:.2f} sec")
main_dataset.metadata['experiment']['intentsity_scale_ppm'] = 1/I0*1e6
main_dataset.metadata['experiment']['incident_beam_current_counts'] = I0
dispersion = main_dataset.energy_loss[1] - main_dataset.energy_loss[0]
spectrum = main_dataset *main_dataset.metadata['experiment']['intentsity_scale_ppm'] * dispersion
spectrum.quantity = 'inelastic scattering probability'
spectrum. units = 'ppm'
view =spectrum.plot()
incident beam current of core--loss spectrum is 34710045600 counts in 0.21 sec
channels = np.searchsorted(spectrum.energy_loss, [188, 388])
dE = (401-188)/ (channels[1]-channels[0])
dE , spectrum.energy_loss[1]- spectrum.energy_loss[0]
(0.26625, 0.25)
spectrum.energy_loss *= dE/(spectrum.energy_loss[1]- spectrum.energy_loss[0])
spectrum.energy_loss[channels[0]]
200.22
spectrum.energy_loss -= spectrum.energy_loss[channels[0]]-188
spectrum.title = main_dataset.title
v = spectrum.plot()
4.5.5. Components of a core loss spectrum#
-background
-absorption edges
4.5.5.1. Plotting of cross sections and spectrum#
please note that spectrum and cross sections are not on the same scale
energy_scale = spectrum.energy_loss
B_Xsection = eels.xsec_xrpa(energy_scale, 200, 5, 10. )/1e10
N_Xsection = eels.xsec_xrpa(energy_scale, 200, 7, 10. ,shift=-6)/1e10 # xsec is in barns = 10^28 m2 = 10^10 nm2
fig, ax1 = plt.subplots()
ax1.plot(energy_scale, B_Xsection, label='B X-section' )
ax1.plot(energy_scale, N_Xsection, label='N X-section' )
ax1.set_xlabel('energy_loss [eV]')
ax1.set_ylabel('probability [atoms/nm$^{2}$]')
plt.legend();
ax2 = ax1.twinx()
ax2.plot(energy_scale, spectrum, c='r', label='spectrum')
ax2.tick_params('y', colors='r')
ax2.set_ylabel('probability [ppm]')
#plt.xlim(100,500)
#plt.legend();
fig.tight_layout();
4.5.5.2. Background#
The other ingredient in a core loss spectrum is the background
The backgrund consists of
ionization edges to the left of the beginning of the spectrum (offset)
tail of the plasmon peak (generally a power_law with
)
Here we approximate the background in an energy window before the first ionization edge in the spectrum as a power law with exponent
from scipy.optimize import leastsq ## leastsqure fitting routine fo scipy
# Determine energy window in pixels
bgdStart = 130
bgdWidth = 40
energy_scale = spectrum.energy_loss
offset = energy_scale[0]
dispersion = energy_scale[1]-energy_scale[0]
startx = int((bgdStart-offset)/dispersion)
endx = startx + int(bgdWidth/dispersion)
x = np.array(energy_scale[startx:endx])
y = np.array(spectrum[startx:endx])
# Initial values of parameters
p0 = np.array([1.0E+20,3])
## background fitting
def bgdfit(p, y, x):
err = y - (p[0]* np.power(x,(-p[1])))
return err
p, lsq = leastsq(bgdfit, p0, args=(y, x), maxfev=2000)
print(f'Power-law background with amplitude A: {p[0]:.1f} and exponent -r: {p[1]:.2f}')
#Calculate background over the whole energy scale
background = p[0]* np.power(energy_scale,(-p[1]))
plt.figure()
plt.plot(spectrum.energy_loss, spectrum, label='spectrum')
plt.plot(spectrum.energy_loss, background, label='background')
plt.xlabel('energy-loss (eV)')
plt.ylabel('scattering probability (ppm)')
plt.title('Power-Law Background')
plt.legend();
Power-law background with amplitude A: 1569531.4 and exponent -r: 3.10
4.5.6. Fitting a Spectrum#
We are revisiting the above the fundamental equation of the chemical composition:
We already calibrated the cross section in per
\begin{equation} I_{edge}(\beta, \Delta E) = N I_{0}(\beta) \sigma_{edge}(\beta, \Delta E) \end{equation}
and as above we calibrate the intensity of the spectrum by
\begin{equation} \frac{I_{edge}(\beta, \Delta E)}{I_0} = I^{norm}{edge} = N \sigma{edge}(\beta, \Delta E) \end{equation}
and if we fit the calibrated intensity with the cross section then we can replace
and N is in atoms per nm
So a fit to a callibrated spectrum as above, will get us a fitting parameter
which is an areal density
(which is a legitimate thermodynamic quantity).
And for the relative composition we get:
$
In the following we will do this kind of a fit by:
calibrate the intensity in the spectrum (in ppm)
using cross section in units of nm
Please note that for the relative composition , the
will fall out and so a fit to a spectrum without calibrated intensity will still give the relative intensity accurately.
4.5.6.1. Preparing the fitting mask#
Our theoretical cross sections do not include any solid state effects (band structure) and so the fine structure at the onset of the spectra must be omitted in a quantification.
These parts of the spectrum will be simply set to zero. We plot the masked spectrum that will be evaluated.
energy_scale = spectrum.energy_loss
dispersion = (energy_scale[1] - energy_scale[0])
offset = energy_scale[0]
startx = int((bgdStart-offset)/dispersion)
mask = np.ones(len(energy_scale))
mask[0 : int(startx)] = 0.0;
edges = {}
edges['1'] = {}
edges['1']['Z']=5
edges['1']['symmetry']= 'K1'
edges['2'] = {}
edges['2']['Z']=7
edges['2']['symmetry']= 'K1'
for key in edges:
print((eels.get_x_sections(edges[key]['Z']))[edges[key]['symmetry']])
edges[key]['onset'] = (eels.get_x_sections(edges[key]['Z']))[edges[key]['symmetry']]['onset']
edges[key]['start_exclude'] = edges[key]['onset']-5
edges[key]['end_exclude'] = edges[key]['onset']+50
edges[key]['data'] = eels.get_x_sections(edges[key]['Z'])
mask[np.searchsorted(energy_scale, edges[key]['start_exclude']):np.searchsorted(energy_scale, edges[key]['end_exclude'])] = 0.0
plt.figure()
plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, spectrum*mask, label='spectrum')
plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]');
{'filename': 'B.K1', 'excl before': 5, 'excl after': 50, 'onset': 188.0, 'factor': 1.0, 'shape': 'hydrogenic'}
{'filename': 'N.K1', 'excl before': 5, 'excl after': 50, 'onset': 401.6, 'factor': 1.0, 'shape': 'hydrogenic'}
4.5.6.2. The Fit#
The function model just sums the weighted cross-sections and the background.
The background consists of the power-lawbackground before plus a polynomial component allowing for a variation of the exponent
The least square fit is weighted by the noise according to Poison statistic
Please note that the cross sections are for single atoms only and do not cover any solid state effects vsible as strong peaks in the first 50 eV or so past the onset.
We exclude those parts from the fits.
plt.figure()
plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, spectrum*mask, label='spectrum')
plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]');
regions = ieels.RegionSelector(plt.gca())
for key in edges:
print(key)
regions.set_regions(str(key),edges[key]['onset']-5, 50.)
regions.set_regions('fit region',bgdStart, energy_scale[-1]-bgdStart)
1
2
region_tags = regions.get_regions()
mask = np.ones(main_dataset.shape)
#startx = np.searchsorted(tags['energy_scale'],region_tags['fit_area']['start_x'])
mask[0:startx] = 0.0
for key in region_tags:
end = region_tags[key]['start_x']+region_tags[key]['width_x']
startx = np.searchsorted(energy_scale,region_tags[key]['start_x'])
endx = np.searchsorted(energy_scale,end)
if key == 'fit_area':
mask[0:startx] = 0.0
mask[endx:-1] = 0.0
else:
mask[startx:endx] = 0.0
pin = np.array([1.,1.,.0,0.0,0.0,0.0, 1.0,1.0,0.001,5,3])
x = energy_scale
blurred = gaussian_filter(spectrum, sigma=5)
y = blurred*1e-6/dispersion ## now in probability
y[np.where(y<1e-8)]=1e-8
B_Xsection = eels.xsec_xrpa(energy_scale, 200, 5, 10. )/1e10
N_Xsection = eels.xsec_xrpa(energy_scale, 200, 7, 10. )/1e10 # xsec is in barns = 10^-28 m2 = 10^-10 nm2
xsec = np.array([B_Xsection, N_Xsection])
numberOfEdges = 2
def residuals(p, x, y ):
err = (y-model(x,p))*mask/np.sqrt(np.abs(y))
return err
def model(x, p):
y = (p[9]* np.power(x,(-p[10]))) +p[7]*x+p[8]*x*x
for i in range(numberOfEdges):
y = y + p[i] * xsec[i,:]
return y
p, cov = leastsq(residuals, pin, args = (x,y) )
print(f"B/N ratio is {p[0]/p[1]:.3f}")
#the B atom areal density of a single layer of h-BN (18.2 nm−2)
print(f" B areal density is {p[0]:.0f} atoms per square nm, which equates {abs(p[0])/18.2:.1f} atomic layers")
print(f" N areal density is {p[1]:.0f} atoms per square nm, which equates {abs(p[1])/18.2:.1f} atomic layers")
B/N ratio is 0.964
B areal density is 122 atoms per square nm, which equates 6.7 atomic layers
N areal density is 127 atoms per square nm, which equates 7.0 atomic layers
region_tags = regions.get_regions()
mask = np.ones(main_dataset.shape)
#startx = np.searchsorted(tags['energy_scale'],region_tags['fit_area']['start_x'])
mask[0:startx] = 0.0
for key in region_tags:
end = region_tags[key]['start_x']+region_tags[key]['width_x']
startx = np.searchsorted(energy_scale,region_tags[key]['start_x'])
endx = np.searchsorted(energy_scale,end)
if key == 'fit_area':
mask[0:startx] = 0.0
mask[endx:-1] = 0.0
else:
mask[startx:endx] = 0.0
pin = np.array([1e12,1e12,.0,0.0,0.0,0.0, 1.0,1.0,0.001,5,3])
x = energy_scale
blurred = gaussian_filter(spectrum, sigma=5)
y = blurred * (I0*1e-6)/dispersion ## now in counts
y[np.where(y<1e-8)]=1e-8
B_Xsection = eels.xsec_xrpa(energy_scale, 60, 5, 40. )/1e10
N_Xsection = eels.xsec_xrpa(energy_scale, 60, 7, 40. )/1e10 # xsec is in barns = 10^-28 m2 = 10^-10 nm2
xsec = np.array([B_Xsection, N_Xsection])
numberOfEdges = 2
def residuals(p, x, y ):
err = (y-model(x,p))*mask/np.sqrt(np.abs(y))
return err
def model(x, p):
y = (p[9]* np.power(x,(-p[10]))) +p[7]*x+p[8]*x*x
for i in range(numberOfEdges):
y = y + p[i] * xsec[i,:]
return y
p, cov = leastsq(residuals, pin, args = (x,y) )
print(f'The multipliers of X-sections are: {p[0]:.2E}, {p[1]:.2E}')
print(f"B/N ratio is {p[0]/p[1]:.3f}")
#the B atom areal density of a single layer of h-BN (18.2 nm−2)
print(f" B areal density is {p[0]/I0:.0f} atoms per square nm, which equates {abs(p[0]/I0)/18.2:.1f} atomic layers")
print(f" N areal density is {p[1]/I0:.0f} atoms per square nm, which equates {abs(p[1]/I0)/18.2:.1f} atomic layers")
C:\Users\gduscher\AppData\Local\Temp\ipykernel_54220\561077839.py:41: RuntimeWarning: overflow encountered in power
y = (p[9]* np.power(x,(-p[10]))) +p[7]*x+p[8]*x*x
C:\Users\gduscher\AppData\Local\Temp\ipykernel_54220\561077839.py:37: RuntimeWarning: invalid value encountered in multiply
err = (y-model(x,p))*mask/np.sqrt(np.abs(y))
The multipliers of X-sections are: 1.63E+12, 1.68E+12
B/N ratio is 0.972
B areal density is 47 atoms per square nm, which equates 2.6 atomic layers
N areal density is 48 atoms per square nm, which equates 2.7 atomic layers
C:\Users\gduscher\AppData\Local\Temp\ipykernel_54220\561077839.py:46: RuntimeWarning: Number of calls to function has reached maxfev = 2400.
p, cov = leastsq(residuals, pin, args = (x,y) )
4.5.6.3. Plotting of the fit#
model_spectrum = model(x, p) # in ppm
model_background = ((p[9]* np.power(x,-p[10])) +p[7]*x+p[8]*x*x) # in ppm
plt.figure()
#plt.plot(energy_scale, spectrum* (I0*1e-6)/0.25, label='spectrum')
plt.plot(energy_scale, blurred* (I0*1e-6)/0.25, label='blurred spectrum')
plt.plot(x,model_spectrum, label='model')
plt.plot(x,main_dataset-model_spectrum, label='difference')
plt.plot(x,model_background, label='background')
plt.plot([x[0],x[-1]],[0,0],c='black')
plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]')
plt.legend();
4.5.7. Summary#
We use a cross section in unsits of nm
The fitting parameter is then the areal density of the element.
We only fit the part of the spectrum we know which is the single atom part of the edge, and avoid to fit any solid state effects at the onset of the edge.
The interpreation of solid state effects at the onset are discussed in the energy-loss near-edge structure (ELNES) notebook.