Files
nappMATLAB-toolbox/beamtime/nappGenerateSpectra.m
T
bartels-rausch 091cf00e77 Refactor energy calibration pipeline, introduce modern arguments blocks, and add universal logging
* Transitioned nappNorm, nappDetermineShift, and nappCopyShift to use modern MATLAB arguments blocks, removing their legacy functionSignatures.json entries.

* Refactored nappNorm to rely on prior peak fitting results (e.g., from nappFitGaussXps) for normalization rather than performing the fitting internally.

* Decoupled energy calibration: nappDetermineShift now solely calculates internal binding energy shifts, and nappCopyShift distributes them using a unified matching engine ('earlier', 'after', 'spectrumNumber').

* Implemented a Universal Logging Block to track data provenance, automatically appending execution timestamps and caller script names to processed rows.

* Updated nappCombineSpectra to output a logDataSource cell array and cleanly name combined spectra.

* Created nappSaveDataTable for automatic, script-named .mat file saving, complete with a Live Editor interceptor.

* Added a publish-mode silencer to nappFitGaussXps to prevent optimizer spam in generated PDFs.

* Updated readme.md to document the new arguments structure, the 3-step calibration workflow, the matching engine, and the new table variables.
2026-03-25 12:07:49 +01:00

245 lines
11 KiB
Matlab

