Files

145 lines
5.7 KiB
Matlab

function DataTable = nappCopyShift(DataTable, processSelection, refSelection, options)
% NAPPCOPYSHIFT Copy binding energy shift from reference spectra to target spectra.
%
% DataTable = NAPPCOPYSHIFT(DataTable, processSelection, refSelection)
% copies the calculated binding energy shift from a pool of reference spectra
% (where refSelection is true) to the target spectra (where processSelection
% is true) using a matching strategy.
%
% NOTE: This function requires that nappDetermineShift has already been
% run on the reference spectra to populate the 'bindingEnergyShift' column.
%
% Name-Value arguments:
% matchStrategy - 'earlier' (default), 'after', 'spectrumNumber'
% timeVariable - Target time column ('lastModifiedDatenum' by default)
% spectrumNumberPrefixLength - Number of leading characters used for name
% matching in 'spectrumNumber' strategy (default 7)
arguments
DataTable table
processSelection logical {mustBeVector}
refSelection logical {mustBeVector}
% Name-Value arguments
options.matchStrategy char {mustBeMember(options.matchStrategy, ...
{'earlier', 'after', 'spectrumNumber'})} = 'earlier'
options.timeVariable char {mustBeMember(options.timeVariable, ...
{'lastModifiedDatenum', 'creationDate'})} = 'lastModifiedDatenum'
options.spectrumNumberPrefixLength double {mustBePositive, mustBeInteger} = 7
end
numRowsTable = height(DataTable);
% Validate length of the logical arrays
if length(processSelection) ~= numRowsTable || length(refSelection) ~= numRowsTable
error('Logical selection arrays must have the exact same length as the DataTable height (%d).', numRowsTable);
end
% Ensure shift data exists
if ~ismember('bindingEnergyShift', DataTable.Properties.VariableNames)
error('The column "bindingEnergyShift" does not exist. Please run nappDetermineShift on your reference spectra first.');
end
% Initialize source logging column if it doesn't exist
if ~ismember('lognappCopyShiftSource', DataTable.Properties.VariableNames)
DataTable.lognappCopyShiftSource = cell(numRowsTable, 1);
end
%% Processing Loop
processIdx = find(processSelection);
refIdx = find(refSelection);
for i = 1:length(processIdx)
currRow = processIdx(i);
currName = DataTable.name{currRow};
% Safely extract time (handles both single numbers and 1x6 datevecs)
currTime = datenum(DataTable.(options.timeVariable)(currRow, :));
refTimes = datenum(DataTable.(options.timeVariable)(refIdx, :));
bestRefRow = NaN;
% Internal Matching Engine
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
case 'spectrumNumber'
% Use the first N characters of the name as the matching prefix
prefixLen = options.spectrumNumberPrefixLength;
if length(currName) >= prefixLen
searchPattern = currName(1:prefixLen);
validRefs = refIdx(startsWith(DataTable.name(refIdx), searchPattern));
if ~isempty(validRefs)
bestRefRow = validRefs(1);
end
else
warning('Row %d: Name "%s" is shorter than the requested prefix length (%d). Skipping.', ...
currRow, currName, prefixLen);
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 shift from reference row
referenceShift = DataTable.bindingEnergyShift(bestRefRow);
if isnan(referenceShift)
warning('Row %d: Matched reference row %d (%s) does not have a valid calculated shift. Skipping.', ...
currRow, bestRefRow, DataTable.name{bestRefRow});
continue;
end
% Copy the shift and log the exact name of the reference used
DataTable.bindingEnergyShift(currRow) = referenceShift;
DataTable.lognappCopyShiftSource{currRow} = DataTable.name{bestRefRow};
% 1. Dynamically get this function's name
thisFuncName = mfilename;
colDate = sprintf('log%sDate', thisFuncName);
colCaller = sprintf('log%sCaller', thisFuncName);
% 2. Probe for column existence, create empty string arrays if missing
if ~ismember(colDate, DataTable.Properties.VariableNames)
DataTable.(colDate) = repmat("", numRowsTable, 1);
end
if ~ismember(colCaller, DataTable.Properties.VariableNames)
DataTable.(colCaller) = repmat("", numRowsTable, 1);
end
% 3. Get current time as a string
currentTimeStr = string(datetime('now', 'Format', 'yyyy-MM-dd HH:mm:ss'));
% 4. Determine the calling script using the call stack
stackInfo = dbstack;
if length(stackInfo) > 1
callerScript = string(stackInfo(2).file); % The script that called this function
else
callerScript = " "; % Called directly from the command window
end
% 5. Write the log data only to the rows being processed
DataTable.(colDate)(processIdx) = currentTimeStr;
DataTable.(colCaller)(processIdx) = callerScript;
% -------------------------------------------------------------------------
end
end