small bug fixes. CopyShift now works with any filename, not just numbers as before

This commit is contained in:
bartels-rausch committed 2026-05-09 07:31:44 +02:00
1 parent d72e8b4ddc
commit 43ac9c00bb
2 files changed
+126 -112

No files matched your search

+109 -104
View File
@@ -1,118 +1,123 @@
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
% 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
% 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'
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);
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)
timeDiffs = currTime - refTimes(refTimes <= currTime);
[~, minIdx] = min(timeDiffs);
bestRefRow = validRefs(minIdx);
bestRefRow = validRefs(1);
end
case 'after'
validRefs = refIdx(refTimes >= currTime);
if ~isempty(validRefs)
timeDiffs = refTimes(refTimes >= currTime) - currTime;
[~, minIdx] = min(timeDiffs);
bestRefRow = validRefs(minIdx);
end
case 'spectrumNumber'
% Extract the 4-digit number at the start of the name (e.g., '0033')
specNumStr = regexp(currName, '^\d{4}', 'match', 'once');
if ~isempty(specNumStr)
searchPattern = [specNumStr, '_'];
validRefs = refIdx(startsWith(DataTable.name(refIdx), searchPattern));
if ~isempty(validRefs)
bestRefRow = validRefs(1);
end
else
warning('Row %d: Could not extract a 4-digit spectrum number from name "%s".', currRow, currName);
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;
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);
@@ -120,10 +125,10 @@ function DataTable = nappCopyShift(DataTable, processSelection, refSelection, op
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
@@ -131,10 +136,10 @@ function DataTable = nappCopyShift(DataTable, processSelection, refSelection, op
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
end
+17 -8
View File
@@ -1,4 +1,4 @@
function DataTable = nappDetermineShift(DataTable, processSelection, options)
function DataTable = nappDetermineShiftXps(DataTable, processSelection, options)
% NAPPDETERMINESHIFT Calculate binding energy axis shifts based on a spectrum's own fitted peak.
%
% DataTable = NAPPDETERMINESHIFT(DataTable, processSelection) calculates
@@ -6,7 +6,7 @@ function DataTable = nappDetermineShift(DataTable, processSelection, options)
% on their own fitted peak position (internal reference).
%
% NOTE: This function requires that peak fitting (e.g., nappFitGaussXps)
% has already been performed so that the 'peakPos' column exists.
% has already been performed so that the 'peakPosition' column exists.
%
% Name-Value arguments:
% referenceBindingEnergy - The theoretical energy to align to (e.g., 285.0 for C1s)
@@ -17,7 +17,7 @@ function DataTable = nappDetermineShift(DataTable, processSelection, options)
processSelection logical {mustBeVector}
% Name-Value arguments
options.referenceBindingEnergy (1,1) double = 285.0 % Default to C1s binding energy
options.referenceBindingEnergy (1,1) double = 284.8 % Default to C1s binding energy
options.peakNumber (1,1) double {mustBeInteger, mustBePositive} = 1
end
@@ -29,8 +29,8 @@ function DataTable = nappDetermineShift(DataTable, processSelection, options)
end
% Ensure peak positions exist (Fit must be run first!)
if ~ismember('peakPos', DataTable.Properties.VariableNames)
error('The column "peakPos" does not exist. Please run nappFitGaussXps before calculating shifts.');
if ~ismember('peakPosition', DataTable.Properties.VariableNames)
error('The column "peakPosition" does not exist. Please run nappFitGaussXps before calculating shifts.');
end
% Initialize new columns to store the shift data if they don't exist
@@ -49,7 +49,7 @@ function DataTable = nappDetermineShift(DataTable, processSelection, options)
currName = DataTable.name{currRow};
% Extract the peak position
refPositions = DataTable.peakPos(currRow, :);
refPositions = DataTable.peakPosition(currRow, :);
if size(refPositions, 2) < options.peakNumber || isnan(refPositions(options.peakNumber))
warning('Row %d (%s): peakNumber %d does not contain a valid fitted position. Skipping.', ...
currRow, currName, options.peakNumber);
@@ -58,10 +58,19 @@ function DataTable = nappDetermineShift(DataTable, processSelection, options)
% Calculate the shift: Shift = Reference Binding Energy - Measured Energy
measuredEnergy = refPositions(options.peakNumber);
calculatedShift = options.referenceBindingEnergy - measuredEnergy;
% Validate that xRayEkin_eV exists and contains a valid scalar for this row
if ~ismember('xRayEkin_eV', DataTable.Properties.VariableNames)
error('The column "xRayEkin_eV" does not exist in DataTable. Cannot compute binding energy shift.');
end
xRayVal = DataTable.xRayEkin_eV{currRow};
if isempty(xRayVal) || ~(isnumeric(xRayVal) && isscalar(xRayVal)) || isnan(xRayVal)
error('Row %d (%s): xRayEkin_eV is empty, non-scalar, or NaN. Aborting shift calculation.', currRow, currName);
end
calculatedShift = xRayVal - options.referenceBindingEnergy - measuredEnergy;
% Log the shift and its source
DataTable.bindingEnergyShift(currRow) = calculatedShift;
DataTable.energyShiftSource{currRow} = 'self';
DataTable.energyShiftSource{currRow} = num2str(options.referenceBindingEnergy);
end
end