bug fixes

This commit is contained in:
bartels-rausch committed 2026-05-04 19:03:39 +02:00
1 parent 5767a2e26d
commit 2a22d2dbea
4 files changed
+257 -227

No files matched your search

+2 -2
View File
@@ -24,8 +24,8 @@ xlims = xlim(ax);
energyMin = xlims(1);
energyMax = xlims(2);
% Create energy range with 0.2 eV step
energyStep = 0.2;
% Create energy range with 0.01 eV step
energyStep = 0.01;
energyRange = (energyMin:energyStep:energyMax)';
% =====================================================================
+16 -8
View File
@@ -548,7 +548,7 @@ if ~isempty(TmpFileList{1})
current_file_info.lastModifiedDatestr = current_file_info.date;
current_file_info.image = {[]};
current_file_info.imageRoh = {[]};
current_file_info.xRayEkin_eV = NaN;
current_file_info.xRayEkin_eV = {[]};
current_file_info.xRayEkinRange_eV = [NaN NaN];
current_file_info.scientaPassEnergy_eV = NaN;
current_file_info.scientaDwellTime_ms = NaN;
@@ -942,7 +942,8 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
end
% Make unique
[TmpFileList{2}(ii).xRayEkin_eV, ind] = unique(TmpXrayEkin);
[tmpE, ind] = unique(TmpXrayEkin);
TmpFileList{2}(ii).xRayEkin_eV = {tmpE(:)'};
TmpFileList{2}(ii).image = TmpImage(:,ind);
TmpFileList{2}(ii).image = TmpImage(:,ind);
% NEXAFS pass energy
@@ -1029,8 +1030,10 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
TmpFileList{2}(ii).scientaEkinRange_eV = [double(tmp(1))-TmpFileList{2}(ii).scientaPassEnergy_eV/20 double(tmp(1))+TmpFileList{2}(ii).scientaPassEnergy_eV/20];
TmpFileList{2}(ii).scientaEkin_eV = linspace(TmpFileList{2}(ii).scientaEkinRange_eV(:,1)...
,TmpFileList{2}(ii).scientaEkinRange_eV(:,2),size(TmpFileList{2}(ii).image,1))';
TmpFileList{2}(ii).scientaEkin_eV = { ...
linspace(TmpFileList{2}(ii).scientaEkinRange_eV(1), ...
TmpFileList{2}(ii).scientaEkinRange_eV(2), ...
size(TmpFileList{2}(ii).image,1))' };
else
% NEXAFS kinetic energy window - scan mode
try
@@ -1057,8 +1060,11 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
TmpFileList{2}(ii).scientaEkinStep_eV = abs(tmp(1));
end
end
TmpFileList{2}(ii).scientaEkin_eV = [TmpFileList{2}(ii).scientaEkinRange_eV(:,1):TmpFileList{2}(ii).scientaEkinStep_eV(1):TmpFileList{2}(ii).scientaEkinRange_eV(:,2)]';
end
TmpFileList{2}(ii).scientaEkin_eV = { ...
(TmpFileList{2}(ii).scientaEkinRange_eV(1) : ...
TmpFileList{2}(ii).scientaEkinStep_eV : ...
TmpFileList{2}(ii).scientaEkinRange_eV(2))' };
end
clearvars tmp
% read I_0
@@ -1202,8 +1208,7 @@ for ii = 1:size(TmpFileList{3},1) % do this for each picture in the list
TmpFileList{3}(ii).lastModifiedDatestr = TmpFileList{3}(ii).date;
end
%% Merge lists into one
%
%% Merge lists into one
ListFiles = table();
@@ -1473,6 +1478,9 @@ if istable(p.Results.DataTable) % do this only if previous list was input to fun
ListFiles = outerjoinRowToTable(p.Results.DataTable, ListFiles);
% Then, we sort the table based on the time (datenum)
ListFiles = sortrows(ListFiles,'lastModifiedDatenum');
% check fromat
assert(iscell(ListFiles.xRayEkin_eV), 'xRayEkin_eV must be cell')
assert(iscell(ListFiles.scientaEkin_eV), 'scientaEkin_eV must be cell')
end
%% Export
+91 -51
View File
@@ -1,20 +1,25 @@
function dataTable = importJeti(filename)
function dataTable = importJeti(filename, selectedDatasets)
%IMPORTJETI Import JETI SPECBOS irradiance data (CSV or TXT format)
% DATATABLE = IMPORTJETI(FILENAME) reads data from text file FILENAME.
% DATATABLE = IMPORTJETI(FILENAME) reads all data from the text file.
%
% DATATABLE = IMPORTJETI(FILENAME, SELECTEDDATASETS) reads only the
% Wavelength and the specific dataset indices you request.
% Example: table = importJeti("20260421_1500_Jeti.csv", [1, 5, 10]);
%
% Supports:
% 1. CSV: Semicolon-separated with header metadata
% 1. CSV: Auto-detects comma (,) or semicolon (;) delimiters, handles
% various Date/Time formats, and flexible header lengths.
% 2. isd: txt-style format with [sections] and tab-separated data
%
% Example:
% table = importJeti("20251212_1606_Jeti.csv");
% table = importJeti("spectrum_#2.isd");
if nargin < 2
selectedDatasets = []; % Empty means import all
end
%% Determine file type and route accordingly
[~, ~, ext] = fileparts(filename);
if strcmpi(ext, '.csv')
dataTable = importCSV(filename);
dataTable = importCSV(filename, selectedDatasets);
elseif strcmpi(ext, '.isd')
dataTable = importTXT(filename);
else
@@ -24,13 +29,27 @@ end
end
%% ========== CSV IMPORT FUNCTION ==========
function dataTable = importCSV(filename)
function dataTable = importCSV(filename, selectedDatasets)
fid = fopen(filename, 'r');
if fid == -1
error('Cannot open file: %s', filename);
end
% 1. Auto-detect Delimiter
fileDelimiter = ','; % default
while ~feof(fid)
thisLine = fgetl(fid);
if contains(thisLine, ';')
fileDelimiter = ';';
break;
elseif contains(thisLine, ',')
fileDelimiter = ',';
break;
end
end
frewind(fid); % Reset back to beginning of file
metadata = struct();
metadata.numDatasets = NaN;
metadata.numPoints = NaN;
@@ -39,7 +58,9 @@ metadata.Time = {};
lineIdx = 0;
dataHeaderLineIdx = NaN;
nHeaderCols = NaN;
% 2. Parse Metadata and find Wavelength header
while true
thisLine = fgetl(fid);
if ~ischar(thisLine)
@@ -47,18 +68,20 @@ while true
end
lineIdx = lineIdx + 1;
% Detect start of data header line
if contains(thisLine, 'Wavelength [nm]')
dataHeaderLineIdx = lineIdx;
break
end
parts = strsplit(thisLine, ';');
parts = strsplit(thisLine, fileDelimiter);
if isempty(parts)
continue
end
% Detect start of data header line (usually 'Wavelength [nm]')
if contains(parts{1}, 'Wavelength', 'IgnoreCase', true) && ...
~contains(parts{1}, 'Start Wavelength') && ...
~contains(parts{1}, 'End Wavelength')
dataHeaderLineIdx = lineIdx;
nHeaderCols = numel(parts);
break
end
key = strtrim(parts{1});
switch key
@@ -72,47 +95,35 @@ while true
end
case 'Date'
if numel(parts) >= 2
% Remove empty trailing cells
metadata.Date = parts(2:end);
metadata.Date = metadata.Date(~cellfun('isempty', metadata.Date));
end
case 'Time'
if numel(parts) >= 2
metadata.Time = parts(2:end);
metadata.Time = metadata.Time(~cellfun('isempty', metadata.Time));
end
end
end
if isnan(dataHeaderLineIdx)
fclose(fid);
error('Could not find data header line in CSV.');
error('Could not find Wavelength data header line in CSV.');
end
firstDataLineInFile = dataHeaderLineIdx + 1;
fclose(fid);
% Derive number of columns:
% If 'Number of datasets' is present: use it.
% Otherwise infer from the header line (new Spec1/Spec2 format).[file:1]
fid = fopen(filename, 'r');
if fid == -1
error('Cannot reopen file: %s', filename);
end
headerLine = '';
for k = 1:dataHeaderLineIdx
headerLine = fgetl(fid);
end
fclose(fid);
headerParts = strsplit(headerLine, ';');
nHeaderCols = numel(headerParts);
% 3. Determine column structures
if isnan(metadata.numDatasets)
% New format: first column is wavelength, remaining are datasets
% Infer from header columns (Subtract 1 for Wavelength column)
metadata.numDatasets = nHeaderCols - 1;
end
numCols = 1 + metadata.numDatasets;
% Build dataLines
% Build dataLines bounds
if isnan(metadata.numPoints)
dataLines = [firstDataLineInFile, Inf];
else
@@ -123,9 +134,9 @@ end
% Set up Import Options
opts = delimitedTextImportOptions("NumVariables", numCols);
opts.DataLines = dataLines;
opts.Delimiter = ";";
opts.Delimiter = fileDelimiter;
% Build variable names with units
% 4. Build Variable Names
varNames = cell(1, numCols);
varNames{1} = "Wavelength [nm]";
@@ -134,13 +145,9 @@ if ~isempty(metadata.Date) && ~isempty(metadata.Time)
for k = 1:nMeta
dstr = strtrim(metadata.Date{k});
tstr = strtrim(metadata.Time{k});
try
% JETI CSV: e.g. 12/18/2025 and 10:40:14 for 12h clock.[file:1]
dt = datetime([dstr ' ' tstr], ...
'InputFormat', 'MM/dd/yyyy HH:mm:ss');
timestr = datestr(dt, 'yyyymmdd_HHMMss');
catch
timestr = parseJetiDateTime(dstr, tstr);
if isempty(timestr)
timestr = sprintf('dataset%02d', k);
end
@@ -162,10 +169,48 @@ opts.ExtraColumnsRule = "ignore";
opts.EmptyLineRule = "read";
opts.VariableNamingRule = "preserve";
% Handle Specific Selected Columns
if ~isempty(selectedDatasets)
colsToKeep = [1, selectedDatasets + 1];
% Prevent out-of-bounds error
colsToKeep = colsToKeep(colsToKeep <= numCols);
opts.SelectedVariableNames = opts.VariableNames(colsToKeep);
end
% 5. Read the Table
dataTable = readtable(filename, opts);
end
%% ========== DATETIME PARSER HELPER ==========
function timestr = parseJetiDateTime(dstr, tstr)
% Tries several Date/Time formats to safely parse standard 24h, 12h(am/pm),
% and regional variations (dd.MM.yyyy vs MM/dd/yyyy).
combinedStr = [dstr ' ' tstr];
formatsToTry = {
'MM/dd/yyyy hh:mm:ssa', ... % US 12-hour (03:05:17pm)
'dd.MM.yyyy HH:mm:ss', ... % EU 24-hour (12.12.2025 15:54:00)
'MM/dd/yyyy HH:mm:ss', ... % US 24-hour
'dd/MM/yyyy HH:mm:ss', ... % EU slash 24-hour
'yyyy-MM-dd HH:mm:ss' % ISO 8601
};
timestr = '';
for i = 1:numel(formatsToTry)
try
dt = datetime(combinedStr, 'InputFormat', formatsToTry{i});
if ~isnat(dt)
timestr = datestr(dt, 'yyyymmdd_HHMMss');
return;
end
catch
% Format mismatched, try the next one
continue;
end
end
end
%% ========== TXT IMPORT FUNCTION ==========
function dataTable = importTXT(filename)
@@ -194,13 +239,11 @@ while true
end
lineIdx = lineIdx + 1;
% Check for data section marker
if strcmp(strtrim(thisLine), 'Data')
dataStartLineIdx = lineIdx + 1;
break
end
% Parse key=value pairs
if contains(thisLine, '=')
parts = strsplit(thisLine, '=');
if numel(parts) == 2
@@ -234,13 +277,11 @@ end
fclose(fid);
% Set up import options for tab-separated data
opts = delimitedTextImportOptions("NumVariables", 2);
opts.DataLines = [dataStartLineIdx, Inf];
opts.Delimiter = '\t';
opts.VariableNamingRule = "preserve";
% Build column names as string array
if ~isempty(metadata.XUnit) && ~isempty(metadata.YUnit)
varNames = string({
sprintf("Wavelength [%s]", metadata.XUnit)
@@ -257,5 +298,4 @@ opts.VariableNames = varNames;
opts.VariableTypes = ["double", "double"];
dataTable = readtable(filename, opts);
end
end
+148 -166
View File
@@ -21,48 +21,6 @@ function exportFigureData(figHandle)
% For non-tiled figures:
% <FigureTitle>_<YLabel>_vs_<XLabel>_<LineLabel>.txt
% Each file contains two columns: X-data and Y-data (tab-delimited)
%
% NOTES:
% - Works with line plots (line objects) and scatter plots (scatter objects)
% - Handles both regular axes and tiled layouts
% - Data outside the x-axis scale limits are excluded
% - Lines without legend entries are named as 'line_N' where N is a counter
% - Scatter plots without legend entries are named as 'scatter_N'
% - Axis labels default to 'x' and 'y' if not set
% - Tile titles are included only for tiled layouts
% - If no figure title is set, 'figure' is used as the base name
%
% EXAMPLE:
% % Create a tiled figure with multiple subplots
% figure
% tiledlayout(2, 1, 'TileSpacing', 'compact')
%
% nexttile
% x = 0:0.1:10;
% plot(x, sin(x), 'DisplayName', 'sin(x)')
% hold on
% plot(x, cos(x), 'DisplayName', 'cos(x)')
% title('Sine and Cosine')
% xlabel('Time (s)')
% ylabel('Amplitude')
% legend
%
% nexttile
% plot(x, sin(x).*cos(x), 'DisplayName', 'sin*cos')
% title('Product')
% xlabel('Time (s)')
% ylabel('Value')
% legend
%
% sgtitle('Trigonometric Functions')
% exportFigureData(gcf)
%
% % Creates files like:
% % Trigonometric_Functions_Sine_and_Cosine_Amplitude_vs_Time_(s)_sin(x).txt
% % Trigonometric_Functions_Sine_and_Cosine_Amplitude_vs_Time_(s)_cos(x).txt
% % Trigonometric_Functions_Product_Value_vs_Time_(s)_sin*cos.txt
%
% See also: tiledlayout, nexttile, legend, line, scatter
% Handle input: use current figure if none provided
if nargin == 0 || isempty(figHandle)
@@ -106,45 +64,14 @@ for ii = 1:length(allAxes)
continue
end
% Get axis titles
xLabel = ax.XLabel.String;
yLabel = ax.YLabel.String;
tileTitle = ax.Title.String;
% Convert to cell if char, then handle empty cases
if ischar(xLabel), xLabel = cellstr(xLabel); end
if ischar(yLabel), yLabel = cellstr(yLabel); end
if ischar(tileTitle), tileTitle = cellstr(tileTitle); end
if isempty(xLabel) || (iscell(xLabel) && isempty(xLabel{1}))
xLabel = 'x';
% Check if this axes uses yyaxis
isYYAxis = isprop(ax, 'YAxis') && numel(ax.YAxis) == 2;
if isYYAxis
sidesToProcess = {'left', 'right'};
else
xLabel = xLabel{1};
sidesToProcess = {'single'};
end
if isempty(yLabel) || (iscell(yLabel) && isempty(yLabel{1}))
yLabel = 'y';
else
yLabel = yLabel{1};
end
if isempty(tileTitle) || (iscell(tileTitle) && isempty(tileTitle{1}))
tileTitle = '';
else
tileTitle = tileTitle{1};
end
% Sanitize axis labels and tile title
xLabel = sanitizeFilename(xLabel);
yLabel = sanitizeFilename(yLabel);
tileTitle = sanitizeFilename(tileTitle);
% Get x-axis limits for filtering
xLims = ax.XLim;
% Get all children of the axes (lines, scatter, etc.)
children = get(ax, 'Children');
% Build a legend object if it exists (for extracting labels)
legendObj = legend(ax);
legendLabels = {};
@@ -158,99 +85,156 @@ for ii = 1:length(allAxes)
% Initialize a counter for matching legend entries to graphics objects
graphicsIndex = 0;
% Loop over all child graphics objects
for jj = 1:length(children)
obj = children(jj);
% Loop through both sides if yyaxis is used, otherwise process normally once
for s = 1:length(sidesToProcess)
% Check if this is a line object
if isa(obj, 'matlab.graphics.chart.primitive.Line')
graphicsIndex = graphicsIndex + 1;
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Skip if empty
if isempty(xData) || isempty(yData)
continue
end
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label: use legend entry or auto-generate name
if graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
lineCounter = lineCounter + 1;
label = sprintf('line_%d', lineCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
if isYYAxis
yyaxis(ax, sidesToProcess{s}); % Switch active side to expose its lines and label
end
% Check if this is a scatter object
elseif isa(obj, 'matlab.graphics.chart.primitive.Scatter')
graphicsIndex = graphicsIndex + 1;
% Get axis titles (now correctly tied to the active side)
xLabel = ax.XLabel.String;
yLabel = ax.YLabel.String;
tileTitle = ax.Title.String;
% Convert to cell if char, then handle empty cases
if ischar(xLabel), xLabel = cellstr(xLabel); end
if ischar(yLabel), yLabel = cellstr(yLabel); end
if ischar(tileTitle), tileTitle = cellstr(tileTitle); end
if isempty(xLabel) || (iscell(xLabel) && isempty(xLabel{1}))
xLabel = 'x';
else
xLabel = xLabel{1};
end
if isempty(yLabel) || (iscell(yLabel) && isempty(yLabel{1}))
yLabel = 'y';
else
yLabel = yLabel{1};
end
if isempty(tileTitle) || (iscell(tileTitle) && isempty(tileTitle{1}))
tileTitle = '';
else
tileTitle = tileTitle{1};
end
% Sanitize axis labels and tile title
xLabel = sanitizeFilename(xLabel);
yLabel = sanitizeFilename(yLabel);
tileTitle = sanitizeFilename(tileTitle);
% Get x-axis limits for filtering
xLims = ax.XLim;
% Get all children of the active side
children = get(ax, 'Children');
% Reverse so we extract in creation order to match the legend
if ~isempty(children)
children = flipud(children(:));
end
% Loop over all child graphics objects for this side
for jj = 1:length(children)
obj = children(jj);
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Check if this is a line object
if isa(obj, 'matlab.graphics.chart.primitive.Line')
graphicsIndex = graphicsIndex + 1;
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Skip if empty
if isempty(xData) || isempty(yData)
continue
end
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label: prefer internal DisplayName to fix yyaxis mismatch
label = '';
if isprop(obj, 'DisplayName') && ~isempty(obj.DisplayName)
label = obj.DisplayName;
elseif graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
lineCounter = lineCounter + 1;
label = sprintf('line_%d', lineCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
% Skip if empty
if isempty(xData) || isempty(yData)
continue
% Check if this is a scatter object
elseif isa(obj, 'matlab.graphics.chart.primitive.Scatter')
graphicsIndex = graphicsIndex + 1;
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Skip if empty
if isempty(xData) || isempty(yData)
continue
end
% Ensure row vectors for indexing
xData = xData(:)';
yData = yData(:)';
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label
label = '';
if isprop(obj, 'DisplayName') && ~isempty(obj.DisplayName)
label = obj.DisplayName;
elseif graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
scatterCounter = scatterCounter + 1;
label = sprintf('scatter_%d', scatterCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
end
% Ensure row vectors for indexing
xData = xData(:)';
yData = yData(:)';
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label: use legend entry or auto-generate name
if graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
scatterCounter = scatterCounter + 1;
label = sprintf('scatter_%d', scatterCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
end
end
end
fprintf('Data export complete.\n');
end
@@ -262,7 +246,6 @@ function cleanStr = sanitizeFilename(str)
%
% Replaces spaces, slashes, backslashes, colons, asterisks, parentheses,
% and other problematic characters with underscores.
cleanStr = str;
cleanStr = strrep(cleanStr, ' ', '_');
cleanStr = strrep(cleanStr, '/', '_');
@@ -277,5 +260,4 @@ cleanStr = strrep(cleanStr, '|', '_');
cleanStr = strrep(cleanStr, '(', '_');
cleanStr = strrep(cleanStr, ')', '_');
cleanStr = strrep(cleanStr, '.', '_'); % Optional: replace dots
end
end