How to analyse ISS XPS and NEXAFS data using Matlab

Contents

Introduction: Concept

NEXAFS and XPS data from beam times is imported into a MATLAB table. The table allows to add meta data such as sample temperature, cell pressure, or Scienta settings (pass energy, etc). Further, data from several beam times can be imported and then filtered - for example to process all gas-phase CO2 PE from the last 5 years; or all ice NEXAFS at -10° to -12°C. Functions and the table are organised such that one can always go back to raw data to re-do the analysis or continue with processed data.

The basic idea is that a set of MATLAB functions that import the data of different format and from various sources and that can be used to perform basic XPS and NEXAFS data processing are used, shared, and developed by all group members. This requires common data formats and naming. The scripts/live scripts/functions that are used to call the common MATLAB functions are written, stored, named individually by each user.

Introduction: Function calls

Most functions need the MATLAB data table as input. The output can be stored in the same data table adding data to the original data table, or in a new one - thus creating a copy. Some functions such as those used for plotting do not write data.

InputTable = nappFunctionAbc(InputTable);

This command runs the function nappFunctionAbc on the data table InputTable and writes the results into the same table (with the name InputTable.

The InputTable may hold different data types such as XPS data, NEXAFS, and pictures. Mostly, the functions require one and the same data type as input. The InputTable needs to be filtered when passed to a function (see below). Exceptions from this are the nappGenerateSpectra and nappPlot functions that can be called with some limitations with mixed data as input (including pictures, that are ignored).

OutputTable = nappFunctionAbc(InputTable);

This command runs the same function on the data in the table InputTable and writes the results into the data table _OutputTable which is a full copy of InputTable plus the results of the function nappFunctionAbc.

Most functions allow to set a number of optional input parameters, some require additional mandatory input. Please check the documentation of each function for further help:

help nappFunctionAbc

The mandatory input comes first in each function call. In the following example the data of the InputTable is passed to the function as well as the variables x and y. The optional function input is given just like in any MATLAB function using parameter descriptions: 'option',setting. Setting can be a text string in quotes, or a variable (array, logical, table, date number, etc). Most common are:

OutputTable = nappFunctionAbc(InputTable,x,y,'tableSelection',tmpDataSelection,'...
'tableVariable','TableVariable', energyScale','kinetic','mode','mean');

Please consult the help files of each function for a complete list of options.

Introduction: Data structure

It starts with importing the multi-dimensional IgorPro files that the SES software saves or with the h5 file structure that is saved for the NEXAFS data.

The data are then saved in a data table, each file in one row and the data in columns. Columns are called table variables in MATLAB and in the nappFunctions. The data columns are:

In other words, the data in the columns spectrum_countsPerSecond, spectrum_counts, image always hold the processed data and are modified by each function call. The Roh data allows to go back and re-do the processing. Next to this the data table stores analyser settings such as scientaEkin and sample parameters such as temperature and pressure. View the data table in the data viewer or command window of MATLAB to see all columns. This data structure allows to process all data and then still plot images, unprocessed data, or processed data of selected experiments.

ToDo

Prepare: Initialise MATLAB

You need to have access to the folder holding the functions and helper files. These are shared via switch drive.

Add the paths to the initializeMatlab function at the end of this script This function needs to be added to the end of each of your scripts or live scripts. Below is an example.

function [myColorMap, PsiFarbe] = initializeMatlab
%initializeMatlab Sets NappIce specific settings
%   Adds Napp files to path and defines file locations for mac, windows,
%   and Matlab online (which runs on unix)
% change working directory
path = matlab.desktop.editor.getActiveFilename;
[filepath,~,~] = fileparts(path);
cd(filepath);
% The fitting routines writes quite a lot of feedback and warnings. I shut them off.
warning('off','all')
warning off backtrace
warning off verbose
% Functions and helper files path:
if ismac
   % add Napp files to path
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/Add-Ons');
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/nappIce');
elseif ispc
   % add Napp files to path
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/Add-Ons');
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/nappIce');
elseif isunix
   % add Napp files to path
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/Add-Ons');
   addpath('/Users/bartels-rausch/SwitchDrive/PSI_Matlab/nappIce');
end
% Color maps
myColorMap.red     = colormap(autumn(7));
myColorMap.blue    = colormap(winter(7));
% define PSI cooperate identity colours
PsiFarbe.hellgrau       = [226 226 226]/255;
PsiFarbe.mittelgrau     = [184 184 184]/255;
PsiFarbe.dunkelgrau     = [78 78 77]/255;
PsiFarbe.blaugrau       = [236 236 243]/255; % This is actually not a PSI colour
PsiFarbe.gelb           = [239 200 37]/255;
PsiFarbe.hellorange     = [227 164 31]/255;
PsiFarbe.dunkelorange   = [207 92 22]/255;
PsiFarbe.hellrot            = [164 36 23]/255;
PsiFarbe.dunkelbraun    = [114 85 65]/255;
PsiFarbe.hellbraun      = [127 119 47]/255;
PsiFarbe.mittelgruen      = [135 150 59]/255;
PsiFarbe.hellgruen      = [195 214 47]/255;
PsiFarbe.dunkelgruen    = [72 130 33]/255;
PsiFarbe.dunkelrot        = [104 47 86]/255;
PsiFarbe.dunkelblau     = [37 65 108]/255;
PsiFarbe.mittelgruenblau = [61 164 168]/255;
PsiFarbe.hellblau         = [58 162 190]/255;
PsiFarbe.mittelblau       = [50 118 158]/255;
end

The path might be different on your computer depending how you have set up SwitchDrive:

addpath('C:\Users\bletry_c\switchdrive\PSI_Matlab\nappIce');
addpath('C:\Users\bletry_c\switchdrive\PSI_Matlab\Add-Ons');

then run the initialiseMatlab function:

clearvars
[myColorMap, PsiFarbe] = initializeMatlab;
close all
clc

Prepare: ISS data structure and path to local copy of ISS data

Data needs to be organised in beam time folders that can be located on the Napp drive or locally on your computer. The data folder naming and structure within the beam time folder (orange on figure) is strict and there must not be any subfolders in the data folders:

beam time folder location

The see all import options (few examples given below) check out the nappFileList.m help (this works for all nappIce functions):

help nappFileList.m

Helper functions

Import ISS data: Complete beam time folder

FileList = nappFileList('2022/2022_02__FebIssIceNapp');

This will create a new data table "FileList" overwriting any previous versions of this list with data imported from the local copy of the beam time folder with the name "2022_02__FebIssIceNapp" being a subfolder of the folder "2022".

data read from mat file

Import ISS data: Error messages

If you get a message like this in the command window

There appears to be no file in the SES folder. Add at least one file first.
I quit now

and MATLAB quits running the script, please check that there is at least a *.ibw file in the SES folder

If you instead get a message like this

There is something wrong with the path. Check for typo and that the drives are mounted
I quit now

and MATLAB quits running the script, please check

Import ISS data: Add data to existing data table

This will import data from the beam time folder "2021/2021_08__AugIssIceNapp" on the NAPP drive adding the data to the data table "FileList" in the MATLAB workspace

FileList = nappFileList('2021/2021_08__AugIssIceNapp',FileList,'mode','nappDrive');

Import ISS data: Add new SES or NEXAFS files from local beam time folder

Using the mode 'localNew' the content of the beam time folder will be compared to that of the FileList in the workspace and only those files not yet imported will be processed. This is a little faster and preserves any changes to the FileList. To prevent data failure of running SES measurements, this only works on the local copy of the beam time folder

FileList = nappFileList('2022/2022_02__FebIssIceNapp',FileList,'mode','localNew');

Import ISS data: Choose files manually

This will open a windows file explorer window where you can choose as many data files to be imported as you want. In this example, a new FileList is written, overwriting any previous versions in the workspace.

FileList = nappFileList('2022/2022_02__FebIssIceNapp','mode','nappDrivePick');

Work with data: Excel

The nappFileList.m function also writes a csv file to the beam time folder. Open it as xlsx and use Excels formatting and filtering tools to work with your data set. So, the xlsx file is edited manually and might contain additional information that you have added such as slit settings, comments, type of sample, scope of the measurement, etc. The csv file it automatically generated and might thus be frequently be over-written. Use copy/paste to update the xlsx file with new data, formatting will be automatically applied to new data then.

Work with data: Add photon energy to table (XPS)

ISS beamline does not log settings of SES data. Photon energy, which is needed to calculate the binding energy later, needs to be added manually. This will read the FileList table and add the photon energy "1000" to the SES data with file numbers 1,8 to 12, and 26 acquired in February (month "2") of the year "2022"). Note, this is only adding to XPS data, NEXAFS data are not touched:

FileList = addPhotonEnergy(FileList,[1,8:12,26],1000,2,2022);

Work with data: Add sample description

Sample description to label figures and for filtering data can be added. It does not make sense to use temperature or pressure, as these data are written to the table anyway. It does make sense to use standard phrases to enable filtering all beam time data. This code example follow the same input structure as the addPhotonEnergy function. This function works on both NEXAFS and XPS data. One may limit and select either one specifically by adding either 'NEXAFS' or 'XPS' strings as last input parameter. This is optional, if not specified function runs on both data types:

FileList = addSampleNotes(FileList,[1,8:11],'Sample holder',2,2022,'XPS');
FileList = addSampleNotes(FileList,[1,8:11],'Sample holder',2,2022,'NEXAFS');
FileList = addSampleNotes(FileList,[2:7,35:36,56:60],'Ice with N2',2,2022);
FileList = addSampleNotes(FileList,73:76,'H2O gas-phase',2,2022);
FileList = addSampleNotes(FileList,73,'H2O gas-phase  (bad data)',2,2022);

Work with data: Filter table

An advantage of the data table is filtering. This works with standard MATLAB functions using logical operations. Main motivation of filtering is to work with selected subset of data which for example can be processed with the same settings, fitted with the same constrains, or shall be plotted in one graph for comparison.

Further, the InputTable may hold different data types such as XPS data, NEXAFS, and pictures. Mostly, the functions require one and the same data type as input. Therefore, the InputTable needs to be filtered when passed to most functions. Exceptions from this are the nappGenerateSpectra and nappPlot functions that can be called with some limitations in the settings with mixed data as input (including pictures, that are ignored).

The first example filter CO2 gas-phase C1s PE data that is not "(bad data)" and has a pass energy of 50 eV. In the second example, we pick a C1s or O1s PE data from the SES file #77 from February 2022. Any table column can be used for filtering. "&" means AND "|" is OR.

tmpDataSelection1 = matches(FileList.sample,'CO2 gas-phase') ...
    & ~contains(FileList.sample,'(bad data)')...
    & contains(FileList.name,'C1s')...
    & FileList.scientaPassEnergy_eV == 50 ;
tmpDataSelection2 = (contains(FileList.name,'077_C1s') | contains(FileList.name,'077_O1s')) ...
        (month(FileList.lastModifiedDatenum) == 2) &...
        (year(FileList.lastModifiedDatenum) == 2022);

Work with data: Display filtered table and selected columns

MATLAB tables allow to address the columns by name. To display only the rows that match the filter criteria and only specific columns, code like the examples below can be used:

disp(FileList(tmpDataSelection1,{'name','sample','xRayEkin_eV','scientaPassEnergy_eV','lastModifiedDatestr','sampleTemp_dC',...
    'cellPressure_mbar' }))
disp(FileList(tmpDataSelection2,{'name','scientaLensMode','xRayEkinRange_eV','scientaPassEnergy_eV'}))
              name                    sample          xRayEkin_eV    scientaPassEnergy_eV      lastModifiedDatestr       sampleTemp_dC    cellPressure_mbar
    _________________________    _________________    ___________    ____________________    ________________________    _____________    _________________

    {'0077077_C1s_900eV.ibw'}    {'CO2 gas-phase'}      {[900]}               50             {'28-Feb-2022 20:52:12'}       19.966              1.3599     
    {'0080080_C1s_650eV.ibw'}    {'CO2 gas-phase'}      {[650]}               50             {'28-Feb-2022 20:59:57'}       19.993               1.432     
    {'0081081_C1s_650eV.ibw'}    {'CO2 gas-phase'}      {[650]}               50             {'28-Feb-2022 21:03:12'}       20.015              1.0105     
    {'0082082_C1s_900eV.ibw'}    {'CO2 gas-phase'}      {[900]}               50             {'28-Feb-2022 21:07:49'}       19.994             0.99001     

              name               scientaLensMode     xRayEkinRange_eV    scientaPassEnergy_eV
    _________________________    ________________    ________________    ____________________

    {'0077077_C1s_900eV.ibw'}    {'T_HiPPHAXPES'}       900    900                50         
    {'0077077_O1s_900eV.ibw'}    {'T_HiPPHAXPES'}       900    900                50         

Work with data: Modify data in table

There might be bad sweeps (sequence iterations) either with too low signal to noise or because some setting was changed during the run. These can be removed by directly addressing the data in the images. Same procedure, first the data (table row) is filtered, then the image modified for the selected data.

remove one sweep

tmpDataSelection = contains(FileList.name,'077_C1s')& ...
      (month(FileList.lastModifiedDatenum) == 2) &...
      (year(FileList.lastModifiedDatenum) == 2022);
FileList(tmpDataSelection,:).image{:}(:,3)=[];
tmpDataSelection = contains(FileList.name,'077_O1s')& ...
      (month(FileList.lastModifiedDatenum) == 2) &...
      (year(FileList.lastModifiedDatenum) == 2022);
FileList(tmpDataSelection,:).image{:}(:,3)=[];

remove several sweeps

tmpDataSelection = contains(FileList.name,'059_C1s')& ...
     (month(FileList.lastModifiedDatenum) == 10) &...
     (year(FileList.lastModifiedDatenum) == 2021);
FileList(tmpDataSelection,:).image{:}(:,5:15)=[];

remove all data (complete table rows) outside a specific time window

tmpTime.start              = datenum('30-Apr-2019 02:00:00');
tmpTime.end                = datenum('30-Apr-2019 05:00:00');
toDelete = Table_FileList.datenum <= tmpTime.start;
Table_FileList(toDelete,:) = [];
toDelete = Table_FileList.datenum >= tmpTime.end;
Table_FileList(toDelete,:) = [];

Correct beam line-offset in data

This will correct 1.6 from the X-ray energies, of particular importance for NEXAFS data. One can also do this on selected data.

beamlineOffset = 1.6;
FileList.xRayEkin_eV      = cellfun(@(x) x+beamlineOffset,FileList.xRayEkin_eV,'UniformOutput',false);

Add data (columns) to table

Calculation results for each row can be added to data tables using standard Matlab functions. This example illustrates the use of anonymous functions, their call for each row using cellfun, and the generation of additional columns to which (selection of) the data are written.

fun1        =@(c) interp1(IzeroAugInCell(:,1),IzeroAugInCell(:,2), c);
fun2        =@(c) interp1(IzeroAugInCell(:,1),IzeroAugInCell(:,3), c);
tmpMatch    = (month(ListNexafs.lastModifiedDatenum) == 8) &...
    (year(ListNexafs.lastModifiedDatenum) == 2020) |...
    (startsWith(ListNexafs.name, 'sum_Aug'));
ListNexafs.uhvI0(tmpMatch)      = cellfun(fun1, ListNexafs.xRayEkin_eV(tmpMatch)  ,'uni' , 0);
ListNexafs.uhvI0std(tmpMatch)   = cellfun(fun2, ListNexafs.xRayEkin_eV(tmpMatch)  ,'uni' , 0);
clearvars tmp* fun*

Plot data: XPS images

We use the nappPlot function to plot the images. For this, we specify the column of the data table that should be plotted: 'tableVarialbe','image' plots the image for all data in FileList that are selected by the logical array tmpDataSelection (See Filtering table above).

nappPlot(FileList,tmpDataSelection,'tableVariable','image');

Plot data: XPS and NEXAFS images

This example filters for a XPS file name and a NEXAFS file name and shows two things. First, that the XPS file name is not unique and filtering thus needs to be combined with extra parameters such as date and second how NEXAS and XPS data can be simultaneously passed to the nappPlot function

tmpDataSelection = matches(FileList.name,'0067067_O1s_1000eV.ibw') | matches(FileList.name, '0063_20200824_042852_O_K_edge.h5');
nappPlot(FileList,tmpDataSelection);

Plot data: XPS spectra

We use the nappPlot function to plot the spectra by selecting 'spectrum_countsPerSecond' as 'tableVariable'. Further options include the binding or kinetic energy scale, and each spectrum in a separate window, or all in one window by setting the 'mode'. This works, of course, only after the spectrum has been generated using the function nappGenerateSpectra.

nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond',...
 'mode','AllInOne','energyScale','kinetic');

