How to analyse ISS XPS and NEXAFS data using Matlab
Contents
- Introduction: Concept
- Introduction: Function calls
- Introduction: Data structure
- ToDo
- Prepare: Initialise MATLAB
- Prepare: ISS data structure and path to local copy of ISS data
- Helper functions
- Import ISS data: Complete beam time folder
- Import ISS data: Error messages
- Import ISS data: Add data to existing data table
- Import ISS data: Add new SES or NEXAFS files from local beam time folder
- Import ISS data: Choose files manually
- Work with data: Excel
- Work with data: Add photon energy to table (XPS)
- Work with data: Add sample description
- Work with data: Filter table
- Work with data: Display filtered table and selected columns
- Work with data: Modify data in table
- Add data (columns) to table
- Plot data: XPS images
- Plot data: XPS and NEXAFS images
- Plot data: XPS spectra
- Plot data: NEXAFS spectra
- Plot data: Figure layout
- Plot data: Export the Figure (for use in powerpoint)
- Plot data: Add region labels to survey
- Process data: Align sequence iterations in images
- Process data: Generate XPS spectra
- Process data: Generate NEXAFS spectra
- Process data: Align XPS spectra to background
- Process data: Align NEXAFS spectra to background
- Process data: Gauss peak fit
- Process data: Norm spectra
- Process data: Binding energy scale
- Process data: Combine spectra
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');
- 'tableSelection': allows to filter the table and run the function only on selected table lines. tmpDataSelection is a logical array, see section Work with data: Filter table in this document.
- 'tableVariable': allows to choose the data of the data table that the function should work with. See Introduction: Data structure section in this document.
- 'energyScale': allows to use kinetic or binding energy scale to plot or perform calculation.
- 'mode': allows to select options for functions such as using mean or sum for calculations, etc.
- 'showFigures': allows to view or view not figures and function output.
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.
- Upon import of the SES data, region iterations are averaged and counts are normalised to 1 s.
- 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:
- image: PE data: Intensity in counts-per-second vs. kinetic energy and sequence iteration. Functions that work with image data, overwrite this column.
- imageRoh: a copy of image at the time of data import.
- spectrum_countsPerSecond and spectrum_counts: Hold the spectra that are computed from the image. These data columns are modified by nappFunctions that work with spectrum data.
- spectrumRoh_countsPerSecond and spectrumRoh_counts: a copy of the spectrum data at time of generation.
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
- add code for intensity vs. time plotting
- add code for average and plot iterations.
- get rid of peakfind and replace by gauss fitting
- use gauss fitting function within functions?
- Tidy html output, and get Yanisha's feedback
- Add section on how to modify figures after plotting
- add section on how to access data in table
- add section on how to merge data tables
- Work on code and add section how to remove XPS from NEXAFS
- Work on code and add section on I0 including 2nd order light
- Add code to add combined spectra to data table as row.
- Update help files for NEXAFS functions and NEXAFS options in general functions.
- Set-up folders for Yanisha
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:
- NEXAFS holds *.h5 XAS data
- Photos holds *.bmp pictures
- Pressure holds the *.dat LabView files
- SES holds the *.ibw PE data.
- To run the scripts, there needs to be at least ONE *.ibw file in the SES folder
beam time folder location
- The beam time folder can be a subfolder of any number of folders.
- The beam time folder needs to have the same name, same subfolders, and folders above (path) locally and on the NAPP drive. This can be set by the user when calling the import function. As standard, we use "year:YYYY_MM__monthIssIceNapp"
- on Napp drive: the beam time folder (and folders above) needs to be within the "SC Data" folder
- locally: the beam time folder (and folders above) needs to be within your "MATLAB home folder:NAPP:SC Data" folder
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
- Napp_p_ice(T_current) states and plots the vapour pressure of ice between 200-270 K
- Napp_p_RH(T_current,RH) calculates the partial pressure of water for a given temperature and relative humidity.
- Napp_RH_p(T_current,partial_pressure_water) calculates the relative humidity for a given temperature and water pressure.
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
- that there are no typos in the path names
- that the drives are mounted.
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
- nappDetermineShiftXps: calculate the kinetic energy shift of each sequence iteration of an image relative to one sequence iteration (first is default)
- nappCopyShift: optionally copy the kinetic energy shift to other data
- 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
- 'widthPosition' (Default): With of all peaks is identical. Position of peaks 2-3 is set relative to peak 1.
- 'position': Position of peaks 2-3 is set relative to peak 1.
- 'width': all peaks have same width
- 'unconstrained': Each gauss peak is fitted independent.
- 'unconstrainedAndWidthPosition': One unconstrained peak #3 (for gas phase feature) and 4 peaks constrained in width and/or position
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.
- spectrum range: define the region of the PE spectrum used for fitting (plus and minus from peak 1 position). % position of peak 2-4 are relative to peak 1, that of peak 1 absolute (either in binding or kinetic energy scale)
- If you are only interested in less than 4 peaks, choose small dummy constrains for those peaks you don't want to fit.
% 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:
- regionOld: C1s (default) or O1s - region that will be normalised
- regionNew: O1s (default) or C1s - peak area that will be used to normalise
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
- nappDetermineShiftXps: calculate the kinetic energy shift relative to a reference value
- nappCopyShift: optionally copy the kinetic energy shift to other data
- 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');