small bug fixes. CopyShift now works with any filename, not just numbers as before
This commit is contained in:
1 parent
d72e8b4ddc
commit
43ac9c00bb
2 files changed
+126
-112
No files matched your search
+109
-104
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user