140 lines
5.6 KiB
Matlab
140 lines
5.6 KiB
Matlab
function DataTable = nappNorm(DataTable, processSelection, refSelection, options)
|
|
% NAPPNORM Normalize XPS spectra based on provided reference selection or self-maximum.
|
|
%
|
|
% DataTable = NAPPNORM(DataTable, processSelection, refSelection) normalizes
|
|
% the spectra where processSelection is true, dividing them by the peak area
|
|
% of matched spectra from the pool where refSelection is true. Both must
|
|
% be logical arrays matching the height of the DataTable.
|
|
%
|
|
% DataTable = NAPPNORM(DataTable, processSelection) scales each spectrum
|
|
% selected by the logical array by its own maximum value.
|
|
%
|
|
% Name-Value arguments:
|
|
% matchStrategy - 'earlier', 'after', 'internalMax'
|
|
% tableVariable - Target column ('spectrum_countsPerSecond' by default)
|
|
% timeVariable - Target time column ('lastModifiedDatenum' by default)
|
|
% peakNumber - Which peak area to use if multiple exist (1 by default)
|
|
|
|
arguments
|
|
DataTable table
|
|
processSelection logical {mustBeVector}
|
|
|
|
% refSelection is optional. If omitted, it defaults to empty.
|
|
refSelection logical {mustBeVector} = false(0,1);
|
|
|
|
% Name-Value arguments
|
|
options.matchStrategy char {mustBeMember(options.matchStrategy, ...
|
|
{'earlier', 'after', 'internalMax'})} = 'earlier'
|
|
options.tableVariable char {mustBeMember(options.tableVariable, ...
|
|
{'spectrum_counts', 'spectrum_countsPerSecond', 'spectrum_countsNorm'})} = 'spectrum_countsPerSecond'
|
|
options.timeVariable char {mustBeMember(options.timeVariable, ...
|
|
{'lastModifiedDatenum', 'creationDate'})} = 'lastModifiedDatenum'
|
|
options.peakNumber (1,1) double {mustBeInteger, mustBePositive} = 1
|
|
end
|
|
|
|
numRowsTable = height(DataTable);
|
|
|
|
% Validate length of the logical arrays
|
|
if length(processSelection) ~= numRowsTable
|
|
error('processSelection must have the exact same length as the DataTable height (%d).', numRowsTable);
|
|
end
|
|
if ~isempty(refSelection) && any(refSelection) && length(refSelection) ~= numRowsTable
|
|
error('refSelection must have the exact same length as the DataTable height (%d).', numRowsTable);
|
|
end
|
|
|
|
% Auto-detect mode: If no valid reference array is passed OR 'internalMax' is chosen
|
|
if strcmpi(options.matchStrategy, 'internalMax') || isempty(refSelection) || ~any(refSelection)
|
|
mode = 'maximum';
|
|
else
|
|
mode = 'referenceArea';
|
|
end
|
|
|
|
% Initialize new columns if they don't exist
|
|
if ~ismember('spectrum_countsNorm', DataTable.Properties.VariableNames)
|
|
DataTable.spectrum_countsNorm = cell(numRowsTable, 1);
|
|
end
|
|
if ~ismember('logNormMode', DataTable.Properties.VariableNames)
|
|
DataTable.logNormMode = cell(numRowsTable, 1);
|
|
end
|
|
if ~ismember('logNormWith', DataTable.Properties.VariableNames)
|
|
DataTable.logNormWith = cell(numRowsTable, 1); % New column for spectrum name
|
|
end
|
|
|
|
%% Processing Loop
|
|
processIdx = find(processSelection);
|
|
refIdx = find(refSelection);
|
|
|
|
for i = 1:length(processIdx)
|
|
currRow = processIdx(i);
|
|
yData = DataTable.(options.tableVariable){currRow};
|
|
currTime = datenum(DataTable.(options.timeVariable)(currRow, :));
|
|
refTimes = datenum(DataTable.(options.timeVariable)(refIdx, :));
|
|
currName = DataTable.name{currRow};
|
|
bestRefRow = NaN;
|
|
|
|
if isempty(yData)
|
|
continue;
|
|
end
|
|
|
|
switch mode
|
|
case 'maximum'
|
|
normFactor = max(yData);
|
|
if normFactor == 0, normFactor = 1; end % Protect against /0
|
|
|
|
DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor;
|
|
DataTable.logNormMode{currRow} = 'maximum';
|
|
DataTable.logNormWith{currRow} = num2str(normFactor); % Log internal maximum
|
|
|
|
case 'referenceArea'
|
|
|
|
|
|
|
|
% Internal Matching Engine using Code Words
|
|
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
|
|
|
|
|
|
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 area and validate
|
|
refAreas = DataTable.peakArea(bestRefRow,:);
|
|
if size(refAreas,2) < options.peakNumber
|
|
warning('Row %d: peakNumber %d exceeds available peaks in reference row %d. Skipping.', ...
|
|
currRow, options.peakNumber, bestRefRow);
|
|
continue;
|
|
end
|
|
|
|
normFactor = refAreas(options.peakNumber);
|
|
if normFactor == 0
|
|
warning('Row %d: Reference area in row %d is zero. Skipping.', currRow, bestRefRow);
|
|
continue;
|
|
end
|
|
|
|
% Apply normalization and log the exact name of the reference
|
|
DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor;
|
|
DataTable.logNormMode{currRow} = sprintf('referenceArea (Row %d)', bestRefRow);
|
|
DataTable.logNormWith{currRow} = DataTable.name{bestRefRow};
|
|
end
|
|
end
|
|
end |