diff --git a/beamtime/documentedExampleScripts/TestAndDevelopShirley.m b/beamtime/documentedExampleScripts/TestAndDevelopShirley.m index 7507c2e..d9e0d0e 100644 --- a/beamtime/documentedExampleScripts/TestAndDevelopShirley.m +++ b/beamtime/documentedExampleScripts/TestAndDevelopShirley.m @@ -78,6 +78,25 @@ FitList(tmpDataSelection,:).spectrum_counts{:} = y; nappPlot(FitList,tmpDataSelection,'tableVariable','spectrum_countsNorm','energyScale','kinetic'); nappPlot(FitList,tmpDataSelection,'tableVariable','spectrum_counts','energyScale','binding'); +%% TEST 0.1: Linear Background + +% exclude one central area +nappBackground(FitList,109,116,'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_countsNorm','energyScale','kinetic','showFigures','on'); +% exclude one central area and start +nappBackground(FitList,[109 106],[116 119],'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_countsNorm','energyScale','kinetic','showFigures','on'); +% exclude 2 central area and start +nappBackground(FitList,[109 106 107],[116 119 108],'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_countsNorm','energyScale','kinetic','showFigures','on'); +% exclude 2 central area and start +nappBackground(FitList,[109 106 107],[116 NaN 108],'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_countsNorm','energyScale','kinetic','showFigures','on'); +% exclude one central area and start +nappBackground(FitList,[109 106],116,'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_countsNorm','energyScale','kinetic','showFigures','on'); + + +% exclude one central area +nappBackground(FitList,35,39,'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_counts','energyScale','binding','showFigures','on'); +% exclude one central area and start +nappBackground(FitList,[35 31],[39 40],'tableSelection',tmpDataSelection,'mode', 'linfit','tableVariable','spectrum_counts','energyScale','binding','showFigures','on'); + %% TEST 1.1: Unconstrained kinetic Gauss linear % Gaussian + Linear, individual area/width/position per peak @@ -97,6 +116,32 @@ tmpFitParameters.peakPosLowerBound = [112 109]; tmpFitParameters.peakPosUpperBound = [114 111]; tmpFitParameters.backgroundRange = [12 6]; % <- selects fit range around peak1 +FitList = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... + 'tableVariable','spectrum_countsNorm','energyScale','kinetic',... + 'mode','unconstrained','backgroundMode','linear','showFigures','on'); +%% TEST 1.1B: Unconstrained kinetic Gauss linear +% Gaussian + Linear, individual area/width/position per peak + +clearvars tmpFitParameters; +clc +tmpFitParameters.peakArea = [5e5 100]; % <- 2 values => 2 peaks +tmpFitParameters.peakWidth = [1.6 0.6]; +tmpFitParameters.peakPos = [113 110]; +% Bounds for areas (lower and upper) +tmpFitParameters.peakAreaLowerBound = [1e3 1e3]; +tmpFitParameters.peakAreaUpperBound = [Inf Inf]; +% Bounds for widths +tmpFitParameters.peakWidthLowerBound = [1.0 0.6]; +tmpFitParameters.peakWidthUpperBound = [2.0 1.2]; +% Bounds for positions +tmpFitParameters.peakPosLowerBound = [112 109]; +tmpFitParameters.peakPosUpperBound = [114 111]; +tmpFitParameters.backgroundRange = [12 6]; % <- selects fit range around peak1 +% Bounds for background +tmpFitParameters.bkgrd = [0 0]; +tmpFitParameters.pbkgrdLowerBound = [0 0]; +tmpFitParameters.bkgrdUpperBound = [0 0]; + FitList = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... 'tableVariable','spectrum_countsNorm','energyScale','kinetic',... 'mode','unconstrained','backgroundMode','linear','showFigures','on'); diff --git a/beamtime/nappAddFitToPlot.m b/beamtime/nappAddFitToPlot.m index 431b4c8..178f1f3 100644 --- a/beamtime/nappAddFitToPlot.m +++ b/beamtime/nappAddFitToPlot.m @@ -37,7 +37,7 @@ nPeaks = sum(validPeaks); % Count valid peaks fprintf('Found %d valid peaks (non-NaN)\n', nPeaks); -if isempty(energyRange) || nPeaks == 0 +if isempty(energyRange) | nPeaks == 0 warning('No valid fit data to plot'); return; end diff --git a/beamtime/nappBackground.m b/beamtime/nappBackground.m index 5caeb2e..79e7b80 100644 --- a/beamtime/nappBackground.m +++ b/beamtime/nappBackground.m @@ -1,9 +1,9 @@ function DataTable = nappBackground(DataTable,varargin) %NAPPBACKGROUND Removes background from photoelectron or XAS spectra. -% This function determines and removes the background from PE or XAS +% This function determines and removes the background from PE or XAS % spectra using mean subtraction or curve fitting (linear or exponential). % Background correction can be performed on kinetic energy, binding energy, -% or X-ray photon energy scales. Results are optionally normalized by +% or X-ray photon energy scales. Results are optionally normalized by % dividing instead of subtracting. % % SYNTAX @@ -28,57 +28,77 @@ function DataTable = nappBackground(DataTable,varargin) % --------------- % DataTable table % Data table containing spectra and energy scales. Must contain -% one of the following variables: 'spectrum_counts', +% one of the following variables: 'spectrum_counts', % 'spectrum_countsPerSecond', or 'spectrum_countsNorm'. % % energyStart — Start energies for excluded ranges -% Numeric vector of length N (must match length of energyEnd). -% Each element pairs with the corresponding element of energyEnd -% to define an excluded domain: +% Numeric vector. Each element pairs with energyEnd at the same position. +% The positions have fixed roles: % % Position 1 (required): % Lower bound of the main excluded domain (e.g. the peak). -% Combined with energyEnd(1) as [energyStart(1), energyEnd(1)]. % -% Position 2 (optional, backward compatible): -% Cuts off the near-edge of the spectrum. Excludes from the -% first data point up to energyStart(2) (kinetic), or from -% energyStart(2) to the last data point (binding). +% Position 2 (optional): ALWAYS the edge cutoffs, never a peak region. +% energyStart(2): trim the low-value spectrum edge — excludes all +% data points below this value. +% Use NaN if no low-end trim is needed. +% If you want positions 3+ but no edge cutoff, pass NaN here. % -% Positions 3+ (optional, new): -% Each pair [energyStart(k), energyEnd(k)] defines an -% additional arbitrary excluded domain. +% Positions 3+ (optional): +% Each pair [energyStart(k), energyEnd(k)] defines an additional +% arbitrary excluded domain (e.g. a secondary peak or artefact). % % energyEnd — End energies for excluded ranges -% Numeric vector of length N (must match length of energyStart). +% Numeric vector, same length as energyStart (or padded with NaN). % % Position 1 (required): % Upper bound of the main excluded domain. % -% Position 2 (optional, backward compatible): -% Cuts off the far-edge of the spectrum. Excludes from -% energyEnd(2) to the last data point (kinetic), or from -% the first data point to energyEnd(2) (binding). +% Position 2 (optional): ALWAYS the edge cutoffs. +% energyEnd(2): trim the high-value spectrum edge — excludes all +% data points above this value. +% Use NaN if no high-end trim is needed. % -% Positions 3+ (optional, new): -% Upper bound of additional excluded domains (pairs with -% energyStart(3), energyStart(4), ...). +% Positions 3+: upper bounds of additional excluded domains. % -% Example — main peak + two additional exclusions: -% energyStart = [395, 380, 410] -% energyEnd = [407, NaN, 415] -% (position 2 energyEnd=NaN means no far-edge cutoff) +% Note: energyStart(2) and energyEnd(2) refer to values on the sorted +% x-axis (prepareCurveData always returns xData ascending), so the +% convention is identical for kinetic and binding energy scales. +% +% Examples: +% % One peak only +% nappBackground(..., 109, 116, ...) +% +% % One peak + low-end cutoff only +% nappBackground(..., [109 106], [116 NaN], ...) +% nappBackground(..., [109 106], [116], ...) % legacy shorthand, equivalent +% +% % One peak + high-end cutoff only +% nappBackground(..., [109 NaN], [116 119], ...) +% nappBackground(..., [109], [116 119], ...) % legacy shorthand, equivalent +% +% % One peak + both edge cutoffs +% nappBackground(..., [109 106], [116 119], ...) +% +% % Two peaks + both edge cutoffs +% nappBackground(..., [109 106 107], [116 119 108], ...) +% +% % Two peaks + low-end cutoff only (NaN = no high-end trim) +% nappBackground(..., [109 106 107], [116 NaN 108], ...) +% +% % Two peaks, no edge cutoffs (NaN NaN at position 2) +% nappBackground(..., [109 NaN 107], [116 NaN 108], ...) % % % NAME-VALUE PAIR ARGUMENTS (optional) % ------------------------------------ % 'tableSelection' logical array (default: all rows) -% Logical index to filter rows in DataTable. +% Logical index to filter rows in DataTable. % Example: tableSelection = DataTable.quality > 0.8 % % 'tableVariable' char vector (default: 'spectrum_countsPerSecond') % Spectral variable to process. Must be one of: -% - 'spectrum_counts' +% - 'spectrum_counts' % - 'spectrum_countsPerSecond' % - 'spectrum_countsNorm' % @@ -94,7 +114,7 @@ function DataTable = nappBackground(DataTable,varargin) % 'energyScale' char vector (default: 'kinetic') % Energy axis to use for background calculation: % - 'kinetic': uses scientaEkin_eV column -% - 'binding': uses bindingEnergy_eV column +% - 'binding': uses bindingEnergy_eV column % - 'xRay': uses xRayEkin_eV column (for NEXAFS data) % % 'showFigures' char vector (default: 'on') @@ -106,7 +126,7 @@ function DataTable = nappBackground(DataTable,varargin) % ---------------- % DataTable table % Updated data table with background-corrected spectra. The table -% variable specified by 'tableVariable' is overwritten with +% variable specified by 'tableVariable' is overwritten with % corrected data. If mode contains 'divide', results are written % to 'spectrum_countsNorm' instead. % @@ -129,7 +149,7 @@ function DataTable = nappBackground(DataTable,varargin) defaultTableVariable = 'spectrum_countsPerSecond'; expectedTableVariable = {'spectrum_counts','spectrum_countsPerSecond','spectrum_countsNorm'}; defaultMode = 'mean'; -expectedMode = {'mean','linfit','exponential','mean, divide','linfit, divide','exponential, divide'}; +expectedMode = {'mean','linfit','exponential','mean, divide','linfit, divide','exponential, divide','interpolate'}; defaultTableSelection = true(size(DataTable,1),1); defaultShowFigures = 'on'; expectedShowFigures = {'on','off'}; @@ -186,39 +206,58 @@ for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1) ft = fittype( 'poly1' ); elseif contains(p.Results.mode,'exponential') ft = fittype( 'exp2' ); + elseif contains(p.Results.mode,'interpolate') + ft = 'linearinterp'; + opts = fitoptions( 'Method', 'LinearInterpolant' ); + opts.ExtrapolationMethod = 'linear'; + opts.Normalize = 'on'; end if contains(p.Results.energyScale,'kinetic') if ~startsWith(p.Results.mode,'mean') [xData, yData] = prepareCurveData(p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii},p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii}); - - % Position 1: main exclusion domain (e.g. the peak) — backward compatible -excludedPoints = excludedata(xData, yData, 'domain', [energyStart(1), energyEnd(1)]); -% Convention: true = point is inside fitting region (kept). Combined with & below. -% Position 2: edge cutoffs — backward compatible -if numel(energyStart) >= 2 && ~isnan(energyStart(2)) - excludedPoints = excludedPoints & ... - excludedata(xData, yData, 'domain', [xData(1), energyStart(2)]); -end -if numel(energyEnd) >= 2 && ~isnan(energyEnd(2)) - excludedPoints = excludedPoints & ... - excludedata(xData, yData, 'domain', [energyEnd(2), xData(end)]); -end + % Convention for excludedPoints: true = point is KEPT for fitting. + % excludedata(...,'domain',[a,b]) returns true for points OUTSIDE [a,b]. + % + % Fixed layout of energyStart / energyEnd vectors: + % Position 1 : main exclusion region (e.g. the peak) — required + % Position 2 : edge cutoffs — NaN on either side = no cutoff there + % energyStart(2): exclude xData < energyStart(2) + % energyEnd(2) : exclude xData > energyEnd(2) + % Use NaN if you have positions 3+ but no edge cutoff. + % Positions 3+ : additional arbitrary exclusion regions -% Positions 3+: additional arbitrary exclusion domains (new) -nPairs = min(numel(energyStart), numel(energyEnd)); -for k = 3:nPairs - if ~isnan(energyStart(k)) && ~isnan(energyEnd(k)) - excludedPoints = excludedPoints & ... - excludedata(xData, yData, 'domain', [energyStart(k), energyEnd(k)]); - end -end + % Position 1: main exclusion domain + excludedPoints = excludedata(xData, yData, 'domain', [energyStart(1), energyEnd(1)]); - opts = fitoptions( 'Method', 'NonlinearLeastSquares','Exclude', ~excludedPoints ); - opts.Display = 'Off'; + % Position 2: edge cutoffs (NaN = skip that side) + if numel(energyStart) >= 2 && ~isnan(energyStart(2)) + excludedPoints = excludedPoints & ... + excludedata(xData, yData, 'domain', [xData(1), energyStart(2)]); + end + if numel(energyEnd) >= 2 && ~isnan(energyEnd(2)) + excludedPoints = excludedPoints & ... + excludedata(xData, yData, 'domain', [energyEnd(2), xData(end)]); + end + % Positions 3+: additional arbitrary exclusion regions + nPairs = min(numel(energyStart), numel(energyEnd)); + for k = 3:nPairs + if ~isnan(energyStart(k)) && ~isnan(energyEnd(k)) + excludedPoints = excludedPoints & ... + excludedata(xData, yData, 'domain', [energyStart(k), energyEnd(k)]); + end + end + + if startsWith(p.Results.mode,'interpolate') + opts.Exclude = ~excludedPoints; + + else + opts = fitoptions( 'Method', 'NonlinearLeastSquares','Exclude', ~excludedPoints ); + opts.Display = 'Off'; + end % Fit model to data. [fitresult, ~] = fit( xData, yData, ft, opts ); if contains(p.Results.showFigures,'on') @@ -258,32 +297,47 @@ end % Set up fittype and options. - % Position 1: main exclusion domain (e.g. the peak) — backward compatible -excludedPoints = ~excludedata(xData, yData, 'domain', [energyStart(1), energyEnd(1)]); -% Convention: true = point should be excluded from fit. Combined with | below. + % Convention for excludedPoints: true = point is EXCLUDED from fitting. + % ~excludedata(...,'domain',[a,b]) returns true for points INSIDE [a,b]. + % + % Fixed layout of energyStart / energyEnd vectors (same as kinetic): + % Position 1 : main exclusion region (e.g. the peak) — required + % Position 2 : edge cutoffs — NaN on either side = no cutoff there + % energyStart(2): exclude xData < energyStart(2) + % energyEnd(2) : exclude xData > energyEnd(2) + % Use NaN if you have positions 3+ but no edge cutoff. + % Positions 3+ : additional arbitrary exclusion regions + % prepareCurveData sorts xData ascending, so xData(1) is always the + % numerically smallest value regardless of kinetic/binding axis direction. -% Position 2: edge cutoffs — backward compatible -if numel(energyStart) >= 2 && ~isnan(energyStart(2)) - excludedPoints = excludedPoints | ... - ~excludedata(xData, yData, 'domain', [xData(end), energyStart(2)]); -end -if numel(energyEnd) >= 2 && ~isnan(energyEnd(2)) - excludedPoints = excludedPoints | ... - ~excludedata(xData, yData, 'domain', [energyEnd(2), xData(1)]); -end + % Position 1: main exclusion domain + excludedPoints = ~excludedata(xData, yData, 'domain', [energyStart(1), energyEnd(1)]); -% Positions 3+: additional arbitrary exclusion domains (new) -nPairs = min(numel(energyStart), numel(energyEnd)); -for k = 3:nPairs - if ~isnan(energyStart(k)) && ~isnan(energyEnd(k)) - excludedPoints = excludedPoints | ... - ~excludedata(xData, yData, 'domain', [energyStart(k), energyEnd(k)]); - end -end - - opts = fitoptions( 'Method', 'NonlinearLeastSquares','Exclude', excludedPoints ); - opts.Display = 'Off'; + % Position 2: edge cutoffs (NaN = skip that side) + if numel(energyStart) >= 2 && ~isnan(energyStart(2)) + excludedPoints = excludedPoints | ... + ~excludedata(xData, yData, 'domain', [xData(1), energyStart(2)]); + end + if numel(energyEnd) >= 2 && ~isnan(energyEnd(2)) + excludedPoints = excludedPoints | ... + ~excludedata(xData, yData, 'domain', [energyEnd(2), xData(end)]); + end + % Positions 3+: additional arbitrary exclusion regions + nPairs = min(numel(energyStart), numel(energyEnd)); + for k = 3:nPairs + if ~isnan(energyStart(k)) && ~isnan(energyEnd(k)) + excludedPoints = excludedPoints | ... + ~excludedata(xData, yData, 'domain', [energyStart(k), energyEnd(k)]); + end + end + if startsWith(p.Results.mode,'interpolate') + opts.Exclude = excludedPoints; + + else + opts = fitoptions( 'Method', 'NonlinearLeastSquares','Exclude', excludedPoints ); + opts.Display = 'Off'; + end % Fit model to data. @@ -385,41 +439,41 @@ end % write log to table DataTable.lognappBackgroundMode(p.Results.tableSelection,:) = cellstr(repmat(p.Results.mode,size(p.Results.DataTable(p.Results.tableSelection,:),1),1)); DataTable.lognappBackgroundRange(p.Results.tableSelection,:) = repmat([energyStart(1) energyEnd(1)],size(p.Results.DataTable(p.Results.tableSelection,:),1),1); - - %--- 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; - % ------------------------------------------------------------------------- +%--- 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 \ No newline at end of file diff --git a/beamtime/nappNorm.m b/beamtime/nappNorm.m index a0f9c98..d51d1a3 100644 --- a/beamtime/nappNorm.m +++ b/beamtime/nappNorm.m @@ -82,11 +82,49 @@ for i = 1:length(processIdx) end switch modus case 'internalMax' + if ~isempty(options.range) + % Auto-detect available energy column for this row + if strcmpi(options.energyScale, 'kinetic') && ... + ismember('scientaEkin_eV', DataTable.Properties.VariableNames) && ... + ~isempty(DataTable.scientaEkin_eV{currRow}) + xData = DataTable.scientaEkin_eV{currRow}; + elseif ismember('bindingEnergy_eV', DataTable.Properties.VariableNames) && ... + ~isempty(DataTable.bindingEnergy_eV{currRow}) + xData = DataTable.bindingEnergy_eV{currRow}; + else + warning('Row %d (%s): No valid energy column found. Falling back to full-spectrum max.', ... + currRow, DataTable.name{currRow}); + xData = []; + end + + if ~isempty(xData) + % min/max of range handles both ascending and descending order + minR = min(options.range); + maxR = max(options.range); + idxRange = (xData >= minR) & (xData <= maxR); + + if ~any(idxRange) + warning('Row %d (%s): options.range [%.4g %.4g] contains no data points (xData spans [%.4g %.4g]). Falling back to full-spectrum max.', ... + currRow, DataTable.name{currRow}, minR, maxR, min(xData), max(xData)); + normFactor = max(yData); + else + searchY = yData(idxRange); + [normFactor, relativeMaxIdx] = max(searchY); + validIndices = find(idxRange); + absoluteMaxIndex = validIndices(relativeMaxIdx); + fprintf('Norming %s at %.1f eV: %.1e\n', DataTable.name{currRow}, xData(absoluteMaxIndex), normFactor); + end + else 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 + end + else + normFactor = max(yData); + end + + if normFactor == 0, normFactor = 1; end % Protect against /0 + DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor; + DataTable.lognappNormMode{currRow} = 'maximum'; + DataTable.lognappNormWith{currRow} = num2str(normFactor); case 'referenceArea' % Internal Matching Engine using Code Words switch options.matchStrategy @@ -230,8 +268,8 @@ for i = 1:length(processIdx) if normFactor == 0, normFactor = 1; end % Protect against /0 DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor; - DataTable.logNormMode{currRow} = 'external maximum'; - DataTable.logNormWith{currRow} = num2str(normFactor); % Log internal maximum + DataTable.lognappNormMode{currRow} = 'external maximum'; + DataTable.lognappNormWith{currRow} = num2str(normFactor); % Log internal maximum end case 'after' validRefs = refIdx(refTimes >= currTime); @@ -292,8 +330,8 @@ for i = 1:length(processIdx) end if normFactor == 0, normFactor = 1; end % Protect against /0 DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor; - DataTable.logNormMode{currRow} = 'external maximum'; - DataTable.logNormWith{currRow} = num2str(normFactor); % Log internal maximum + DataTable.lognappNormMode{currRow} = 'external maximum'; + DataTable.lognappNormWith{currRow} = num2str(normFactor); % Log internal maximum end case 'spectrumNumber' % Extract the 4-digit number at the start of the name (e.g., '0033') @@ -361,8 +399,8 @@ for i = 1:length(processIdx) end if normFactor == 0, normFactor = 1; end % Protect against /0 DataTable.spectrum_countsNorm{currRow} = yData ./ normFactor; - DataTable.logNormMode{currRow} = 'external maximum'; - DataTable.logNormWith{currRow} = num2str(normFactor); % Log internal maximum + DataTable.lognappNormMode{currRow} = 'external maximum'; + DataTable.lognappNormWith{currRow} = num2str(normFactor); % Log internal maximum end end end \ No newline at end of file