Files
nappMATLAB-toolbox/beamtime/nappCopyShift.m
T

226 lines
10 KiB
Matlab

function DataTable = nappCopyShift(DataTable,varargin)
%NAPPCOPYSHIFT Copy energy shift from one XPS region to spectra of the same sequence but other regions.
% This function transfers energy shift calibration data (kinetic or binding
% energy) from a reference region to spectra of the same sequence iteration in a different
% region. It matches spectra based on filename and acquisition timestamp,
% enabling consistent energy axis calibration across XPS
% measurements of the same sequence (e.g., copying O1s shift to C1s spectra acquired simultaneously).
%
% This approach ensures that energy calibrations determined from a
% reference region (typically C1s or O1s) are consistently applied to
% other regions in the same sequence. Works with both spectral data in
% binding and in kinetic energy scale and with images - that is can be
% used to align all sweeps in one image. Multiple destination rows can
% receive the same shift value if acquired simultaneously (one-to-many
% copying).
%
% SYNTAX
% ------
% DataTable = NAPPCOPYSHIFT(DataTable)
%
% DataTable = NAPPCOPYSHIFT(DataTable, tableSelection, 'Name', Value)
%
% EXAMPLES
% --------
% % Copy O1s kinetic energy shift to C1s spectra
% DataTable = nappCopyShift(DataTable, ...
% 'regionOld', 'O1s', ...
% 'regionNew', 'C1s', ...
% 'energyScale', 'kinetic');
%
% % Copy binding energy shift from N1s to Cl2p on selected rows
% selection = contains(DataTable.name, 'polymer_sample');
% DataTable = nappCopyShift(DataTable, selection, ...
% 'regionOld', 'N1s', ...
% 'regionNew', 'Cl2p', ...
% 'energyScale', 'binding');
%
% % Copy C1s calibration (binding energy) to all matching O1s spectra
% DataTable = nappCopyShift(DataTable, ...
% 'regionOld', 'C1s', ...
% 'regionNew', 'O1s');
%
% INPUT ARGUMENTS
% ---------------
% DataTable table
% Data table containing XPS spectra with energy shift calibration data
% and time stamps. Ideally the napp standard format, but must at least contain:
% - name field: spectrum identifiers with region designation (e.g.,
% 'sample_C1s', 'sample_O1s')
% - lastModifiedDatenum field: acquisition timestamps (MATLAB datenum format)
% - kineticEnergyShift or bindingEnergyShift field: calibration shift values
% (created by nappDetermineShiftXps)
%
% NAME-VALUE PAIR ARGUMENTS (optional)
% ------------------------------------
% 'tableSelection' logical array (default: all rows)
% Logical index to filter which rows in DataTable contain the source
% shift data (regionOld spectra). Only spectra in this selection will
% be processed as source regions. Their shifts will be copied to matching
% spectra in regionNew (which may or may not be in tableSelection).
% Example: contains(DataTable.name, 'C1s') to use only C1s as source
%
% 'regionOld' char vector (default: 'O1s')
% Source region containing the energy shift calibration data.
% Must be one of:
% - 'O1s'
% - 'C1s'
% - 'N1s'
%
% 'regionNew' char vector (default: 'C1s')
% Destination region to which shift data will be copied.
% Must be one of:
% - 'O1s'
% - 'C1s'
% - 'N1s'
% - 'Cl2p'
%
% 'energyScale' char vector (default: 'kinetic')
% Energy scale type for shift calibration:
% - 'kinetic': copies kineticEnergyShift column (used for image analysis)
% - 'binding': copies bindingEnergyShift column (used for spectral analysis)
%
% OUTPUT ARGUMENTS
% ----------------
% DataTable table
% Updated data table with energy shift values copied to destination region
% spectra. The appropriate column is populated:
% - kineticEnergyShift (if energyScale = 'kinetic')
% - bindingEnergyShift (if energyScale = 'binding')
%
% Shift values are matched and copied for all spectra in the destination
% region (regionNew) that share the same acquisition timestamp as spectra
% in the source region (regionOld, filtered by tableSelection).
%
% MATCHING ALGORITHM
% ------------------
% The function matches source spectra (regionOld) to destination spectra
% (regionNew) using:
% 1. Filename: region name is replaced (O1s → C1s, etc.)
% 2. Acquisition timestamp: year, month, day, hour, and minute must all match
%
% This ensures that shift calibrations from simultaneously acquired reference
% regions are correctly applied to their complementary spectra. An error is
% displayed if a matching spectrum cannot be found.
%
% NOTES
% -----
% - PREREQUISITE: Run nappDetermineShiftXps on the source region (regionOld)
% BEFORE running this function. This populates the shift column in DataTable.
% - Timestamp matching is strict: year, month, day, hour, and minute must all
% match exactly. Second-level precision is not used.
% - If regionOld and regionNew are the same, no copy operation is performed.
% - Timestamps with mismatches indicate possible data organization issues and
% trigger a diagnostic message.
% - The function operates on selected rows only when tableSelection is provided.
% Rows outside the selection are not processed as source regions.
%
% WORKFLOW EXAMPLE
% ----------------
% % Typical usage sequence for multi-region XPS dataset:
% % 1. Determine shift for reference region (C1s)
% DataTable = nappDetermineShiftXps(DataTable, ...
% contains(DataTable.name, 'C1s'), ...
% 'energyScale', 'binding');
%
% % 2. Copy C1s shift to complementary regions
% DataTable = nappCopyShift(DataTable, ...
% contains(DataTable.name, 'C1s'), ...
% 'regionOld', 'C1s', ...
% 'regionNew', 'O1s', ...
% 'energyScale', 'binding');
%
% DataTable = nappCopyShift(DataTable, ...
% contains(DataTable.name, 'C1s'), ...
% 'regionOld', 'C1s', ...
% 'regionNew', 'N1s', ...
% 'energyScale', 'binding');
%
% SEE ALSO
% --------
% nappDetermineShiftXps, nappBindingEnergyXps, nappAlignImages,
% nappFitGaussXps, datenum
% Define conditions and defaults of input
% Old region must be also selected in tableSelection! it is kind like
% double.
defaultRegionOld = 'O1s';
expectedRegionOld = {'O1s','C1s','N1s'};
defaultRegionNew = 'C1s';
expectedRegionNew = {'O1s','C1s','N1s','Cl2p'};
defaultTableSelection = true(size(DataTable,1));
defaultEnergyScale = 'kinetic';
expectedEnergyScale = {'kinetic','binding'};
% Parse input
p = inputParser;
addRequired(p,'DataTable',@istable);
addOptional(p,'tableSelection',defaultTableSelection,@islogical);
addParameter(p,'regionOld',defaultRegionOld,...
@(x) any(validatestring(x,expectedRegionOld)));
addParameter(p,'regionNew',defaultRegionNew,...
@(x) any(validatestring(x,expectedRegionNew)));
addParameter(p,'energyScale',defaultEnergyScale,...
@(x) any(validatestring(x,expectedEnergyScale)));
parse(p,DataTable,varargin{:});
% The function operates as follows:
% 1. For each source spectrum/image (regionOld) in tableSelection:
% - Extract the energy shift value (kinetic or binding scale)
% - Extract the acquisition timestamp
% 2. Generate search criteria by replacing the region name in the source
% filename (e.g., "sample_O1s" becomes "sample_C1s")
% 3. Search entire DataTable for destination spectra/images (regionNew) that:
% - Have the transformed filename
% - Match the acquisition timestamp exactly (year, month, day, hour, minute)
% 4. Copy the source energy shift value to all matching destination rows
if contains(p.Results.energyScale,'kinetic')
% repeat this for all columns in input.
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
% get name and date
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
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));
if ~any(tmpDataSelection) == 1
disp('There is something wrong with the data time info')
disp(tmpName)
end
% replace the region in the name and copy the data
DataTable.kineticEnergyShift(tmpDataSelection,:) = p.Results.DataTable(p.Results.tableSelection,:).kineticEnergyShift(ii);
end
elseif contains(p.Results.energyScale,'binding')
% repeat this for all columns in input.
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
% get name and date
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
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));
if ~any(tmpDataSelection) == 1
disp('There is something wrong with the data time info')
disp(tmpName)
end
% replace the region in the name and copy the data
DataTable.bindingEnergyShift(tmpDataSelection,:) = p.Results.DataTable(p.Results.tableSelection,:).bindingEnergyShift(ii);
end
end
end