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