Files
nappMATLAB-toolbox/beamtime/documentedExampleScripts/generateSpectra.m
T
bartels-rausch 091cf00e77 Refactor energy calibration pipeline, introduce modern arguments blocks, and add universal logging
* Transitioned nappNorm, nappDetermineShift, and nappCopyShift to use modern MATLAB arguments blocks, removing their legacy functionSignatures.json entries.

* Refactored nappNorm to rely on prior peak fitting results (e.g., from nappFitGaussXps) for normalization rather than performing the fitting internally.

* Decoupled energy calibration: nappDetermineShift now solely calculates internal binding energy shifts, and nappCopyShift distributes them using a unified matching engine ('earlier', 'after', 'spectrumNumber').

* Implemented a Universal Logging Block to track data provenance, automatically appending execution timestamps and caller script names to processed rows.

* Updated nappCombineSpectra to output a logDataSource cell array and cleanly name combined spectra.

* Created nappSaveDataTable for automatic, script-named .mat file saving, complete with a Live Editor interceptor.

* Added a publish-mode silencer to nappFitGaussXps to prevent optimizer spam in generated PDFs.

* Updated readme.md to document the new arguments structure, the 3-step calibration workflow, the matching engine, and the new table variables.
2026-03-25 12:07:49 +01:00

311 lines
14 KiB
Matlab
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
%% Generate Spectra
%
% Here we plot all spectra and all regions to confirm that we get what we
% need.
%
% This file shows some typical out-of-the-box examples.
%% Start fresh
clc
close all
clearvars
%% Import Data
%
% This was generated by importData.m. You can either force that it runs
% again before this script runs (preferred, ensures you always have recent
% data in MATLAB's data table) or load the FileList.
run('importData.m')
% load("FileList.mat","FileList")
%% Plot a Survey
for ii = 1
%% Start fresh
close all
clc
%% Select data
% It is convenient to add date and year of the beamtime. This ensures you
% can use the code also when you work with data from several beamtimes
% (indexing of the files starts with 0 for each beamtime by default).
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& contains(FileList.name,"Survey") ...
& (contains(FileList.name,"128"));
% Always ensure that you select the spectra you intended.
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr'}))
%% Generate spectrum and plot
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond','mode','OneEach');
%% Add annotation to the figure
%
% In a survey, we usually want to identify all peaks. Here, based on
% binding energies from the literature and a shift, we add common regions:
%
% **Note that the binding energies are rough estimates – to use this further
% we need to confirm them and add the source/reference.**
dataObjs = findobj(gcf,'-property','YData');
tmpxShift = 7;
tmpxRayEnergy = 850;
tmpyPosition = 0.5E5;
tmpyPositionTwo = 0.8E5;
% 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-271-tmpxShift,tmpyPosition,dataObjs(1),'Cl2s');
nappAnnotateSurvey(tmpxRayEnergy-286-tmpxShift,tmpyPosition-tmpyPositionTwo,dataObjs(1),'Sn3p');
% Clay
%nappAnnotateSurvey(tmpxRayEnergy-351-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ca2p');
%nappAnnotateSurvey(tmpxRayEnergy-347-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ca2p');
%nappAnnotateSurvey(tmpxRayEnergy-440-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ca2s');
%nappAnnotateSurvey(tmpxRayEnergy-34-tmpxShift,tmpyPosition+tmpyPosition,dataObjs(1),'Ca3s');
%nappAnnotateSurvey(tmpxRayEnergy-26-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ca3p');
%nappAnnotateSurvey(tmpxRayEnergy-297-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'K2p');
%nappAnnotateSurvey(tmpxRayEnergy-294-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'K2p');
%nappAnnotateSurvey(tmpxRayEnergy-35-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'K3s');
%nappAnnotateSurvey(tmpxRayEnergy-19-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'K3p');
nappAnnotateSurvey(tmpxRayEnergy-118-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Al');
nappAnnotateSurvey(tmpxRayEnergy-72.5-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Al');
% Ar gas
nappAnnotateSurvey(tmpxRayEnergy-245-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ar2p');
nappAnnotateSurvey(tmpxRayEnergy-323-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ar2s');
nappAnnotateSurvey(tmpxRayEnergy-12-tmpxShift,tmpyPosition+tmpyPositionTwo,dataObjs(1),'Ar3p');
% Some Auger
nappAnnotateSurvey(363,tmpyPosition-tmpyPositionTwo,dataObjs(1),'N KLL');
nappAnnotateSurvey(203,tmpyPosition-tmpyPositionTwo,dataObjs(1),'Ar LMN');
nappAnnotateSurvey(500,tmpyPosition-tmpyPositionTwo,dataObjs(1),'O KLL');
end
%% Plot NEXAFS & Resonant Data
%
% You can plot images (raw data from SES) without generating the spectra.
% (You can also first generate the spectra, but to plot images the spectra
% are not needed.)
for ii = 1
%% Start fresh
clc; close all
%% Select data
tmpDataSelection = ...
~contains(FileList.sample,"(bad data)") ...
& contains(FileList.name,"O_Kedge_Resonant") ...
& contains(FileList.name,"0025");
% Always ensure that you select the spectra you intended.
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr'}))
%% Plot images
% This one shows x, y in energy scales (data were interpolated).
nappPlot(FileList,tmpDataSelection,'tableVariable','image',...
'mode','OneEach');
% This one shows sweeps (original data).
nappPlot(FileList,tmpDataSelection,'tableVariable','image','energyScale','sweep');
%% Plot selected Auger-Meitner
%
% This plots the electron intensity vs. kinetic energy for chosen X-ray
% energies (directly from the image).
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& contains(FileList.name,"O_Kedge_Resonant") ...
& contains(FileList.name,"0025");
nappPlot(FileList,tmpDataSelection,'tableVariable','image','mode','AugerMeitner','AugerMeitnerEnergies',[525,545,550]);
% It also shows an example of how the current plot can be edited manually
% after its generation by nappPlot.
hax = gca;
hax.XDir = 'reverse';
xlabel('Auger-Meitner binding energy [eV]');
%% Plot image interactive
%
% Use the mouse: click once in the image, then the Auger-Meitner and NEXAFS will
% follow the mouse position.
% Select data (Nov 2025, O_Kedge resonant, and file number 25):
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& contains(FileList.name,"O_Kedge_Resonant") ...
& contains(FileList.name,"0025");
% Plot the selected data in an interactive figure:
nappPlot(FileList,tmpDataSelection,'tableVariable','image','mode','interactive');
%% Generate spectra and plot
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_counts',...
'mode','OneEach');
%% arrange figures
distFig
end
%% Plot iterations
for ii = 1
%% Select your data
%
% You can change the plotStep to show fewer iterations (for example 2 for every second one).
% However, the total number of sequence iterations needs to be a multiple of
% that factor. This is nice to follow a signal with time, e.g. upon dosing.
% It requires that each sequence iteration has long enough dwell time and
% iterations to give a signal with decent S/N.
%% Select data
close all; clc
tmpDataSelection = startsWith(FileList.name, "0191191_O1s_VB_525eV");
% List the selected files in the command window.
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr','scientaPassEnergy_eV'}))
%% Plot the iterations
nappPlot(FileList,tmpDataSelection,'tableVariable','image','mode','iterations','plotStep',6);
%% Modify the top figure
legend
distFig
end
%% Look at all regions
%
% To confirm you see what you expect or to change the experimental settings
% if not.
for ii = 1
%% Start fresh
close all
clc
% Filter data (select all regions of two files).
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& ~contains(FileList.name,"Survey") ...
& ~contains(FileList.name,"Resonant") ...
& (contains(FileList.name,"146") | contains(FileList.name,"148"));
% List the selected files in the command window.
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr','scientaPassEnergy_eV'}))
%% Generate spectra and plot
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond','mode','OneEach');
distFig
%% Discussion
%
% Your observation and comments could go here
end
%% Look at one region in detail
%
% To confirm you see what you expect or to change the experimental settings
% if not.
for ii = 1
%% Start fresh
clc; close all
%% Select data
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& ~contains(FileList.name,"Survey") ...
& ~contains(FileList.name,"Resonant") ...
& (contains(FileList.name,"Al2p") | contains(FileList.name,"Cl2p")) ...
& (contains(FileList.name,"146") | contains(FileList.name,"148"));
% List the selected files in the command window.
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr'}))
%% Plot
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond','mode','OneEach');
%% Normalize Cl2p intensity to that of Al2p
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& ~contains(FileList.name,"Survey") ...
& ~contains(FileList.name,"Resonant") ...
& contains(FileList.name,"Cl2p") ...
& contains(FileList.name,"146");
FileList(tmpDataSelection,:).spectrum_countsPerSecond{:} = FileList(tmpDataSelection,:).spectrum_countsPerSecond{:} .* 22335./39687;
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& ~contains(FileList.name,"Survey") ...
& ~contains(FileList.name,"Resonant") ...
& contains(FileList.name,"Al2p") ...
& contains(FileList.name,"146");
FileList(tmpDataSelection,:).spectrum_countsPerSecond{:} = FileList(tmpDataSelection,:).spectrum_countsPerSecond{:} .* 22335./39687;
%% Align to same background
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& ~contains(FileList.name,"Survey") ...
& ~contains(FileList.name,"Resonant") ...
& contains(FileList.name,"Cl2p") ...
& (contains(FileList.name,"146") | contains(FileList.name,"148"));
FileList = nappBackground(FileList,647,648,'tableSelection',tmpDataSelection,'energyScale','kinetic',...
'mode','mean','showFigures','on');
%% Plot again
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond','mode','AllInOne');
% Optimize figure
xlim([647 652])
ylabel("counts [norm to Al]")
legend
distFig
end
%% Plot several spectra in one figure
clc; close all
for ii = 1
close all
clc
tmpDataSelection = month(FileList.lastModifiedDatenum) == 11 ...
& year(FileList.lastModifiedDatenum) == 2025 ...
& ~contains(FileList.sample,"(bad data)") ...
& contains(FileList.name, "Cl2p")...
& ~contains(FileList.name,"Survey") ...
& contains(FileList.name,"680eV") ...
& (contains(FileList.name,"77") | contains(FileList.name,"116") | contains(FileList.name,"130") | contains(FileList.name,"132") | contains(FileList.name,"133"));
FileList = nappGenerateSpectra(FileList,tmpDataSelection);
nappPlot(FileList,tmpDataSelection,'tableVariable','spectrum_countsPerSecond','mode','AllInOne');
disp(FileList(tmpDataSelection,{'name','lastModifiedDatestr','scientaRegionIterations','scientaSequenceIterations','scientaDwellTime_ms','scientaPassEnergy_eV'}))
end
%% helper functions
% Check if the environment is MATLAB or Octave
if exist('OCTAVE_VERSION', 'builtin')
% Anonymous function mimicking contains
contains = @(str, pattern) ~cellfun('isempty', strfind(str, pattern));
end