XPS Analysis#
X-ray Photoelectron Spectroscopy (XPS) analysis requires calibration
parameters pixel_per_ev and peak_shift. The calibration analyzer needs to
run first to obtain the calibration parameters. The base class SpectraBase
provides the basic stitching profile analysis.
XPSCalibration derives pixel_per_ev and
peak_shift from a stack of readers with "Start Voltage" and
"Beam Energy" metadata. The calibration analyze() method uses XPS
fitting methods that require baseline intensities, total number of peaks,
reference peak index and energy value.
XPSAnalyzer converts the pixel axis to
binding energy, fits profiles with Shirley background subtraction and
pseudo-Voigt peaks, plots fit results, and can stitch profiles through the
shared spectra base class. Automatic peak fitting detects peaks on the
background-subtracted signal. Manual fitting can be done by providing
a dictionary of peak constraints. The constraints follow the lmfit format.
The LEEM spectra analyzer has limited energy range; large energy range spectra need to be stitched together. The stitching method takes a list of indices and determines the overlapped regions, and outputs a combined profile.
The analyzer uses the SpectraBase base class for converting the pixel axis to binding energy and adding stitching logics.
Example#
from pyleem.analysis.xps import XPSAnalyzer, XPSCalibration
from pyleem.reader import UViewReader, read_files
from pyleem.roi import LineROI
readers = read_files(
["xps_0.dat", "xps_1.dat", "xps_2.dat"],
reader_cls=UViewReader,
metadata_list=[
{"Beam Energy": (400, "eV")},
{"Beam Energy": (400, "eV")},
{"Beam Energy": (400, "eV")},
],
)
roi = LineROI(src=(0, 0), dst=(0, 127), linewidth=1)
calibration = XPSCalibration(readers, roi=roi)
cal_result = calibration.analyze(
baselines=[(197, 100)] * len(readers),
num_peaks=1,
ref_index=0,
ref_value=285.0,
)
pixel_per_ev = cal_result["pixel_per_ev"]
peak_shift = cal_result["peak_shift"]
analyzer = XPSAnalyzer(
readers,
roi=roi,
pixel_per_ev=pixel_per_ev,
peak_shift=peak_shift,
)
binding_energy = analyzer.get_binding_energy(0)
fit_result, background = analyzer.fit(0, num_peaks=1, baseline=(200, 100))
stitched_energy, stitched_profile = analyzer.stitch_profiles([0, 1, 2])
ax = analyzer.plot_profile(
0,
show_fit=True,
num_peaks=1,
baseline=(200, 100),
)
print(fit_result.best_values)
Peak Fitting#
The peak fitting takes the profile and abscissa as input. The fit result is a dictionary of the fit, background and other fitting parameters.
Manual Peak Fitting#
For analyzer, the manual peak fitting can be done by passing a dictionary
of peak constraints. The constraints follows the lmfit format.
peak_constraints = {
"peak1": {
"center": {"value": 531.0, "min": 530.5, "max": 531.5},
"sigma": {"value": 0.4, "min": 0.05, "max": 1.2},
"peak2": ...
}
The fit_xps function can be used to fit spectrum directly without the analyzer.
The function is useful if more detailed control needed. For example, if we
want to fit a spectrum with a specific range, we can use the fit_range argument.
from pyleem.analysis.xps import fit_xps
peak_constraints = {
"peak1": {
"center": {"value": 531.0, "min": 530.5, "max": 531.5},
"sigma": {"value": 0.4, "min": 0.05, "max": 1.2},
"amplitude": {"value": 800, "min": 0},
"fraction": {"value": 0.5, "min": 0, "max": 1},
},
"peak2": {
"center": {"value": 529.2, "min": 528.6, "max": 529.8},
"sigma": {"value": 0.5, "min": 0.05, "max": 1.2},
"amplitude": {"value": 200, "min": 0},
"fraction": {"value": 0.5, "min": 0, "max": 1},
},
}
fit_result = fit_xps(
stitched_profile,
stitched_energy,
baseline=(120, 80), # optional
peak_constraints=peak_constraints, # optional
fit_range=(528.1, 532.8), # optional
)
Automatic Peak Fitting#
For automatic fitting, pass num_peaks instead of peak_constraints. Use
peak_prominence and smooth_sigma to make peak detection less
sensitive to noise or single-point stitch spikes.
fit_result = fit_xps(
stitched_profile,
stitched_energy,
num_peaks=2,
peak_prominence=0.1,
smooth_sigma=2,
)
Stitching Profiles#
The stitch_method argument can be "midpoint", "start", or "end".
# Use the calibrated analyzer from the example above.
stitched_energy, stitched_profile = analyzer.stitch_profiles(
indices=[0, 1, 2],
stitch_method="midpoint",
)
ax.plot(stitched_energy, stitched_profile)
ax.set_xlabel("Binding Energy [eV]")
ax.set_ylabel("Intensity")