Files
nappMATLAB-toolbox/beamtime/nappGenerateSpectra.m
T
bartels-rausch 00d34110db MAJOR UPDATE: Add support for Specs .xy files from offline XPS (Soleil/Luca)
Both Scienta (.ibw) and SpecsLab (.xy) raw files capture multidimensional XPS data but use different terminology to structure it. Scienta uses "Region Iterations" and "Sequence Iterations". SpecsLab organizes data by "Groups" or "Regions" (different elemental energy sweeps), "Scans" (time-resolved repetitions), and "Curves". Scienta Region Iterations are automatically averaged to improve the signal-to-noise ratio, while Scienta Sequence Iterations and SpecsLab Scans are  preserved to keep the time-resolved data. For SpecsLab files specifically, distinct Regions are split into separate table rows. Ultimately, the preserved time-resolved data is saved in the table's image variable as a 2D matrix ([Energy bins × Sequence iterations/Scans]).

File type handling: Added .xy as a recognized XPS format. Currently, the script differentiates data types strictly by file extension (NEXAFS: .h5, XPS: .ibw and .xy). This extension-based differentiation needs to be improved in future updates.

Metadata parsing: Implemented global header scanning for .xy files to correctly extract instrument parameters (Acquisition Mode, Lens Mode, Slit, Workfunction) located before the first region block.

Timezone alignment: Added timestamp conversion for .xy files from UTC to local time (Europe/Zurich) to ensure synchronization with LabView logs.

Region separation: Modified .xy parsing to append a _RegX suffix to filenames. This correctly splits single .xy files that contain multiple elements or energy sweeps into distinct table rows so they can be processed individually.

Import efficiency: Added an originalFileName variable. The script now cross-references the input DataTable and skips files that have already been imported to reduce execution time on re-runs.

Testing status: Processing of .xy data has currently only been tested and debugged with nappGenerateSpectra and nappPlot. Compatibility with other functions in the suite remains untested.
2026-03-12 10:08:47 +01:00

213 lines
9.5 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];
DataTable.logGenerateDate(p.Results.tableSelection,:) = cellstr(repmat(datestr(now),size(p.Results.DataTable(p.Results.tableSelection,:),1),1));
end