function DataTable = nappGenerateSpectra(DataTable,varargin)
%NAPPGENERATESPECTRA Generate 1D spectra from image spectra.
% DataTable = NAPPGENERATESPECTRA(DataTable) processes all data in the
% input table and generates spectra for the data files in each row. The
% spectra are written back into the table into new columns (table
% variables).
%
% DataTable = NAPPGENERATESPECTRA(DataTable, tableSelection) restricts
% processing to the rows where tableSelection is true. The length of
% tableSelection must match the number of rows in DataTable.
%
% DataTable = NAPPGENERATESPECTRA(DataTable, tableSelection, Name, Value)
% additionally customizes the source variable, reduction mode, and energy
% or iteration ranges via Name-Value arguments.
%
% Required input
% --------------
% DataTable
% Table that contains at least the variables:
% - name : file name as character vector or string
% - image or imageRoh : 2D image or image stack stored in a cell
% - scientaEkin_eV : kinetic energy axis
%
% Optional positional input
% -------------------------
% tableSelection
% Logical vector selecting rows to process.
% Size: [height(DataTable) 1] or [1 height(DataTable)]
% Default: all rows true
%
% Name-Value arguments
% --------------------
% 'tableVariable'
% Name of the variable in DataTable that contains the image data.
% Type : char or string
% Values : 'image' (default) | 'imageRoh'
%
% 'mode'
% Reduction mode used to generate the spectra.
% Type : char or string
% Values : 'mean' (default) | 'sum' | 'trapz' | 'AugerMeitnerMean'
% Behaviour:
% - For '.h5' entries:
% 'sum' or 'mean' : sum over the selected kinetic energy range
% (currently identical behaviour; no
% normalization by time implemented).
% 'trapz' : trapezoidal integration over kinetic
% energy for each column.
% - For '.ibw' entries:
% 'mean' or 'trapz': mean over the selected iteration range
% (2nd dimension) and store as a column
% vector.
% 'sum' : sum over the selected iteration range.
%
% 'iterationRange'
% Iteration indices for '.ibw' image stacks (2nd dimension).
% Type : numeric 1x2 vector [startIdx endIdx]
% Default: [0 0] -> use full range 1 : size(image,2)
%
% 'eKinStart'
% Lower kinetic energy limit for '.h5' data.
% Type : numeric scalar
% Default: 0 -> use first element of scientaEkin_eV
%
% 'eKinEnd'
% Upper kinetic energy limit for '.h5' data.
% Type : numeric scalar
% Default: 0 -> use last element of scientaEkin_eV
%
% Output
% ------
% DataTable
% Same table as input, with the following variables ensured and
% updated for the selected rows:
%
% spectrum_counts
% Cell array. Each cell contains a spectrum generated from the
% selected image data, depending on file type and mode
% ('sum', 'mean', or 'trapz').
%
% spectrum_countsPerSecond
% Cell array. For '.ibw' entries and modes 'mean' or 'trapz',
% this stores the spectrum obtained by averaging over the chosen
% iteration range.
%
% logGenerateMode
% Cell array of character vectors specifying the mode used for
% each processed row.
%
% logGenerateRange
% Numeric matrix with two columns. For '.h5' entries this stores
% [eKinStart eKinEnd]. For '.ibw' entries this stores
% [iterationStart iterationEnd].
%
% logGenerateDate
% Cell array of character vectors containing the date and time
% when the spectra were generated.
%
% Notes
% -----
% - The function distinguishes between '.h5' and '.ibw' files based on
% the file name stored in the variable 'name'.
% Define conditions and defaults of input
defaultTableVariable = 'image';
expectedTableVariable = {'image','imageRoh'};
defaultMode = 'mean';
expectedMode = {'mean','sum','trapz','AugerMeitnerMean'};
defaultEkinStart = 0;
defaultEkinEnd = 0;
defaultTableSelection = true(size(DataTable,1),1);
% Parse input
p = inputParser;
addRequired(p,'DataTable',@istable);
addOptional(p,'tableSelection',defaultTableSelection,@islogical);
addParameter(p,'tableVariable',defaultTableVariable,...
@(x) any(validatestring(x,expectedTableVariable)));
addParameter(p,'mode',defaultMode,...
@(x) any(validatestring(x,expectedMode)));
addParameter(p,'iterationRange',[0:0]);
addParameter(p,'eKinStart',defaultEkinStart,@isnumeric)
addParameter(p,'eKinEnd',defaultEkinEnd,@isnumeric)
parse(p,DataTable,varargin{:});
% add empty variable to table
existColumn = strcmp('spectrum_countsPerSecond',p.Results.DataTable.Properties.VariableNames);
if ~any(existColumn)
DataTable.spectrum_countsPerSecond = cell(size(p.Results.DataTable,1),1);
end
existColumn = strcmp('spectrum_counts',p.Results.DataTable.Properties.VariableNames);
if ~any(existColumn)
DataTable.spectrum_counts = cell(size(p.Results.DataTable,1),1);
end
existColumn = strcmp('logGenerateRange',p.Results.DataTable.Properties.VariableNames);
if ~any(existColumn)
DataTable.logGenerateRange = NaN(size(p.Results.DataTable,1),2);
end
existColumn = strcmp('logGenerateDate',p.Results.DataTable.Properties.VariableNames);
if ~any(existColumn)
DataTable.logGenerateDate = cellstr(repmat(' ',size(p.Results.DataTable,1),1));
end
existColumn = strcmp('logGenerateMode',p.Results.DataTable.Properties.VariableNames);
if ~any(existColumn)
DataTable.logGenerateDate = cellstr(repmat(' ',size(p.Results.DataTable,1),1));
end
% check input (all elements of input variable need to have non-zero size)
if ~all(cell2mat(cellfun(@size,p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable),'UniformOutput',false)),'all')
disp('The table variable appears to be empty. I quit now')
error('The table variable appears to be empty.')
end
% create empty variables
tmp_3 = cell(size(p.Results.DataTable(p.Results.tableSelection,:),1),1);
tmp_6 = cell(size(p.Results.DataTable(p.Results.tableSelection,:),1),1);
tmp_4 = NaN(size(p.Results.DataTable(p.Results.tableSelection,:),1),1);
tmp_5 = NaN(size(p.Results.DataTable(p.Results.tableSelection,:),1),1);
% repeat this for all columns in input.
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
if endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'h5') % let's do NEXAFS first.
% Get index of EkinStart and EkinEnd
if (p.Results.eKinStart) == 0
tmp_1 = 1;
else
tmp_1 = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii} >= p.Results.eKinStart,1);
end
if (p.Results.eKinEnd) == 0
tmp_2 = length(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii});
else
tmp_2 = find(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii} >= p.Results.eKinEnd,1);
end
if contains(p.Results.mode,'trapz')
tmp_3(ii,1)= {trapz(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(tmp_1:tmp_2),...
p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(tmp_1:tmp_2,:))'};
elseif (contains(p.Results.mode,'sum') | contains(p.Results.mode,'mean'))
tmp_3(ii,1) = {sum(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(tmp_1:tmp_2,:),1)'}; %#ok<*AGROW>
end
% log
tmp_4(ii,1) = p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(tmp_1);
tmp_5(ii,1) = p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii}(tmp_2);
elseif endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'ibw') || endsWith(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'xy') % then do Igorfiles or xy files
if p.Results.iterationRange(1) == 0
startRange = 1;
endRange = size(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},2);
else
startRange = p.Results.iterationRange(1);
endRange = p.Results.iterationRange(2);
end
if (contains(p.Results.mode,'mean') | contains(p.Results.mode,'trapz'))
tmp_6(ii,1)= {mean(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(:,startRange:endRange),2)};
elseif contains(p.Results.mode,'sum')
tmp_3(ii,1) = {sum(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}(:,startRange:endRange),2)}; %#ok<*AGROW>
end
% log
tmp_4(ii,1) = startRange;
tmp_5(ii,1) = endRange;
end
end
% write results to table
DataTable.spectrum_counts(p.Results.tableSelection) = tmp_3;
DataTable.spectrum_countsPerSecond(p.Results.tableSelection) = tmp_6;
% write log to table
DataTable.logGenerateMode(p.Results.tableSelection,:) = cellstr(repmat(p.Results.mode,size(p.Results.DataTable(p.Results.tableSelection,:),1),1));
DataTable.logGenerateRange(p.Results.tableSelection,:) = [tmp_4 tmp_5];
% --- Define variables for the Universal Logging Block ---
numRowsTable = height(DataTable);
processIdx = find(p.Results.tableSelection);
% -------------------------------------------------------------------
% 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