Added some helpers and moved the older verions to a specific folder
This commit is contained in:
1 parent
f15eadac6d
commit
873377ce80
4 files changed
+1132
No files matched your search
+140
@@ -0,0 +1,140 @@
|
||||
function DataTable = nappNorm(DataTable, processSelection, refSelection, options)
|
||||
% NAPPNORM Normalize XPS spectra based on provided reference selection or self-maximum.
|
||||
%
|
||||
% DataTable = NAPPNORM(DataTable, processSelection, refSelection) normalizes
|
||||
% the spectra where processSelection is true, dividing them by the peak area
|
||||
% of matched spectra from the pool where refSelection is true. Both must
|
||||
% be logical arrays matching the height of the DataTable.
|
||||
%
|
||||
% DataTable = NAPPNORM(DataTable, processSelection) scales each spectrum
|
||||
% selected by the logical array by its own maximum value.
|
||||
%
|
||||
% Name-Value arguments:
|
||||
% matchStrategy - 'earlier', 'after', 'internalMax'
|
||||
% tableVariable - Target column ('spectrum_countsPerSecond' by default)
|
||||
% timeVariable - Target time column ('lastModifiedDatenum' by default)
|
||||
% peakNumber - Which peak area to use if multiple exist (1 by default)
|
||||
|
||||
arguments
|
||||
DataTable table
|
||||
processSelection logical {mustBeVector}
|
||||
|
||||
% refSelection is optional. If omitted, it defaults to empty.
|
||||
refSelection logical {mustBeVector} = false(0,1);
|
||||
|
||||
% Name-Value arguments
|
||||
options.matchStrategy char {mustBeMember(options.matchStrategy, ...
|
||||
{'earlier', 'after', 'internalMax'})} = 'earlier'
|
||||
options.tableVariable char {mustBeMember(options.tableVariable, ...
|
||||
{'spectrum_counts', 'spectrum_countsPerSecond', 'spectrum_countsNorm'})} = 'spectrum_countsPerSecond'
|
||||
options.timeVariable char {mustBeMember(options.timeVariable, ...
|
||||
{'lastModifiedDatenum', 'creationDate'})} = 'lastModifiedDatenum'
|
||||
options.peakNumber (1,1) double {mustBeInteger, mustBePositive} = 1
|
||||
end
|
||||
|
||||
numRowsTable = height(DataTable);
|
||||
|
||||
% Validate length of the logical arrays
|
||||
if length(processSelection) ~= numRowsTable
|
||||
error('processSelection must have the exact same length as the DataTable height (%d).', numRowsTable);
|
||||
end
|
||||
if ~isempty(refSelection) && any(refSelection) && length(refSelection) ~= numRowsTable
|
||||
error('refSelection must have the exact same length as the DataTable height (%d).', numRowsTable);
|
||||
end
|
||||
|
||||
% Auto-detect mode: If no valid reference array is passed OR 'internalMax' is chosen
|
||||
if strcmpi(options.matchStrategy, 'internalMax') || isempty(refSelection) || ~any(refSelection)
|
||||
mode = 'maximum';
|
||||
else
|
||||
mode = 'referenceArea';
|
||||
end
|
||||
|
||||
% Initialize new columns if they don't exist
|
||||
if ~ismember('spectrum_countsNorm', DataTable.Properties.VariableNames)
|
||||
DataTable.spectrum_countsNorm = cell(numRowsTable, 1);
|
||||
end
|
||||
if ~ismember('logNormMode', DataTable.Properties.VariableNames)
|
||||
DataTable.logNormMode = cell(numRowsTable, 1);
|
||||
end
|
||||
if ~ismember('logNormWith', DataTable.Properties.VariableNames)
|
||||
DataTable.logNormWith = cell(numRowsTable, 1); % New column for spectrum name
|
||||
end
|
||||
|
||||
%% Processing Loop
|
||||
processIdx = find(processSelection);
|
||||
refIdx = find(refSelection);
|
||||
|
||||
for i = 1:length(processIdx)
|
||||
currRow = processIdx(i);
|
||||
yData = DataTable.(options.tableVariable){currRow};
|
||||
currTime = datenum(DataTable.(options.timeVariable)(currRow, :));
|
||||
refTimes = datenum(DataTable.(options.timeVariable)(refIdx, :));
|
||||
currName = DataTable.name{currRow};
|
||||
bestRefRow = NaN;
|
||||
|
||||
if isempty(yData)
|
||||
continue;
|
||||
end
|
||||
|
||||
switch mode
|
||||
case 'maximum'
|
||||
normFactor = max(yData);
|
||||
if normFactor == 0, normFactor = 1; end % Protect against /0
|
||||
|
||||
DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor;
|
||||
DataTable.logNormMode{currRow} = 'maximum';
|
||||
DataTable.logNormWith{currRow} = num2str(normFactor); % Log internal maximum
|
||||
|
||||
case 'referenceArea'
|
||||
|
||||
|
||||
|
||||
% Internal Matching Engine using Code Words
|
||||
switch options.matchStrategy
|
||||
case 'earlier'
|
||||
validRefs = refIdx(refTimes <= currTime);
|
||||
if ~isempty(validRefs)
|
||||
timeDiffs = currTime - refTimes(refTimes <= currTime);
|
||||
[~, minIdx] = min(timeDiffs);
|
||||
bestRefRow = validRefs(minIdx);
|
||||
end
|
||||
|
||||
case 'after'
|
||||
validRefs = refIdx(refTimes >= currTime);
|
||||
if ~isempty(validRefs)
|
||||
timeDiffs = refTimes(refTimes >= currTime) - currTime;
|
||||
[~, minIdx] = min(timeDiffs);
|
||||
bestRefRow = validRefs(minIdx);
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
% Validate we found a match
|
||||
if isnan(bestRefRow)
|
||||
warning('Row %d (%s): No matching reference found for strategy "%s". Skipping.', ...
|
||||
currRow, currName, options.matchStrategy);
|
||||
continue;
|
||||
end
|
||||
|
||||
% Extract area and validate
|
||||
refAreas = DataTable.peakArea(bestRefRow,:);
|
||||
if size(refAreas,2) < options.peakNumber
|
||||
warning('Row %d: peakNumber %d exceeds available peaks in reference row %d. Skipping.', ...
|
||||
currRow, options.peakNumber, bestRefRow);
|
||||
continue;
|
||||
end
|
||||
|
||||
normFactor = refAreas(options.peakNumber);
|
||||
if normFactor == 0
|
||||
warning('Row %d: Reference area in row %d is zero. Skipping.', currRow, bestRefRow);
|
||||
continue;
|
||||
end
|
||||
|
||||
% Apply normalization and log the exact name of the reference
|
||||
DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor;
|
||||
DataTable.logNormMode{currRow} = sprintf('referenceArea (Row %d)', bestRefRow);
|
||||
DataTable.logNormWith{currRow} = DataTable.name{bestRefRow};
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,609 @@
|
||||
function DataTable = nappDetermineShiftXps(DataTable, varargin)
|
||||
%NAPPDETERMINESHIFTXPS Determine energy shift calibration from XPS images or spectra.
|
||||
% This function calculates the energy shift needed to calibrate XPS
|
||||
% spectra to a binding energy reference or align all sequence iterations
|
||||
% of the same file to a common kinetic energy. For spectra data, it
|
||||
% computes absolute binding energy shifts relative to a reference binding
|
||||
% energy value. For images, it computes relative kinetic energy shifts
|
||||
% between sequence iterations. Shifts are determined by fitting Gaussian
|
||||
% peaks or finding maxima in the spectra (simpler option using Matlab's
|
||||
% findpeaks function).
|
||||
%
|
||||
% Needs X-ray energy in data table. Run addPhotonEnergy.m first.
|
||||
%
|
||||
%
|
||||
% SYNTAX
|
||||
% ------
|
||||
% DataTable = NAPPDETERMINESHIFTXPS(DataTable)
|
||||
%
|
||||
% DataTable = NAPPDETERMINESHIFTXPS(DataTable, eKinStart, eKinEnd, 'Name', Value)
|
||||
%
|
||||
% EXAMPLES
|
||||
% --------
|
||||
% % Determine binding energy shift for C1s spectra using peak finding
|
||||
% DataTable = nappDetermineShiftXps(DataTable, ...
|
||||
% 'tableVariable', 'spectrum_countsPerSecond', ...
|
||||
% 'binEnReference', 285.0, ...
|
||||
% 'mode', 'findPeak');
|
||||
%
|
||||
% % Determine kinetic energy shift for imaging data using the first
|
||||
% iteration as reference
|
||||
% DataTable = nappDetermineShiftXps(DataTable, 100, 200, ...
|
||||
% 'tableVariable', 'image', ...
|
||||
% 'kinEnReference', 1, ...
|
||||
% 'showFigures', 'on');
|
||||
%
|
||||
% % Determine binding energy shift using Gaussian fitting with custom
|
||||
% parameters specified in tmpFitParameters
|
||||
% FitPar.fitMode = 'position';
|
||||
% FitPar.refPeak = 1;
|
||||
% DataTable = nappDetermineShiftXps(DataTable, 280, 290, ...
|
||||
% 'tableVariable', 'spectrum_countsPerSecond', ...
|
||||
% 'binEnReference', 284.8, ...
|
||||
% 'mode', 'gaussFit', ...
|
||||
% 'FitParameter', tmpFitParameters);
|
||||
%
|
||||
% tmpFitParameters.peakArea = [1e5 0.5 0.1 0.1 0.1] ;
|
||||
% tmpFitParameters.peakWidth = 1.2;
|
||||
% tmpFitParameters.peakPos = [198 1.6 10 10 10];
|
||||
%
|
||||
% tmpFitParameters.peakAreaUpperBond = [Inf 0.5 0.1 0.1 0.1];
|
||||
% tmpFitParameters.peakWidthUpperBond = [3 NaN NaN NaN NaN];
|
||||
% tmpFitParameters.peakPosUpperBond = [200 1.6 10 10 10];
|
||||
%
|
||||
% tmpFitParameters.peakAreaLowerBond = [100 10 0.5 0.1 0.1 0.1];
|
||||
% tmpFitParameters.peakWidthLowerBond = [1.2 NaN NaN NaN NaN];
|
||||
% tmpFitParameters.peakPosLowerBxxond = [195 1.3 0.1 0.1 0.1];
|
||||
%
|
||||
% tmpFitParameters.backgroundRange = [6 5];
|
||||
% tmpFitParameters.weight = [198 200 0.1];
|
||||
% tmpFitParameters.fitMode = 'widthPosition';
|
||||
%
|
||||
% PROCESSING OVERVIEW
|
||||
% -------------------
|
||||
% The function operates differently depending on data type:
|
||||
%
|
||||
% FOR IMAGES (tableVariable = 'image' or 'imageRoh'):
|
||||
% 1. For each image in tableSelection:
|
||||
% - Extract kinetic energy axis (scientaEkin_eV or scientaEkinRoh_eV)
|
||||
% - Fit 2-peak Gaussian model to each sequence iteration (WIP: change
|
||||
% this to use nappFitGaussXPS.m)
|
||||
% - Extract peak position from fitting results
|
||||
% - Normalize shifts relative to reference iteration (kinEnReference)
|
||||
% 2. Result: kineticEnergyShift column contains shifts relative to reference iteration
|
||||
% (reference iteration has shift = 0)
|
||||
%
|
||||
% FOR SPECTRA (tableVariable = 'spectrum_counts', 'spectrum_countsPerSecond', or 'spectrum_countsNorm'):
|
||||
% 1. For each spectrum in tableSelection:
|
||||
% - Optional: restrict energy range using eKinStart/eKinEnd
|
||||
% - Find peak position using either:
|
||||
% * findPeak method: standard matlab peak detection
|
||||
% * gaussFit method: Gaussian fitting with global optimization
|
||||
% - Compute binding energy shift as: xRayPhotonEnergy - peakPosition - binEnReference
|
||||
% 2. Result: bindingEnergyShift column contains absolute shifts relative to reference BE
|
||||
% (positive shift means peak is at lower kinetic energy than expected)
|
||||
%
|
||||
% PEAK DETECTION METHODS
|
||||
% ----------------------
|
||||
% Two methods are available for finding peak positions in spectral
|
||||
%
|
||||
% 1. 'findPeak' (default): Fast automated detection using MATLAB's findpeaks
|
||||
% function with moving mean smoothing. Uses minPeakWidth, minPeakHeight,
|
||||
% and movMeanWindow parameters to control sensitivity. Best for well-defined
|
||||
% peaks with good signal-to-noise ratio. Accuracy: ±0.1-0.2 eV typical.
|
||||
%
|
||||
% 2. 'gaussFit': Slower but more accurate Gaussian fitting with global
|
||||
% optimization using GlobalSearch. Fits background, Gaussian peaks,
|
||||
% and extracts precise peak positions. Requires FitParameter structure.
|
||||
% Best for noisy data or when high precision is required. Accuracy: ±0.05 eV typical.
|
||||
%
|
||||
% IMPORTANT LIMITATION: This function requires distinct photoelectron peaks
|
||||
% to determine energy shifts. It will NOT work for background-only spectra
|
||||
% or featureless data. The fitting algorithms assume at least one measurable
|
||||
% peak is present in the specified energy range.
|
||||
%
|
||||
% INPUT ARGUMENTS
|
||||
% ---------------
|
||||
% DataTable table
|
||||
% Data table containing XPS image or spectral data. Must contain:
|
||||
% - scientaEkin_eV or scientaEkinRoh_eV: kinetic energy axis
|
||||
% - tableVariable data: images or spectra
|
||||
% - xRayEkin_eV: X-ray photon energy (eV) for each row
|
||||
%
|
||||
% eKinStart numeric scalar (optional)
|
||||
% Lower kinetic energy bound for peak fitting (eV). If omitted, uses
|
||||
% first energy point. Restricts the fitting region to improve accuracy.
|
||||
%
|
||||
% eKinEnd numeric scalar (optional)
|
||||
% Upper kinetic energy bound for peak fitting (eV). If omitted, uses
|
||||
% last energy point. Restricts the fitting region to improve accuracy.
|
||||
%
|
||||
% NAME-VALUE PAIR ARGUMENTS (optional)
|
||||
% ------------------------------------
|
||||
% 'tableSelection' logical array (default: all rows)
|
||||
% Logical index to filter which rows in DataTable to process.
|
||||
% Example: contains(DataTable.name, 'C1s') to process only C1s spectra
|
||||
%
|
||||
% 'tableVariable' char vector (default: 'spectrum_countsPerSecond')
|
||||
% Data type to process. Must be one of:
|
||||
%
|
||||
% Spectral data (compute binding energy shifts):
|
||||
% - 'spectrum_counts'
|
||||
% - 'spectrum_countsPerSecond'
|
||||
% - 'spectrum_countsNorm'
|
||||
%
|
||||
% Imaging data (compute kinetic energy shifts):
|
||||
% - 'image'
|
||||
% - 'imageRoh'
|
||||
%
|
||||
% 'mode' char vector (default: 'findPeak')
|
||||
% Peak detection method (spectral data only):
|
||||
% - 'findPeak': automated peak detection using moving mean smoothing
|
||||
% - 'gaussFit': 2-peak Gaussian fitting with global optimization
|
||||
%
|
||||
% 'binEnReference' numeric scalar (required for spectra)
|
||||
% Reference binding energy value (eV) for spectral calibration.
|
||||
% Typically the known BE of the primary peak (e.g., 285.0 eV for C1s C-H).
|
||||
% Example: 'binEnReference', 285.0
|
||||
%
|
||||
% 'kinEnReference' numeric scalar (default: 1)
|
||||
% Reference iteration index for imaging data. All kinetic energy shifts
|
||||
% are computed relative to this iteration. Reference iteration gets shift = 0.
|
||||
% Range: 1 to number of image columns.
|
||||
%
|
||||
% 'mode' char vector (default: 'findPeak')
|
||||
% Peak finding method for spectra:
|
||||
% - 'findPeak': uses findpeaks with smoothing and height/width constraints
|
||||
% - 'gaussFit': uses nappFitGaussXps with global optimization (slower, more accurate)
|
||||
%
|
||||
% 'minPeakWidth' numeric scalar (default: 0.1)
|
||||
% Minimum peak width (eV) for findPeak method. Peaks narrower than this
|
||||
% are rejected. Typical range: 0.05-0.5 eV depending on spectrometer resolution.
|
||||
%
|
||||
% 'minPeakHeight' numeric scalar (default: 0.1)
|
||||
% Minimum peak height (intensity) for findPeak method. Peaks below this
|
||||
% are rejected. Units match tableVariable data.
|
||||
%
|
||||
% 'movMeanWindow' numeric scalar (default: 1)
|
||||
% Moving mean window width (number of points) for smoothing before peak
|
||||
% detection in findPeak method. Reduces noise; typical range: 1-5 points.
|
||||
%
|
||||
% 'FitParameter' struct
|
||||
% Structure containing Gaussian fitting parameters for gaussFit mode.
|
||||
% Required fields:
|
||||
% - fitMode: 'position', 'width', 'unconstrained', etc.
|
||||
% - refPeak: peak number (1 or 2) whose position is used for shift calculation
|
||||
% Example: FitPar.fitMode = 'position'; FitPar.refPeak = 1;
|
||||
%
|
||||
% 'refPeak' numeric scalar (default: 1)
|
||||
% Peak number (1 or 2) whose position is used for binding energy shift
|
||||
% calculation in gaussFit mode. Peak 1 is typically the main photoelectron peak.
|
||||
%
|
||||
% 'showFigures' char vector (default: 'off')
|
||||
% Display fitting results:
|
||||
% - 'on': show Gaussian fit plot, peak table, and peak finding results
|
||||
% - 'off': suppress all figure output
|
||||
%
|
||||
% OUTPUT ARGUMENTS
|
||||
% ----------------
|
||||
% DataTable table
|
||||
% Updated data table with new columns:
|
||||
%
|
||||
% For imaging data (tableVariable = 'image' or 'imageRoh'):
|
||||
% - kineticEnergyShift: column vector of relative kinetic energy shifts (eV)
|
||||
% relative to kinEnReference iteration. Reference = 0.
|
||||
%
|
||||
% For spectral data (tableVariable = 'spectrum_*'):
|
||||
% - bindingEnergyShift: column vector of absolute binding energy shifts (eV)
|
||||
% relative to binEnReference. Typically small values (±0.5 eV range).
|
||||
%
|
||||
% For gaussFit mode on spectra, additional columns are added:
|
||||
% - peakPosition: [peak1, peak2] positions (eV) for each spectrum
|
||||
% - peakArea: [peak1, peak2] areas (eV·counts) for each spectrum
|
||||
% - peakFWHM: [peak1, peak2] full-width half-maximum (eV) for each spectrum
|
||||
% - peakBackground: linear background parameters [slope, intercept]
|
||||
%
|
||||
% NOTES
|
||||
% -----
|
||||
% - PREREQUISITE: X-ray photon energy (xRayEkin_eV) must be present in DataTable.
|
||||
% If empty, function returns with warning.
|
||||
% - For imaging: Gaussian fitting assumes 2 peaks in each iteration. Adjust
|
||||
% eKinStart/eKinEnd to isolate peak region if data is complex.
|
||||
% - For spectra: findPeak method is fast but less accurate (±0.1-0.2 eV typical).
|
||||
% gaussFit method is slower but more accurate (±0.05 eV typical).
|
||||
% - Peak detection may fail if signal-to-noise is poor. Check showFigures output
|
||||
% to diagnose fitting quality.
|
||||
% - For imageRoh variant: uses scientaEkinRoh_eV if available; falls back to
|
||||
% scientaEkin_eV if not populated.
|
||||
% - Binding energy shifts can be negative (peak at higher BE than reference) or
|
||||
% positive (peak at lower BE than reference).
|
||||
% - Figure windows are automatically closed after processing to prevent clutter.
|
||||
%
|
||||
% SEE ALSO
|
||||
% --------
|
||||
% nappCopyShift, nappBindingEnergyXps, nappFitGaussXps, nappAlignImages,
|
||||
% findpeaks, GaussFitOnePeak
|
||||
defaultKinEnReference = 1;
|
||||
defaultTableVariable = 'spectrum_countsPerSecond';
|
||||
expectedTableVariable = {'spectrum_counts','spectrum_countsPerSecond','spectrum_countsNorm','image','imageRoh'};
|
||||
defaultTableSelection = true(size(DataTable,1));
|
||||
defaultMinPeakWidth = 0.1;
|
||||
defaultMinPeakHeight = 0.1;
|
||||
defaultMovMeanWindow = 1;
|
||||
defaultRefPeak = 1;
|
||||
defaultShowFigures = 'off';
|
||||
expectedShowFigures = {'on','off'};
|
||||
defaultMode = 'findPeak';
|
||||
expectedMode = {'gaussFit','findPeak'};
|
||||
% defaultFitMode = 'widthPosition';
|
||||
% expectedFitMode = {'unconstrained','widthPosition','position','width','unconstrainedAndWidthPosition'};
|
||||
|
||||
|
||||
%%
|
||||
|
||||
% Parse input
|
||||
p = inputParser;
|
||||
addRequired(p,'DataTable',@istable);
|
||||
addOptional(p,'eKinStart',@isnumeric)
|
||||
addOptional(p,'eKinEnd',@isnumeric)
|
||||
addParameter(p,'FitParameter',@isstruct);
|
||||
addParameter(p,'binEnReference',@isnumeric)
|
||||
addParameter(p,'kinEnReference',defaultKinEnReference,@isnumeric)
|
||||
addParameter(p,'tableSelection',defaultTableSelection,@islogical);
|
||||
addParameter(p,'minPeakWidth',defaultMinPeakWidth,@isnumeric);
|
||||
addParameter(p,'minPeakHeight',defaultMinPeakHeight,@isnumeric);
|
||||
addParameter(p,'movMeanWindow',defaultMovMeanWindow ,@isnumeric);
|
||||
addParameter(p,'tableVariable',defaultTableVariable,...
|
||||
@(x) any(validatestring(x,expectedTableVariable)));
|
||||
addParameter(p,'showFigures',defaultShowFigures,...
|
||||
@(x) any(validatestring(x,expectedShowFigures)));
|
||||
addParameter(p,'mode',defaultMode, @(x) any(validatestring(x,expectedMode)));
|
||||
% addParameter(p,'fitMode',defaultFitMode, @(x) any(validatestring(x,expectedFitMode)));
|
||||
addParameter(p,'refPeak',defaultRefPeak,@isnumeric);
|
||||
parse(p,DataTable,varargin{:});
|
||||
|
||||
|
||||
% check input (all elements of input variable need to have non-zero size)
|
||||
if ~all(cell2mat(cellfun(@size,p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable),'UniformOutput',false)),'all')
|
||||
disp('The table variable appears to be empty. I quit now')
|
||||
error('The table variable appears to be empty.')
|
||||
end
|
||||
|
||||
% add empty variable to table
|
||||
if contains(p.Results.tableVariable,'image')
|
||||
existColumn = strcmp('kineticEnergyShift',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.kineticEnergyShift = cell(size(DataTable,1),1);
|
||||
end
|
||||
else
|
||||
existColumn = strcmp('bindingEnergyShift',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.bindingEnergyShift = NaN(size(DataTable,1),1);
|
||||
end
|
||||
end
|
||||
%Colors; 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;
|
||||
|
||||
% confirm that X-ray energy is set
|
||||
if any(isnan(cell2mat(p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV)))
|
||||
disp('Opps, X-ray energy is empty. Please add manually. Will stop now');
|
||||
return
|
||||
end
|
||||
if matches(p.Results.tableVariable,'image')
|
||||
% repeat this for all columns in input.
|
||||
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
|
||||
%%
|
||||
try
|
||||
if isnumeric(p.Results.eKinStart)
|
||||
start = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinStart,1);
|
||||
stop = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinEnd,1);
|
||||
else
|
||||
start = 1;
|
||||
stop = length(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii});
|
||||
end
|
||||
catch
|
||||
warning('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
disp('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
end
|
||||
|
||||
for ij = 1:size(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},2)
|
||||
[B,x1,y1] = GaussFitOnePeak([p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(start:stop)',...
|
||||
p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(start:stop,ij)]);
|
||||
tmp_1(ij) = p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}-B(3);
|
||||
% plot result
|
||||
if matches(p.Results.showFigures,'on')
|
||||
% define equations to be plotted
|
||||
Gauss = @(x,Hgt, Pos, Wdt) Hgt./(Wdt.*sqrt(pi/2))*exp(-2.*((x - Pos).^2/Wdt.^2));
|
||||
linear = @(x,slp,intrcpt) slp*x+intrcpt;
|
||||
figure( 'Name', 'Gauss Multi Fit ')
|
||||
tiledlayout(1,1)
|
||||
nexttile;
|
||||
%Define gauss peaks with hight, position, width from fitting results
|
||||
yData_pk1= Gauss(x1 ,B(1), B(3), B(2));
|
||||
yData_pk2= Gauss(x1 ,B(4), B(6), B(5));
|
||||
bkgrd = linear(x1,B(7),B(8));
|
||||
% plot data
|
||||
plot(x1,y1,'.')
|
||||
hold on
|
||||
% plot fit
|
||||
h = plot(x1,yData_pk1+yData_pk2+bkgrd);
|
||||
h.LineWidth=2;
|
||||
h.Color = PsiFarbe.dunkelblau;
|
||||
% plot each gauss peak
|
||||
h = plot(x1,yData_pk1+bkgrd);
|
||||
h.LineWidth=1.5;
|
||||
h.Color = PsiFarbe.gelb;
|
||||
h = plot(x1,yData_pk2+bkgrd);
|
||||
h.LineWidth=1.5;
|
||||
h.Color = PsiFarbe.gelb;
|
||||
|
||||
legend OFF
|
||||
xlabel('Kinetic Energy [eV]');
|
||||
ylabel('PE intensity [-]');
|
||||
grid on
|
||||
|
||||
% List results in table
|
||||
|
||||
disp('Results');
|
||||
disp(' ' );
|
||||
disp(cell2table({sprintf('%.1f',B(3)),...
|
||||
sprintf('%.1f',B(2)*sqrt(log(4))),...
|
||||
sprintf('%.1f',B(2)),...
|
||||
sprintf('%7.0f',B(1))},...
|
||||
'VariableNames',{'position';'FWHW';'width';'area'},...
|
||||
'RowNames',{'pk 1'}));
|
||||
end
|
||||
end
|
||||
%%
|
||||
|
||||
|
||||
DataTable(p.Results.tableSelection,:).kineticEnergyShift{ii} = tmp_1-tmp_1(p.Results.kinEnReference);
|
||||
clearvars tmp_1
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'Gauss Multi Fit ');
|
||||
close(fh)
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'GlobalSearch');
|
||||
close(fh)
|
||||
end
|
||||
elseif matches(p.Results.tableVariable,'imageRoh')
|
||||
% repeat this for all columns in input.
|
||||
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
|
||||
|
||||
%%
|
||||
try
|
||||
if isnumeric(p.Results.eKinStart) && isempty(DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii})
|
||||
start = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinStart,1);
|
||||
stop = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinEnd,1);
|
||||
elseif isnumeric(p.Results.eKinStart) && ~isempty(DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii})
|
||||
start = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii}...
|
||||
>= p.Results.eKinStart,1);
|
||||
stop = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii}...
|
||||
>= p.Results.eKinEnd,1);
|
||||
elseif isempty(DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii})
|
||||
start = 1;
|
||||
stop = length(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii});
|
||||
else
|
||||
start = 1;
|
||||
stop = length(DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii});
|
||||
end
|
||||
catch
|
||||
warning('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
disp('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
end
|
||||
for ij = 1:size(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},2)
|
||||
if isempty(DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii})
|
||||
[B,x1,y1] = GaussFitOnePeak([p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(start:stop)',...
|
||||
p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(start:stop,ij)]);
|
||||
else
|
||||
[B,x1,y1] = GaussFitOnePeak([DataTable(p.Results.tableSelection,:).scientaEkinRoh_eV{ii}(start:stop)',...
|
||||
p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(start:stop,ij)]);
|
||||
end
|
||||
|
||||
tmp_1(ij) = p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}-B(3);
|
||||
% plot result
|
||||
if matches(p.Results.showFigures,'on')
|
||||
% define equations to be plotted
|
||||
Gauss = @(x,Hgt, Pos, Wdt) Hgt./(Wdt.*sqrt(pi/2))*exp(-2.*((x - Pos).^2/Wdt.^2));
|
||||
linear = @(x,slp,intrcpt) slp*x+intrcpt;
|
||||
figure( 'Name', 'Gauss Multi Fit ')
|
||||
tiledlayout(1,1)
|
||||
nexttile;
|
||||
%Define gauss peaks with hight, position, width from fitting results
|
||||
yData_pk1= Gauss(x1 ,B(1), B(3), B(2));
|
||||
yData_pk2= Gauss(x1 ,B(4), B(6), B(5));
|
||||
bkgrd = linear(x1,B(7),B(8));
|
||||
% plot data
|
||||
plot(x1,y1,'.')
|
||||
hold on
|
||||
% plot fit
|
||||
h = plot(x1,yData_pk1+yData_pk2+bkgrd);
|
||||
h.LineWidth=2;
|
||||
h.Color = PsiFarbe.dunkelblau;
|
||||
% plot each gauss peak
|
||||
h = plot(x1,yData_pk1+bkgrd);
|
||||
h.LineWidth=1.5;
|
||||
h.Color = PsiFarbe.gelb;
|
||||
h = plot(x1,yData_pk2+bkgrd);
|
||||
h.LineWidth=1.5;
|
||||
h.Color = PsiFarbe.gelb;
|
||||
|
||||
legend OFF
|
||||
xlabel('Kinetic Energy [eV]');
|
||||
ylabel('PE intensity [-]');
|
||||
grid on
|
||||
|
||||
% List results in table
|
||||
|
||||
disp('Results');
|
||||
disp(' ' );
|
||||
disp(cell2table({sprintf('%.1f',B(3)),...
|
||||
sprintf('%.1f',B(2)*sqrt(log(4))),...
|
||||
sprintf('%.1f',B(2)),...
|
||||
sprintf('%7.0f',B(1))},...
|
||||
'VariableNames',{'position';'FWHW';'width';'area'},...
|
||||
'RowNames',{'pk 1'}));
|
||||
end
|
||||
end
|
||||
%%
|
||||
|
||||
DataTable(p.Results.tableSelection,:).kineticEnergyShift{ii} = tmp_1-tmp_1(p.Results.kinEnReference);
|
||||
clearvars tmp_1
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'Gauss Multi Fit ');
|
||||
close(fh)
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'GlobalSearch');
|
||||
close(fh)
|
||||
end
|
||||
else
|
||||
% repeat this for all columns in input.
|
||||
if matches(p.Results.mode,'findPeak')
|
||||
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
|
||||
|
||||
%%
|
||||
try
|
||||
if isnumeric(p.Results.eKinStart)
|
||||
start = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinStart,1);
|
||||
stop = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}...
|
||||
>= p.Results.eKinEnd,1);
|
||||
else
|
||||
start = 1;
|
||||
stop = length(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii});
|
||||
end
|
||||
catch
|
||||
warning('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
disp('There is something wrong with the eKinStart and/or eKinEnd parameters');
|
||||
end
|
||||
|
||||
if contains(p.Results.showFigures,'on')
|
||||
figure
|
||||
|
||||
findpeaks(movmean(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(start:stop),...
|
||||
p.Results.movMeanWindow),...
|
||||
p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(start:stop),...
|
||||
'MinPeakWidth',p.Results.minPeakWidth,'MinPeakHeight',p.Results.minPeakHeight)
|
||||
end
|
||||
[pks,locs]=findpeaks(movmean(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(start:stop),...
|
||||
p.Results.movMeanWindow),...
|
||||
p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(start:stop),...
|
||||
'MinPeakWidth',p.Results.minPeakWidth,'MinPeakHeight',p.Results.minPeakHeight);
|
||||
|
||||
%%
|
||||
|
||||
[~,I] = max(pks);
|
||||
tmp_1(ii,1) = p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}-locs(I)-p.Results.binEnReference;
|
||||
clearvars tmp_2
|
||||
end
|
||||
DataTable.bindingEnergyShift(p.Results.tableSelection) = tmp_1;
|
||||
|
||||
elseif matches(p.Results.mode,'gaussFit')
|
||||
tmpFitResults = nappFitGaussXps(DataTable,p.Results.FitParameter,p.Results.tableSelection,...
|
||||
'energyScale','kinetic','mode',p.Results.FitParameter.fitMode,'showFigures','on','tableVariable',p.Results.tableVariable);
|
||||
|
||||
DataTable.bindingEnergyShift(p.Results.tableSelection) = tmpFitResults(p.Results.tableSelection,:).xRayEkinRange_eV(:,1) - tmpFitResults(p.Results.tableSelection,:).peakPosition(:,p.Results.FitParameter.refPeak) - repmat(p.Results.binEnReference,size(DataTable.bindingEnergyShift(p.Results.tableSelection)));
|
||||
DataTable.peakPosition(p.Results.tableSelection,:) = tmpFitResults(p.Results.tableSelection,:).peakPosition;
|
||||
DataTable.peakArea(p.Results.tableSelection,:) = tmpFitResults(p.Results.tableSelection,:).peakArea;
|
||||
DataTable.peakFWHM(p.Results.tableSelection,:) = tmpFitResults(p.Results.tableSelection,:).peakFWHM;
|
||||
% added 6.2025 tbr
|
||||
DataTable.peakBackground(p.Results.tableSelection,:) = tmpFitResults(p.Results.tableSelection,:).peakBackground;
|
||||
%%
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
try
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'Gauss Multi Fit ');
|
||||
close(fh)
|
||||
catch
|
||||
end
|
||||
try
|
||||
fh = findobj( 'Type', 'Figure', 'Name', 'GlobalSearch');
|
||||
close(fh)
|
||||
catch
|
||||
end
|
||||
|
||||
end
|
||||
function [B,x1,y1] = GaussFitOnePeak(data1)
|
||||
|
||||
|
||||
[x1, y1] = prepareCurveData(data1(:,1),data1(:,2));
|
||||
|
||||
% make data slimmer so that background makes sense
|
||||
[~,I] = max(y1);
|
||||
tmp1 = find(x1>=x1(I)-8,1);
|
||||
tmp2 = find(x1>=x1(I)+5,1);
|
||||
if isempty(tmp2)
|
||||
y1 = y1(tmp1:end);
|
||||
x1 = x1(tmp1:end);
|
||||
else
|
||||
y1 = y1(tmp1:tmp2);
|
||||
x1 = x1(tmp1:tmp2);
|
||||
end
|
||||
[~,I] = max(y1);
|
||||
|
||||
% Define equation to fit data
|
||||
% I use the area Gauss equations here, which I took from the OriginPro
|
||||
% documentation. https://www.originlab.com/doc/Origin-Help/Gauss-FitFunc
|
||||
%
|
||||
% Gauss = area/(width*sqrt(pi/2)) * exp(-2*(x-position)^2/width^2)
|
||||
%
|
||||
% where FWHM = width * sqrt(ln(4))
|
||||
|
||||
% Data set 1:
|
||||
yfcn1 = @(b,x)...
|
||||
b(1)/(b(2)*sqrt(pi/2))*exp(-2.*((x(:,1) - b(3)).^2/b(2).^2)) ... % Gauss peak 1: C-H, Ac-
|
||||
+ b(4)/(b(5)*sqrt(pi/2))*exp(-2.*((x(:,1)- b(6)).^2/b(5).^2))... % Gauss peak 2: C-OOH, Ac-
|
||||
+b(7).*x(:,1)+b(8); % linear background
|
||||
|
||||
B0 = [1e5 1.5 x1(I) ...
|
||||
1e5 1.0 x1(I)-4 ...
|
||||
1 0];
|
||||
|
||||
|
||||
% lower bonds
|
||||
lb = [100 0.5 x1(I)-1. ...
|
||||
100 1.0 x1(I)-5 ...
|
||||
-Inf -Inf];
|
||||
|
||||
|
||||
% upper bond
|
||||
ub = [Inf 3 x1(I)+1 ...
|
||||
Inf 5 x1(I)-1 ...
|
||||
Inf Inf];
|
||||
|
||||
|
||||
% Fit equations: Gauss eq. 1 with data
|
||||
yfcn = @(b,x) yfcn1(b,x);
|
||||
% We find an optimal fit by minimising the residuals:
|
||||
fitfcnw = @(b) sum((y1 - yfcn(b,x1)).^2);
|
||||
|
||||
problem = createOptimProblem('fmincon', 'x0',B0, 'lb',lb,'ub',ub, 'objective',fitfcnw);
|
||||
gs = GlobalSearch('PlotFcns',@gsplotbestf);
|
||||
B = run(gs,problem);
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,313 @@
|
||||
function DataTable = nappScale(DataTable,varargin)
|
||||
%NAPPSCALE Normalize XPS and NEXAFS spectra.
|
||||
% The peak of the same region or of a different region with identical
|
||||
% SES file number (same sequence iteration) can be used for XPS data.
|
||||
% NEXAFS spectra can be normed to their area or to the intensity
|
||||
% at an energy range of choice.
|
||||
%
|
||||
% For XPS ('.ibw') data, NAPPSCALE divides spectra of one region
|
||||
% (regionOld, default is C1s) by the fitted peak area taken from a
|
||||
% spectrum of the same sequence but different region (regionNew, default
|
||||
% is O1s). The same sequence iteration is identified by the SES file
|
||||
% number and acquisition time. For NEXAFS ('.h5') data, NAPPSCALE
|
||||
% normalizes spectra to the mean intensity or to the integrated area over
|
||||
% a chosen photon-energy interval.
|
||||
%
|
||||
% DataTable = NAPPSCALE(DataTable) normalizes spectra in the input table
|
||||
% using default options and returns the updated table with
|
||||
% normalized spectra in the variable spectrum_countsNorm.
|
||||
%
|
||||
% DataTable = NAPPSCALE(DataTable, tableSelection) restricts
|
||||
% normalization to rows where tableSelection is true. The length
|
||||
% of tableSelection must match the height of DataTable.
|
||||
%
|
||||
% DataTable = NAPPSCALE(DataTable, tableSelection, energyStart, energyEnd)
|
||||
% additionally specifies an energy interval used to normalize NEXAFS
|
||||
% spectra ('.h5' data)
|
||||
%
|
||||
% DataTable = NAPPSCALE(..., Name, Value) customizes the source variable,
|
||||
% normalization mode, line region mapping, and peak selection via
|
||||
% Name-Value arguments (see below):
|
||||
%
|
||||
|
||||
%
|
||||
% Required input
|
||||
% --------------
|
||||
% DataTable
|
||||
% Table containing spectroscopy data and metadata.[web:2]
|
||||
% Depending on file type and options, the function uses:
|
||||
% - name : file name, used to distinguish 'ibw'
|
||||
% and 'h5' and to construct regionNew
|
||||
% file names (XPS)
|
||||
% - spectrum_counts : 1D spectrum (cell of numeric)
|
||||
% - spectrum_countsPerSecond
|
||||
% : 1D spectrum normalized to rate
|
||||
% - spectrum_countsNorm : (created/overwritten) normalized
|
||||
% spectrum
|
||||
% - peakArea : numeric vector of fitted peak areas
|
||||
% (XPS, one row per region)
|
||||
% - lastModifiedDatenum : datenum time stamp used to find the
|
||||
% matching regionNew spectrum
|
||||
% - xRayEkin_eV : photon energy axis (NEXAFS)
|
||||
%
|
||||
% Optional positional input
|
||||
% -------------------------
|
||||
% tableSelection
|
||||
% Logical vector selecting rows in DataTable to normalize.
|
||||
% Default: all rows true
|
||||
%
|
||||
% energyStart, energyEnd
|
||||
% Photon-energy limits (in eV) that define the normalization
|
||||
% interval for NEXAFS spectra ('.h5').
|
||||
% Type : numeric scalars
|
||||
% Use : required for 'intensity' or 'area' mode on '.h5' data.
|
||||
%
|
||||
% Name-Value arguments
|
||||
% --------------------
|
||||
% 'tableVariable'
|
||||
% Name of the spectral variable to be normalized.[web:10]
|
||||
% Type : char or string
|
||||
% Values : 'spectrum_countsPerSecond' (default)
|
||||
% 'spectrum_counts'
|
||||
% 'spectrum_countsNorm'
|
||||
%
|
||||
% 'regionOld'
|
||||
% XPS region name of the spectra to be normalized ('.ibw').
|
||||
% Used for file-name replacement when searching the reference
|
||||
% region
|
||||
% Type : char or string
|
||||
% Values : 'C1s' (default) | 'O1s' | 'N1s' | 'Cl2p'
|
||||
%
|
||||
% 'regionNew'
|
||||
% XPS region name that provides the reference peak area used for
|
||||
% normalization.
|
||||
% Type : char or string
|
||||
% Values : 'O1s' (default) | 'C1s' | 'N1s'
|
||||
%
|
||||
% 'peakNumber'
|
||||
% Index of the peak in the peakArea vector used as normalization
|
||||
% reference for XPS data.
|
||||
% Type : positive integer scalar
|
||||
% Default: 1
|
||||
%
|
||||
% 'mode'
|
||||
% Normalization mode.
|
||||
% Type : char or string
|
||||
% Values : 'area' (default)
|
||||
% 'intensity'
|
||||
%
|
||||
% Behaviour:
|
||||
% - For '.ibw' (XPS)
|
||||
% 'area' : divide selected spectra by the chosen peakArea
|
||||
% of the corresponding regionNew spectrum.
|
||||
% 'intensity' : currently not implemented (warning printed).
|
||||
% - For '.h5' (NEXAFS)
|
||||
% 'intensity' : divide spectra by the mean intensity over
|
||||
% [energyStart, energyEnd].
|
||||
% 'area' : divide spectra by the integrated area under
|
||||
% the curve over [energyStart, energyEnd]
|
||||
% (trapz).
|
||||
%
|
||||
% Output
|
||||
% ------
|
||||
% DataTable
|
||||
% Updated input table with normalized spectra and log variables.
|
||||
% The function ensures the following variables:
|
||||
%
|
||||
% spectrum_countsNorm
|
||||
% Cell array containing the normalized spectrum for each
|
||||
% processed row. Existing values are overwritten.
|
||||
%
|
||||
% logScaleRange
|
||||
% Numeric matrix with two columns. For '.h5' rows, stores the
|
||||
% [energyStart energyEnd] interval used for normalization.
|
||||
%
|
||||
% logScaleMode
|
||||
% Cell array of character vectors with the mode ('area' or
|
||||
% 'intensity') used for each normalized row.
|
||||
%
|
||||
% logScaleDate
|
||||
% Cell array of character vectors containing the time stamp of
|
||||
% the normalization (datestr(now)).
|
||||
%
|
||||
% Notes
|
||||
% -----
|
||||
% - All selected entries in the chosen tableVariable must contain
|
||||
% non-empty spectra; otherwise, the function throws an error.
|
||||
% - For XPS data, regionOld and the tableSelection filter must refer to
|
||||
% the same region; regionNew is found by name replacement and by
|
||||
% matching date and time to locate the correct reference spectrum.
|
||||
% - For NEXAFS data, the function supports both increasing and decreasing
|
||||
% photon-energy axes when locating the normalization interval.
|
||||
|
||||
|
||||
|
||||
% Define conditions and defaults of input
|
||||
defaultTableVariable = 'spectrum_countsPerSecond';
|
||||
expectedTableVariable = {'spectrum_counts','spectrum_countsPerSecond','spectrum_countsNorm'};
|
||||
defaultMode = 'area';
|
||||
expectedMode = {'intensity','area'};
|
||||
defaultRegionOld = 'C1s';
|
||||
expectedRegionOld = {'O1s','C1s','N1s','Cl2p'};
|
||||
defaultRegionNew = 'O1s';
|
||||
expectedRegionNew = {'O1s','C1s','N1s'};
|
||||
defaultPeakNumber = 1;
|
||||
defaultTableSelection = true(size(DataTable,1));
|
||||
|
||||
|
||||
% Parse input
|
||||
p = inputParser;
|
||||
addRequired(p,'DataTable',@istable);
|
||||
addOptional(p,'tableSelection',defaultTableSelection,@islogical);
|
||||
addOptional(p,'energyStart',@isnumeric)
|
||||
addOptional(p,'energyEnd',@isnumeric)
|
||||
addParameter(p,'tableVariable',defaultTableVariable,...
|
||||
@(x) any(validatestring(x,expectedTableVariable)));
|
||||
addParameter(p,'regionOld',defaultRegionOld,...
|
||||
@(x) any(validatestring(x,expectedRegionOld)));
|
||||
addParameter(p,'regionNew',defaultRegionNew,...
|
||||
@(x) any(validatestring(x,expectedRegionNew)));
|
||||
addParameter(p,'peakNumber',defaultPeakNumber);
|
||||
addParameter(p,'mode',defaultMode,...
|
||||
@(x) any(validatestring(x,expectedMode)));
|
||||
parse(p,DataTable,varargin{:});
|
||||
|
||||
% add empty variable to table
|
||||
existColumn = strcmp('spectrum_countsNorm',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.spectrum_countsNorm = cell(size(DataTable,1),1);
|
||||
end
|
||||
|
||||
existColumn = strcmp('logScaleRange',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.logBackgroundRange = NaN(size(DataTable,1),2);
|
||||
end
|
||||
existColumn = strcmp('logScaleMode',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.logBackgroundMode = cellstr(repmat(' ',size(p.Results.DataTable,1),1));
|
||||
end
|
||||
existColumn = strcmp('logScaleDate',DataTable.Properties.VariableNames);
|
||||
if ~any(existColumn)
|
||||
DataTable.logBackgroundDate = cellstr(repmat(' ',size(p.Results.DataTable,1),1));
|
||||
end
|
||||
|
||||
% check input (all elements of input variable need to have non-zero size)
|
||||
if ~all(cell2mat(cellfun(@size,p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable),'UniformOutput',false)),'all')
|
||||
disp('The table variable appears to be empty. I quit now')
|
||||
error('The table variable appears to be empty.')
|
||||
end
|
||||
|
||||
|
||||
% repeat this for all columns in input.
|
||||
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
|
||||
if endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'ibw') || endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'xy')
|
||||
% get name and date of input file and replace old region with new
|
||||
% region. This will be the files where the peak area is taken from.
|
||||
tmpName = replace(p.Results.DataTable(p.Results.tableSelection,:).name{ii}, ...
|
||||
p.Results.regionOld,p.Results.regionNew);
|
||||
tmpDate = p.Results.DataTable(p.Results.tableSelection,:).lastModifiedDatenum(ii);
|
||||
% select spectra based on the new names and dates of original file
|
||||
tmpDataSelection = contains(p.Results.DataTable.name,tmpName)...
|
||||
& ismember(year(p.Results.DataTable.lastModifiedDatenum),year(tmpDate))...
|
||||
& ismember(month(p.Results.DataTable.lastModifiedDatenum),month(tmpDate))...
|
||||
& ismember(day(p.Results.DataTable.lastModifiedDatenum),day(tmpDate))...
|
||||
& ismember(hour(p.Results.DataTable.lastModifiedDatenum),hour(tmpDate))...
|
||||
& ismember(minute(p.Results.DataTable.lastModifiedDatenum),minute(tmpDate));
|
||||
% check peak area
|
||||
existColumn = strcmp('peakArea',DataTable.Properties.VariableNames);
|
||||
if any(existColumn)
|
||||
if isnan(DataTable( tmpDataSelection,:).peakArea(p.Results.peakNumber))
|
||||
disp('The peak area appears to be empty. I quit now')
|
||||
disp(DataTable( tmpDataSelection,:).name{ii})
|
||||
error('The peak area appears to be empty.')
|
||||
end
|
||||
else
|
||||
disp('The data table appear to have not peak area column. I quit now')
|
||||
error('No peak area column')
|
||||
end
|
||||
% do the norming
|
||||
if contains(p.Results.mode,'area')
|
||||
DataTable(p.Results.tableSelection,:).spectrum_countsNorm{ii} = ...
|
||||
DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}...
|
||||
./ DataTable( tmpDataSelection,:).peakArea(p.Results.peakNumber);
|
||||
elseif contains(p.Results.mode,'intensity')
|
||||
disp('Intensity mode not yet programmed for XPS data')
|
||||
end
|
||||
|
||||
elseif endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'h5')
|
||||
% if ~exist(p.Results.energyStart) | ~exist(p.Results.energyEnd)
|
||||
% disp('The energy region used for normalisation appears to be empty. I quit now')
|
||||
% error('The energy region used to normalise appears to be empty.')
|
||||
% end
|
||||
if contains(p.Results.mode,'intensity')
|
||||
if p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(1)...
|
||||
< p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(end)
|
||||
% prepare Data
|
||||
[xData, yData] = prepareCurveData(p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}...
|
||||
, p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii});
|
||||
% MEAN
|
||||
b_1 = find(xData >= p.Results.energyStart,1);
|
||||
b_2 = find(xData >= p.Results.energyEnd,1);
|
||||
|
||||
b_mean = mean(yData(b_1:b_2));
|
||||
elseif p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(1)...
|
||||
> p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(end)
|
||||
% prepare Data
|
||||
[xData, yData] = prepareCurveData( p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}...
|
||||
, p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii});
|
||||
% MEAN
|
||||
b_1 = find(xData <= p.Results.energyStart,1);
|
||||
b_2 = find(xData <= p.Results.energyEnd,1);
|
||||
|
||||
b_mean = mean(yData(b_2:b_1));
|
||||
else
|
||||
disp('Warning: Issue with NEXAFS_col_scaling')
|
||||
end
|
||||
|
||||
tmp_3(:,1) = p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}...
|
||||
./b_mean;
|
||||
|
||||
DataTable(p.Results.tableSelection,:).spectrum_countsNorm{ii} = tmp_3;
|
||||
clearvars tmp_3
|
||||
elseif contains(p.Results.mode,'area')
|
||||
if p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(1)...
|
||||
< p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(end)
|
||||
% prepare Data
|
||||
[xData, yData] = prepareCurveData( p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}...
|
||||
, p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii});
|
||||
% Area
|
||||
b_1 = find(xData >= p.Results.energyStart,1);
|
||||
b_2 = find(xData >= p.Results.energyEnd,1);
|
||||
|
||||
b_area = trapz(xData(b_1:b_2),yData(b_1:b_2));
|
||||
elseif p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(1)...
|
||||
> p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(end)
|
||||
% prepare Data
|
||||
[xData, yData] = prepareCurveData( p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}...
|
||||
, p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii});
|
||||
% MEAN
|
||||
b_1 = find(xData <= p.Results.energyStart,1);
|
||||
b_2 = find(xData <= p.Results.energyEnd,1);
|
||||
|
||||
b_area = trapz(xData(b_2:b_1),yData(b_2:b_1));
|
||||
else
|
||||
disp('Warning: Issue with NEXAFS_col_scaling')
|
||||
end
|
||||
|
||||
tmp_3(:,1) = p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}...
|
||||
./b_area;
|
||||
|
||||
DataTable(p.Results.tableSelection,:).spectrum_countsNorm{ii} = tmp_3;
|
||||
clearvars tmp_3
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
% write log to table
|
||||
DataTable.logScaleMode(p.Results.tableSelection,:) = cellstr(repmat(p.Results.mode,size(p.Results.DataTable(p.Results.tableSelection,:),1),1));
|
||||
if endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'h5')
|
||||
DataTable.logScaleRange(p.Results.tableSelection,:) = repmat([p.Results.energyStart p.Results.energyEnd],size(p.Results.DataTable(p.Results.tableSelection,:),1),1);
|
||||
end
|
||||
DataTable.logScaleDate(p.Results.tableSelection,:) = cellstr(repmat(datestr(now),size(p.Results.DataTable(p.Results.tableSelection,:),1),1));
|
||||
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
function saveFilename = nappSaveDataTable(DataTable, options)
|
||||
% NAPPSAVEDATATABLE Automatically saves the data table with a dynamic filename.
|
||||
%
|
||||
% NAPPSAVEDATATABLE(DataTable) saves the provided table into a .mat file.
|
||||
% The filename is automatically generated based on the script or Live
|
||||
% Script that called this function (e.g., "myScript_FileList.mat").
|
||||
%
|
||||
% The variable inside the .mat file will keep the exact same name it had
|
||||
% in your workspace (e.g., if you pass 'FileList', it saves as 'FileList').
|
||||
%
|
||||
% Name-Value arguments:
|
||||
% suffix - Text to append to the script name (default: nothing)
|
||||
|
||||
arguments
|
||||
DataTable table
|
||||
|
||||
% Name-Value arguments
|
||||
options.suffix char = ""
|
||||
end
|
||||
|
||||
currentScriptName = '';
|
||||
|
||||
% 1. Determine the name of the script that CALLED this function
|
||||
stackInfo = dbstack;
|
||||
if length(stackInfo) > 1
|
||||
% stackInfo(1) is nappSaveDataTable, stackInfo(2) is the caller
|
||||
[~, currentScriptName, ~] = fileparts(stackInfo(2).file);
|
||||
|
||||
% INTERCEPT LIVE EDITOR: If it's a hidden temporary evaluation file,
|
||||
% clear the name so we force it to use the fallback below.
|
||||
if contains(currentScriptName, 'LiveEditorEvaluationHelper')
|
||||
currentScriptName = '';
|
||||
end
|
||||
end
|
||||
|
||||
% 2. Fallback if run from the command window or intercepted from the Live Editor
|
||||
if isempty(currentScriptName)
|
||||
try
|
||||
% Ask the MATLAB editor for the actual name of the currently open tab
|
||||
[~, currentScriptName, ~] = fileparts(matlab.desktop.editor.getActiveFilename);
|
||||
catch
|
||||
currentScriptName = 'ManualRun';
|
||||
end
|
||||
end
|
||||
|
||||
% Ensure we have a valid string (just in case the editor is closed)
|
||||
if isempty(currentScriptName)
|
||||
currentScriptName = 'WorkspaceData';
|
||||
end
|
||||
|
||||
% 3. Generate the final filename
|
||||
saveFilename = sprintf('%s%s.mat', currentScriptName, options.suffix);
|
||||
|
||||
% 4. Determine the original variable name passed by the user (e.g., "FileList")
|
||||
originalVarName = inputname(1);
|
||||
|
||||
% If it has no workspace name, default to 'DataTable'
|
||||
if isempty(originalVarName)
|
||||
originalVarName = 'DataTable';
|
||||
end
|
||||
|
||||
% 5. Create a local variable with that exact name so the .mat file stores it correctly
|
||||
eval(sprintf('%s = DataTable;', originalVarName));
|
||||
|
||||
% 6. Save the specific variable to the file
|
||||
save(saveFilename, originalVarName);
|
||||
|
||||
% 7. Print confirmation
|
||||
fprintf('✅ Successfully saved %s to: %s\n', originalVarName, saveFilename);
|
||||
end
|
||||
Reference in new issue
Block a user