Plot data: NEXAFS spectra

We use the same, one-and-only nappPlot function to plot the NEXAFS spectra by selecting 'spectrum_counts' as 'tableVariable' and 'energyScale' as 'xRay'. Further options include plotting each spectrum in a separate window, or all in one window by setting the 'mode'. This works, of course, only after the spectrum has been generated using the function nappGenerateSpectra.

nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_counts','energyScale','xRay','mode','OneEach')

Plot data: Figure layout

The nappPlot function has 3 pre-defined figure layouts optimised for the computer screen, for powerpoint presentation or for word (and other text) files:

nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_counts','energyScale','xRay','mode','OneEach','figureMode','screen');
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_counts','energyScale','xRay','mode','OneEach','figureMode','word');

nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_counts','energyScale','xRay','mode','OneEach','figureMode','powerpoint');

Plot data: Export the Figure (for use in powerpoint)

Either, copy paste the figure, or use this command on the front most figure in Matlab:

set(gcf,'InvertHardcopy','on');
print('NameForFigureFile, '-dpng', '-r220');

Plot data: Add region labels to survey

Here is an example of how to mark regions of interest in survey plots. For this, a survey must be the topmost figure.

   dataObjs    = findobj(gcf,'-property','YData');
   tmpxShift          = 5;         % binding energy shift and mono offset
   tmpxRayEnergy      = 900;       % of this survey
   tmpyPosition       = 0.5E5;     % y position of labels, delta to signal height,  in axis scale
   tmpyPositionTwo    = 0.1E5;     % optional, to shift neighouring labels up and dow
   % n
   % Au peaks
   nappAnnotateSurvey(tmpxRayEnergy-57-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'Au5p3/2');
   nappAnnotateSurvey(tmpxRayEnergy-74-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Au5p5/2');
   nappAnnotateSurvey(tmpxRayEnergy-84-tmpxShift,tmpyPosition,dataObjs(1),'Au4f7/2');
   nappAnnotateSurvey(tmpxRayEnergy-88-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'Au4f5/2');
   nappAnnotateSurvey(tmpxRayEnergy-107-tmpxShift,tmpyPosition,dataObjs(1),'Au5s');
   nappAnnotateSurvey(tmpxRayEnergy-335-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Au4d1/2');
   nappAnnotateSurvey(tmpxRayEnergy-353-tmpxShift,tmpyPosition,dataObjs(1),'Au4d3/2');
   nappAnnotateSurvey(tmpxRayEnergy-546-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Au4p3/2');
   nappAnnotateSurvey(tmpxRayEnergy-643-tmpxShift,tmpyPosition,dataObjs(1),'Au4p1/2');
   nappAnnotateSurvey(tmpxRayEnergy-762-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Au4s');
   nappAnnotateSurvey(tmpxRayEnergy-8-tmpxShift,tmpyPosition,dataObjs(1),'AuVB');
   % usual suspects
   nappAnnotateSurvey(tmpxRayEnergy-285-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'C1s');
   nappAnnotateSurvey(tmpxRayEnergy-534-tmpxShift,tmpyPosition,dataObjs(1),'O1s');
   nappAnnotateSurvey(tmpxRayEnergy-30-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'O2s');
   nappAnnotateSurvey(tmpxRayEnergy-405-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'N1s');
   nappAnnotateSurvey(tmpxRayEnergy-495-tmpxShift,tmpyPosition,dataObjs(1),'Sn3d');
   nappAnnotateSurvey(tmpxRayEnergy-691-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'F1s');
   nappAnnotateSurvey(tmpxRayEnergy-295-tmpxShift,tmpyPosition,dataObjs(1),'K2p');
   nappAnnotateSurvey(tmpxRayEnergy-380-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'K2s');
   nappAnnotateSurvey(tmpxRayEnergy-153-tmpxShift,tmpyPosition,dataObjs(1),'Si2s');
   nappAnnotateSurvey(tmpxRayEnergy-100-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Si2p');
   nappAnnotateSurvey(tmpxRayEnergy-197-tmpxShift,tmpyPosition,dataObjs(1),'Cl2p');
   nappAnnotateSurvey(tmpxRayEnergy-286-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'Sn3p');
   % Some Auger
   nappAnnotateSurvey(360-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'N KLL');
   nappAnnotateSurvey(500-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'O KLL');
   % tid up
   clearvars tmp*

Process data: Align sequence iterations in images

To shift and align the kinetic energy of each sequence iteration relative to each other, one can use

  1. nappDetermineShiftXps: calculate the kinetic energy shift of each sequence iteration of an image relative to one sequence iteration (first is default)
  2. nappCopyShift: optionally copy the kinetic energy shift to other data
  3. nnappAlignImages: shift each sequence iterations of an image so that the peak maxima align.
% compile data
tmpDataSelection = contains(FileList.name,'67_O1s') ...
    & (month(FileList.lastModifiedDatenum) == 8) ...
    & (year(FileList.lastModifiedDatenum) == 2021);
% plot images
nappPlot(FileList,tmpDataSelection,'tableVariable','image');
% align images
FileList = nappDetermineShiftXps(FileList,'tableSelection',tmpDataSelection,...
    'showFigures','off','tableVariable','image');
FileList = nappAlignImages(FileList,'tableSelection',tmpDataSelection,...
    'tableVariable','image');
% plot images
nappPlot(FileList,tmpDataSelection,'tableVariable','image');
GlobalSearch stopped because it analyzed all the trial points.

All 26 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 17 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 14 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 29 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 27 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 36 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 25 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 26 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 43 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 22 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 37 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 36 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 42 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 38 local solver runs converged with a positive local solver exit flag.

GlobalSearch stopped because it analyzed all the trial points.

All 41 local solver runs converged with a positive local solver exit flag.

Process data: Generate XPS spectra

All sequence iterations of an image are summed or the mean of all sequence iterations is calculated. The following command calculates the average of all sequence iterations from the filtered files in the FileList table. The spectrum based on the mean is saved in the column 'spectrum_countsPerSecond'.

FileList = nappGenerateSpectra(FileList,tmpDataSelection);

Process data: Generate NEXAFS spectra

% All Auger spectra of an image are summed. Options include to sum the data with the energy interval of the aquisition, or to use the trapz function to normalise to 1 eV data spacing. The NEXAFS spectrua are always saved in the column 'spectrum_counts'.

FileList = nappGenerateSpectra(FileList,'tableSelection',tmpDataSelection,'mode','sum');
FileList = nappGenerateSpectra(FileList,tmpDataSelection);

Process data: Align XPS spectra to background

To align the spectra to a background the function nappBacktround can be used. Options include to compute the mean or a linear regression. Here, we calculate the mean between 367 and 370 eV.

FileList = nappBackground(FileList,367,370,'tableSelection',tmpDataSelection,'...
energyScale','kinetic','mode','mean');

Process data: Align NEXAFS spectra to background

To align the spectra to a background the function nappBacktround can be used. Options include to compute the mean or a linear regression. Here, we calculate the mean between 522 and 525 eV xRay energy.

FileList = nappBackground(FileList,522,525,'tableSelection',tmpDataSelection,...
'tableVariable','spectrum_counts','energyScale','xRay','mode','mean');

Process data: Gauss peak fit

Gauss fitting can be used to determine area, width, and position of PE peaks. nappFitGaussXps de-convolutes the PE signal using 4 features. The fits can be constrained by

The function needs fit parameters as input, such as min and max peak area, width, and position; initial values for fitting; and the spectrum range.

% initial fit parameters
tmpFitParameters.peakArea = [1E4 5E3 0.1 0.1];
tmpFitParameters.peakWidth = [2.0 2.0 0.1 0.1];
tmpFitParameters.peakPos = [363 -1 -2 -3];
% lower bonds
tmpFitParameters.peakAreaLowerBond = [1E3 5E3 0.1 0.1];
tmpFitParameters.peakWidthLowerBond = [2.0 2.0 0.1 0.1];
tmpFitParameters.peakPosLowerBond = 361;
% upper bonds
tmpFitParameters.peakAreaUpperBond = [1E6 10E4 0.1 0.1];
tmpFitParameters.peakWidthUpperBond = [3.0 2.0 0.1 0.1];
tmpFitParameters.peakPosUpperBond = 365;
% spectrum range
tmpFitParameters.backgroundRange = [5 5];

% „position“ mode tmpFitParameters.peakArea = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakWidth = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakPos = [abs#1 rel#2 rel#3 rel#4];

tmpFitParameters.peakAreaUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakPosUpperBond = #1; tmpFitParameters.peakAreaLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakPosLowerBond = #1;

% „unconstrained“ mode tmpFitParameters.peakArea = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakWidth = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakPos = [abs#1 abs#2 abs#3 abs#4];

tmpFitParameters.peakAreaUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakPosUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakAreaLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakPosLowerBond = [#1 #2 #3 #4];

% „ widthPosition“ mode tmpFitParameters.peakArea = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakWidth = [abs#1-#4]; tmpFitParameters.peakPos = [abs#1 rel#2 rel#3 rel#4];

tmpFitParameters.peakAreaUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthUpperBond = #1-#4; tmpFitParameters.peakPosUpperBond = #1; tmpFitParameters.peakAreaLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthLowerBond = #1-#4; tmpFitParameters.peakPosLowerBond = #1;

% „ width“ mode tmpFitParameters.peakArea = [abs#1 abs#2 abs#3 abs#4]; tmpFitParameters.peakWidth = [abs#1-#4]; tmpFitParameters.peakPos = [abs#1 abs#2 abs#3 abs#4];

tmpFitParameters.peakAreaUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthUpperBond = #1-#4; tmpFitParameters.peakPosUpperBond = [#1 #2 #3 #4]; tmpFitParameters.peakAreaLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthLowerBond = #1-#4; tmpFitParameters.peakPosLowerBond = [#1 #2 #3 #4];

% „ unconstrainedAndWidthPosition" mode (novel: rel distance fit parameter) tmpFitParameters.peakArea = [abs#1 abs#2 abs#3 abs#4 abs#5]; tmpFitParameters.peakWidth = [abs#1,2,4,5 abs#3]; tmpFitParameters.peakPos = [rel#1 abs#2 abs#3 abs#4 abs#5];

tmpFitParameters.peakAreaUpperBond = [#1 #2 #3 #4 #5]; tmpFitParameters.peakWidthUpperBond = [#1,2,4,5 #3]; tmpFitParameters.peakPosUpperBond = [#1 #2 #3 #4 #5]; tmpFitParameters.peakAreaLowerBond = [#1 #2 #3 #4]; tmpFitParameters.peakWidthLowerBond = [#1,2,4,5 #3]; tmpFitParameters.peakPosLowerBond = [#1 #2 #3 #4];

FileList = nappFitGaussXps(FileList,tmpDataSelection,tmpFitParameters,'tableSelection',tmpDataSelection,...
    'energyScale','kinetic','mode','position','showFigures','on');

% The fit results are written to the data table. Peak position is only written to the table if in binding energy mode. Further, results are plotted to the command line and fits are shown in graphs:

GlobalSearch stopped because it analyzed all the trial points.

All 32 local solver runs converged with a positive local solver exit flag.
 
 
Fit results
0035035_O1s_900eV.ibw
 
             position      FWHW       width        area    
             _________    _______    _______    ___________

    peak1    {'363.6'}    {'2.4'}    {'2.0'}    {' 644646'}
    peak2    {'362.6'}    {'2.4'}    {'2.0'}    {'   5000'}
    peak3    {'361.6'}    {'0.1'}    {'0.1'}    {'      0'}
    peak4    {'360.6'}    {'0.1'}    {'0.1'}    {'      0'}
    peak5    {'NaN'  }    {'NaN'}    {'NaN'}    {'    NaN'}


GlobalSearch stopped because it analyzed all the trial points.

All 43 local solver runs converged with a positive local solver exit flag.
 
 
Fit results
0056056_O1s_900eV.ibw
 
             position      FWHW       width        area    
             _________    _______    _______    ___________

    peak1    {'363.2'}    {'2.4'}    {'2.0'}    {' 291250'}
    peak2    {'362.2'}    {'2.4'}    {'2.0'}    {'   5000'}
    peak3    {'361.2'}    {'0.1'}    {'0.1'}    {'      0'}
    peak4    {'360.2'}    {'0.1'}    {'0.1'}    {'      0'}
    peak5    {'NaN'  }    {'NaN'}    {'NaN'}    {'    NaN'}


GlobalSearch stopped because it analyzed all the trial points.

All 36 local solver runs converged with a positive local solver exit flag.
 
 
Fit results
0060060_O1s_900eV.ibw
 
             position      FWHW       width        area    
             _________    _______    _______    ___________

    peak1    {'363.4'}    {'2.4'}    {'2.0'}    {' 291466'}
    peak2    {'362.4'}    {'2.4'}    {'2.0'}    {'   5000'}
    peak3    {'361.4'}    {'0.1'}    {'0.1'}    {'      0'}
    peak4    {'360.4'}    {'0.1'}    {'0.1'}    {'      0'}
    peak5    {'NaN'  }    {'NaN'}    {'NaN'}    {'    NaN'}

Could not write results to table. Writing only 4 entries

Process data: Norm spectra

To normalise spectra, nappScaleToArea can be used. A typical example is to normalise C1s to the O1s peak area to compensate for changes in the working distance. The function uses the peakArea table column, thus nappFitGaussXps needs to run first. Results are written to the table variable spectrum_countsNorm (as the unit is no longer countsPerSecond). To normalise C1s to the O1s peak area, select C1s spectra using a logical (tmpDataSelection), and choose C1s for regionOld and O1s for regionNew:

FileList = nappScale(FileList,tmpDataSelection,'regionOld','C1s','regionNew','O1s');
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsNorm');

Process data: Binding energy scale

To calculate the binding energy, you need to

  1. nappDetermineShiftXps: calculate the kinetic energy shift relative to a reference value
  2. nappCopyShift: optionally copy the kinetic energy shift to other data
  3. nappBindingEnergyXps: calculate the binding energy based on the kinetic energy shift either determined relative to a reference or copied to the individual data.

The following example shows how the kinetic energy shift is calculated based on selected O1s spectra relative to 535 eV, copied to the C1s data with the same file number, and finally the binding energy of the C1s data is calculated (based on the observed shift of the O1s of the same sequence run) and plotted

close all
% generate spectra for C1s and O1s
tmpDataSelection =  matches(FileList.sample,'Ice with N2')...
    & ~contains(FileList.sample,'(bad data)')...
    & (contains(FileList.name,'O1s') | contains(FileList.name,'C1s'))...
    & FileList.xRayEkinRange_eV(:,1) == 900 ...
    & FileList.scientaPassEnergy_eV == 50 ...
    & (month(FileList.lastModifiedDatenum) == 2) ...
    & (year(FileList.lastModifiedDatenum) == 2022) ...
    & (day(FileList.lastModifiedDatenum) >= 27);
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
FileList = nappBackground(FileList,367,370,'tableSelection',tmpDataSelection,'energyScale','kinetic',...
    'mode','mean');
% determine shift of O1s
tmpDataSelection =  matches(FileList.sample,'Ice with N2')...
    & ~contains(FileList.sample,'(bad data)')...
    & contains(FileList.name,'O1s')...
    & FileList.xRayEkinRange_eV(:,1) == 900 ...
    & FileList.scientaPassEnergy_eV == 50 ...
    & (month(FileList.lastModifiedDatenum) == 2) ...
    & (year(FileList.lastModifiedDatenum) == 2022) ...
    & (day(FileList.lastModifiedDatenum) >= 27);
FileList = nappDetermineShiftXps(FileList,'tableSelection',tmpDataSelection,...
          'showFigures','off','tableVariable','spectrum_countsPerSecond','binEnReference',533);
% copy shift from O1s to C1s
FileList = nappCopyShift(FileList,tmpDataSelection,'energyScale','binding');
% calculate binding energy of these C1s
tmpDataSelection =  matches(FileList.sample,'Ice with N2')...
    & ~contains(FileList.sample,'(bad data)')...
    & contains(FileList.name,'C1s') & FileList.xRayEkinRange_eV(:,1) == 900 ...
    & FileList.scientaPassEnergy_eV == 50 ...
    & (month(FileList.lastModifiedDatenum) == 2) ...
    & (year(FileList.lastModifiedDatenum) == 2022) ...
    & (day(FileList.lastModifiedDatenum) >= 27);
FileList = nappBindingEnergyXps(FileList,'tableSelection',tmpDataSelection);
% plot C1s spectra
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond',...
    'mode','AllInOne','energyScale','kinetic');
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond',...
    'mode','AllInOne','energyScale','binding');

Process data: Combine spectra

To get better statistics (signal-to-noise), repeated measurements can be added or averaged into one spectrum. The results are written into a matrix with energy in column 1, counts in column2, and standard deviation in column 3 (only for mean). So, the nappFunctions can no longer be used for this data format.

tmpDataSelection =  contains(FileList.name,'C1s')...
    & contains(FileList.sample,'MgO ice AA')...
    &  ~endsWith(FileList.sample,'(bad data)')...
    & FileList.sampleTemp_dC >= -26 & FileList.sampleTemp_dC <= -24;
MgO_C1s_Warm = nappCombineSpectra(FileList,'tableRow',1,...
    'tableSelection',tmpDataSelection,'tableVariable',...
    'spectrum_countsPerSecond','EnergyScale','binding','mode','average');