diff --git a/import/LabView_importData.m b/beamtime/LabView_importData.m similarity index 58% rename from import/LabView_importData.m rename to beamtime/LabView_importData.m index ce8dab9..842961f 100644 --- a/import/LabView_importData.m +++ b/beamtime/LabView_importData.m @@ -1,7 +1,7 @@ function LabView = LabView_importData(fullFileName,varargin) % % LabView_importData reads data log ASCII files stored by LabView into -% a Matlab time series. +% a Matlab time series. % % LabView = LabView_importData(A) imports file A % and stores its content as time series in a structure called "LabView". @@ -77,6 +77,64 @@ addOptional(p,'appendToStructure',defaultAppendToStructure,@isstruct); % Parse the inputs. parse(p,fullFileName,varargin{:}) +%% ===================================================================== +% NEW FILE FORMAT (added 2026-07-02) +% --------------------------------------------------------------------- +% Detect the mid-2026 LabView format from its header and import it here, +% then return early so none of the older-format logic below is touched. +% New file: ISO-8859-1 encoded, 2 header lines, tab-delimited, 16 data +% columns + trailing tab, CRLF. The temperature column is written +% corrupted as "NaN" (e.g. "-8NaN"); the leading integer is the real +% ice-sample temperature. Channels no longer logged are filled with NaN so +% the output keeps the same 13-column ts.Data layout, field naming and +% .Name as older files. +% col 3 pressure_cell (hPa) -> ts.Data:4 col 4 temperature -> ts.Data:1 +% col 5-7 x/y/z (mm) -> ts.Data:10-12 col 8 Tsealing -> ts.Data:13 +% col 9 flow cryo (l/min) -> ts.Data:7 +% ===================================================================== +keepTemperatureInteger = true; % true: use leading integer of "-8NaN"; false: NaN + +if isstruct(p.Results.appendToStructure) + LabView = p.Results.appendToStructure; +end + +fidChk = fopen(p.Results.fullFileName,'r','n','ISO-8859-1'); +hdrLine1 = fgetl(fidChk); +hdrLine2 = fgetl(fidChk); +fclose(fidChk); + +if (ischar(hdrLine2) && contains(hdrLine2,'pressure_cell')) || ... + (ischar(hdrLine1) && contains(hdrLine1,'gas name')) + + fid = fopen(p.Results.fullFileName,'r','n','ISO-8859-1'); + data = textscan(fid, ... + '%s %s %f %s %f %f %f %f %f %f %f %f %f %f %f %f %*s', ... + 'HeaderLines', 2, 'Delimiter', '\t', 'EndOfLine', '\r\n'); + fclose(fid); + + if keepTemperatureInteger + temperature = cellfun(@(s) str2double(regexp(s,'^-?\d+','match','once')), data{4}); + else + temperature = nan(numel(data{4}),1); + end + + nanCol = nan(numel(data{3}),1); + + tsName = ['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})], ... + 'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]; + + LabView.(tsName) = timeseries( ... + [ temperature nanCol nanCol data{3} nanCol nanCol data{9} ... + nanCol nanCol data{5} data{6} data{7} data{8} ], ... + datestr(datenum([char(data{1}), char(data{2})],'dd.mm.yyyyHH:MM:SS'),0)); + LabView.(tsName).Name = 'Readout Napp Chamber raw signal'; + + clearvars fid data temperature nanCol tsName hdrLine1 hdrLine2 fidChk ... + keepTemperatureInteger + return +end +%% ===================================================================== + %% % get filedate [~,name,~] = fileparts(p.Results.fullFileName); @@ -93,7 +151,7 @@ if isstruct(p.Results.appendToStructure) end %% if datenum(filedate) <= datenum('04-Mar-2019 13:30:00') - + fid = fopen(p.Results.fullFileName); data = textscan(fid, ... '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... @@ -109,49 +167,73 @@ if datenum(filedate) <= datenum('04-Mar-2019 13:30:00') = 'Readout Napp Chamber raw signal'; clear fid ans formatOut nans t x -elseif datenum(filedate) >= datenum('15-Sep-2021 01:00:00') - +elseif datenum(filedate) >= datenum('15-Sep-2021 01:00:00') && datenum(filedate) <= datenum('01-Apr-2023 01:00:00') + % during this time, date and time were in american format. fid = fopen(p.Results.fullFileName); data = textscan(fid, ... '%s %s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); % - + 'HeaderLines',1); % + fclose(fid); % the date formating in the dat file is really messed up. Try to make % it consitent with MATLAB. The problem is that MATLAB does not % understand 1 digit month and hours. % - % + % % Split the date in column 1 into year,month,day newDateSplit = cellfun(@(x) split(char(x),'/'),data{1},'UniformOutput', false); % Split the time in column 2 into hours, minutes, seconds newTimeSplit = cellfun(@(x) split(char(x),':'),data{2},'UniformOutput', false); % combine both with the am/pm string in column 3 to a new datestring customDateStr = cellfun(@(x,y,z) [datestr(datenum([str2double(x{3}),str2double(x{1}),str2double(x{2}),str2double(y{1}),str2double(y{2}),str2double(y{3})])), ' ', char(z)], newDateSplit,newTimeSplit,data{3}, 'uniformOutput',false) ; - % define time series + + % define time series LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]) ... = timeseries([data{10} data{4} data{5} data{6} data{12} ... - data{13} data{14} data{7} data{8} data{17} data{18} data{19} data{21}], ... + data{13} data{14} data{7} data{8} data{17} data{18} data{19} data{21}], ... customDateStr); LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]).Name ... = 'Readout Napp Chamber raw signal'; clearvars fid ans formatOut nans t x new* custom* + + +elseif datenum(filedate) >= datenum('01-Apr-2023 02:00:00') + + fid = fopen(p.Results.fullFileName); + data = textscan(fid, ... + '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... + 'HeaderLines',1); + % data file has 2 text columns, followed by 18 numeric columns (THE AM + % PM IS MISSING and some changes to the LabView file. Confirm with Luca....) + fclose(fid); + % define time series + + LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... + = timeseries([data{9} data{3} data{4} data{5} data{11} ... + data{12} data{20} data{6} data{7} data{15} data{16} data{17} data{19}] , ... + datestr(datenum([char(data{1}), char(data{2})],... + 'dd.mm.yyyyHH:MM:SS'),0)); + + LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... + = 'Readout Napp Chamber raw signal'; + clear fid ans formatOut nans t x + else fid = fopen(p.Results.fullFileName); data = textscan(fid, ... '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); % data file has 2 text columns, followed by 16 numeric columns + 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns fclose(fid); % define time series - - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... - = timeseries([data{9} data{3} data{4} data{5} data{11} ... - data{12} data{13} data{6} data{7} data{16} data{17} data{18} data{20}] , ... - datestr(datenum([char(data{1}), char(data{2})],... - 'dd.mm.yyyyHH:MM:SS'),0)); - + + LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... + = timeseries([data{9} data{3} data{4} data{5} data{11} ... + data{12} data{13} data{6} data{7} data{16} data{17} data{18} data{20}] , ... + datestr(datenum([char(data{1}), char(data{2})],... + 'dd.mm.yyyyHH:MM:SS'),0)); + LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... = 'Readout Napp Chamber raw signal'; clear fid ans formatOut nans t x end -end \ No newline at end of file +end diff --git a/beamtime/createBeamtimeFolder.m b/beamtime/createBeamtimeFolder.m index 9c73b63..e94ddcc 100644 --- a/beamtime/createBeamtimeFolder.m +++ b/beamtime/createBeamtimeFolder.m @@ -29,7 +29,7 @@ function createBeamtimeFolder(projectName) % List of subfolders to create subFolders = {'Analysis', 'Images', 'LabBook', 'NEXAFS', ... - 'Photos', 'pressure', 'PShell', 'RGA', 'SES'}; + 'Photos', 'Pressure', 'PShell', 'RGA', 'SES'}; % Create main project folder if ~exist(projectRoot, 'dir') diff --git a/beamtime/documentedExampleScripts/TestAndDevelopShirley.m b/beamtime/documentedExampleScripts/TestAndDevelopShirley.m index d9e0d0e..8b71830 100644 --- a/beamtime/documentedExampleScripts/TestAndDevelopShirley.m +++ b/beamtime/documentedExampleScripts/TestAndDevelopShirley.m @@ -140,7 +140,7 @@ tmpFitParameters.backgroundRange = [12 6]; % <- selects fit range aro % Bounds for background tmpFitParameters.bkgrd = [0 0]; tmpFitParameters.pbkgrdLowerBound = [0 0]; -tmpFitParameters.bkgrdUpperBound = [0 0]; +tmpFitParameters.bkgrdUpperBound = [0 0]; FitList = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... 'tableVariable','spectrum_countsNorm','energyScale','kinetic',... @@ -283,6 +283,63 @@ tmpFitParameters.backgroundRange = [12 6]; FitList = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... 'tableVariable','spectrum_counts','energyScale','binding',... 'mode','unconstrained','backgroundMode','linear','showFigures','on'); + +%% TEST 1.5.3: Split unconstrained binding Gauss linear +% Gaussian + Linear, individual area/width/position per peak + + +% peak #1 -> Write into FitList +clearvars tmpFitParameters; +clc +tmpFitParameters.peakArea = 1e5; +tmpFitParameters.peakWidth = 1.6; +tmpFitParameters.peakPos = 38; +% Bounds for areas (lower and upper) +tmpFitParameters.peakAreaLowerBound = 0.01; +tmpFitParameters.peakAreaUpperBound = Inf; +% Bounds for widths +tmpFitParameters.peakWidthLowerBound = 0.2; +tmpFitParameters.peakWidthUpperBound = 2.0; +% Bounds for positions +tmpFitParameters.peakPosLowerBound = 37; +tmpFitParameters.peakPosUpperBound = 39; +% fit range +tmpFitParameters.backgroundRange = [1 5]; +% fit +FitList = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... + 'tableVariable','spectrum_counts','energyScale','binding',... % <- process binding energy + 'mode','unconstrained','backgroundMode','linear','showFigures','on'); +% peak #2 -> Write into new list, then copy to FitList +clearvars tmpFitParameters; +clc +tmpFitParameters.peakArea = 1e5; +tmpFitParameters.peakWidth = 1.6; +tmpFitParameters.peakPos = 36; +% Bounds for areas (lower and upper) +tmpFitParameters.peakAreaLowerBound = 0.01; +tmpFitParameters.peakAreaUpperBound = Inf; +% Bounds for widths +tmpFitParameters.peakWidthLowerBound = 0.2; +tmpFitParameters.peakWidthUpperBound = 2.0; +% Bounds for positions +tmpFitParameters.peakPosLowerBound = 35; +tmpFitParameters.peakPosUpperBound = 37; +% fit range +tmpFitParameters.backgroundRange = [5 1]; +% fit +FitListTmp = nappFitGaussXps(FitList,tmpFitParameters,tmpDataSelection,... + 'tableVariable','spectrum_counts','energyScale','binding',... % <- process binding energy + 'mode','unconstrained','backgroundMode','linear','showFigures','on'); + +% merge fit results +disp(FitList(tmpDataSelection,{'name','peakPosition'})) +disp(FitListTmp(tmpDataSelection,{'name','peakPosition'})) +FitList(tmpDataSelection,:).peakPosition(2) = FitListTmp(tmpDataSelection,:).peakPosition(1); +FitList(tmpDataSelection,:).peakArea(2) = FitListTmp(tmpDataSelection,:).peakArea(1); +FitList(tmpDataSelection,:).peakFWHM(2) = FitListTmp(tmpDataSelection,:).peakFWHM(1); +clearvars FitListTmp +disp(FitList(tmpDataSelection,{'name','peakPosition'})) + %% TEST 1.6: Unconstrained binding PV linear % Pseudo-Voigt + Linear, individual area/width/eta/position per peak diff --git a/beamtime/nappFileList.m b/beamtime/nappFileList.m index c9d1ee1..21dcc44 100755 --- a/beamtime/nappFileList.m +++ b/beamtime/nappFileList.m @@ -1281,7 +1281,16 @@ if matches(p.Results.importLabView,'on') try % Get list of all files in that folder - TmpFileList = dir([dataPath_LabView filesep '**' filesep 'logfile*.txt']); + TmpFileList = [ ... + dir([dataPath_LabView filesep '**' filesep 'logfile*.txt']); ... + dir([dataPath_LabView filesep '**' filesep '*datafile.txt'])]; + if ~isempty(TmpFileList) + keep = ~startsWith({TmpFileList.name}, {'.','~'}) & ... + ~startsWith({TmpFileList.name}, 'LabView_T_RH', 'IgnoreCase', true); + TmpFileList = TmpFileList(keep); + [~, iu] = unique(fullfile({TmpFileList.folder}, {TmpFileList.name}), 'stable'); + TmpFileList = TmpFileList(iu); + end % import LabView data LabView = LabView_importData(fullfile(TmpFileList(1).folder,TmpFileList(1).name)); % Overwrite LabView data in workspace, if this is the first import @@ -1509,187 +1518,187 @@ disp('*************************************************') end -function LabView = LabView_importData(fullFileName,varargin) -% -% LabView_importData reads data log ASCII files stored by LabView into -% a Matlab time series. -% -% LabView = LabView_importData(A) imports file A -% and stores its content as time series in a structure called "LabView". -% -% LabView = LabView_importData(A,LabView) imports file A -% and appends its content as time series to the LabView structure. -% -% -% Example -% LabView = LabView_importData('exmpl.dat',LabView) -% -% Input -% * Napp_chamber_file: Complete and absolute path to the Napp chamber log file. -% * LabView: name of output structure. Always call this'LabView' -% -% Output -% * LabView: time series, the name starting with "ts" -% followed by date and time the recording started. -% -% See also LABVIEW_COMBINE, LABVIEW_PLOT, LABVIEW_CUTDATA -% -%% Data Structure -% -% Data structure before 4.3.2019 -% col 1: Date -% col 2: Time -% col 3: Reservoir "Pressure 1" - Unit "1 water" - mbar -> ts.Data:2 -% col 4: Reservoir "Pressure 2" - Unit "2 other" - mbar -> ts.Data:3 -% col 5: Baratron 1 - cell - 1mbar max - mbar -> ts.Data:4 -% col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8 -% col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9 -% col 8: Baratron 4 - - mbar -% col 9: Temp.Ice Sample - °C -> ts.Data:1 -% col 10: Temp.Heated Sample -% col 11: none -% col 12: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5 -% col 13: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6 -% col 14: Flow He(cryoholder) - l/min -> ts.Data:7 -% col 15: none -% col 16: none -% -% Data structure after 4.3.2019 -% col 1: Date -% col 2: Time -% col 3: Reservoir "Pressure 1" - Unit "1 water" - mbar -> ts.Data:2 -% col 4: Reservoir "Pressure 2" - Unit "2 other" - mbar -> ts.Data:3 -% col 5: Baratron 1 - cell - 1mbar max - mbar -> ts.Data:4 -% col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8 -% col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9 -% col 8: Baratron 4 - - mbar -% col 9: Temp.Ice Sample - °C -> ts.Data:1 -% col 10: Temp.Heated Sample -% col 11: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5 -% col 12: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6 -% col 13: Flow He(cryoholder) - l/min -> ts.Data:7 -% col 14: Flow gas 1 -% col 15: Pressure chamber - mbar -% col 16: x-position of cryoholder - mm -> ts.Data:10 -% col 17: y-position of cryoholder - mm -> ts.Data:11 -% col 18: z-position of cryoholder - mm -> ts.Data:12 -% col 19: nothing -% col 20: Temp.Sealing - °C -> ts.Data:13 - - -%% - -% Create an InputParser object -p = inputParser; -% Add inputs to the scheme. -defaultAppendToStructure = 0; -addRequired(p,'fullFileName',@isfile); -addOptional(p,'appendToStructure',defaultAppendToStructure,@isstruct); -% Parse the inputs. -parse(p,fullFileName,varargin{:}) - -%% -% get filedate -[~,name,~] = fileparts(p.Results.fullFileName); -tmp = strsplit(name,'_PC'); -filedate = datenum(tmp{1},'yyyy-mm-dd_HH.MM.SS'); - -clearvars name tmp - -%% -% parse structure to append to - -if isstruct(p.Results.appendToStructure) - LabView = p.Results.appendToStructure; -end -%% -if datenum(filedate) <= datenum('04-Mar-2019 13:30:00') - - fid = fopen(p.Results.fullFileName); - data = textscan(fid, ... - '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns - fclose(fid); - % define time series - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... - = timeseries([data{9} data{3} data{4} data{5} data{12} ... - data{13} data{14} data{6} data{7}] , ... - datestr(datenum([char(data{1}), char(data{2})],... - 'dd.mm.yyyyHH:MM:SS'),0)); - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... - = 'Readout Napp Chamber raw signal'; - clear fid ans formatOut nans t x - -elseif datenum(filedate) >= datenum('15-Sep-2021 01:00:00') && datenum(filedate) <= datenum('01-Apr-2023 01:00:00') - % during this time, date and time were in american format. - fid = fopen(p.Results.fullFileName); - data = textscan(fid, ... - '%s %s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); % - - fclose(fid); - % the date formating in the dat file is really messed up. Try to make - % it consitent with MATLAB. The problem is that MATLAB does not - % understand 1 digit month and hours. - % - % - % Split the date in column 1 into year,month,day - newDateSplit = cellfun(@(x) split(char(x),'/'),data{1},'UniformOutput', false); - % Split the time in column 2 into hours, minutes, seconds - newTimeSplit = cellfun(@(x) split(char(x),':'),data{2},'UniformOutput', false); - % combine both with the am/pm string in column 3 to a new datestring - customDateStr = cellfun(@(x,y,z) [datestr(datenum([str2double(x{3}),str2double(x{1}),str2double(x{2}),str2double(y{1}),str2double(y{2}),str2double(y{3})])), ' ', char(z)], newDateSplit,newTimeSplit,data{3}, 'uniformOutput',false) ; - - % define time series - LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]) ... - = timeseries([data{10} data{4} data{5} data{6} data{12} ... - data{13} data{14} data{7} data{8} data{17} data{18} data{19} data{21}], ... - customDateStr); - LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]).Name ... - = 'Readout Napp Chamber raw signal'; - clearvars fid ans formatOut nans t x new* custom* - - -elseif datenum(filedate) >= datenum('01-Apr-2023 02:00:00') - - fid = fopen(p.Results.fullFileName); - data = textscan(fid, ... - '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); - % data file has 2 text columns, followed by 18 numeric columns (THE AM - % PM IS MISSING and some changes to the LabView file. Confirm with Luca....) - fclose(fid); - % define time series - - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... - = timeseries([data{9} data{3} data{4} data{5} data{11} ... - data{12} data{20} data{6} data{7} data{15} data{16} data{17} data{19}] , ... - datestr(datenum([char(data{1}), char(data{2})],... - 'dd.mm.yyyyHH:MM:SS'),0)); - - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... - = 'Readout Napp Chamber raw signal'; - clear fid ans formatOut nans t x - -else - fid = fopen(p.Results.fullFileName); - data = textscan(fid, ... - '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... - 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns - fclose(fid); - % define time series - - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... - = timeseries([data{9} data{3} data{4} data{5} data{11} ... - data{12} data{13} data{6} data{7} data{16} data{17} data{18} data{20}] , ... - datestr(datenum([char(data{1}), char(data{2})],... - 'dd.mm.yyyyHH:MM:SS'),0)); - - LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... - = 'Readout Napp Chamber raw signal'; - clear fid ans formatOut nans t x -end -end +% function LabView = LabView_importData(fullFileName,varargin) +% % +% % LabView_importData reads data log ASCII files stored by LabView into +% % a Matlab time series. +% % +% % LabView = LabView_importData(A) imports file A +% % and stores its content as time series in a structure called "LabView". +% % +% % LabView = LabView_importData(A,LabView) imports file A +% % and appends its content as time series to the LabView structure. +% % +% % +% % Example +% % LabView = LabView_importData('exmpl.dat',LabView) +% % +% % Input +% % * Napp_chamber_file: Complete and absolute path to the Napp chamber log file. +% % * LabView: name of output structure. Always call this'LabView' +% % +% % Output +% % * LabView: time series, the name starting with "ts" +% % followed by date and time the recording started. +% % +% % See also LABVIEW_COMBINE, LABVIEW_PLOT, LABVIEW_CUTDATA +% % +% %% Data Structure +% % +% % Data structure before 4.3.2019 +% % col 1: Date +% % col 2: Time +% % col 3: Reservoir "Pressure 1" - Unit "1 water" - mbar -> ts.Data:2 +% % col 4: Reservoir "Pressure 2" - Unit "2 other" - mbar -> ts.Data:3 +% % col 5: Baratron 1 - cell - 1mbar max - mbar -> ts.Data:4 +% % col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8 +% % col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9 +% % col 8: Baratron 4 - - mbar +% % col 9: Temp.Ice Sample - °C -> ts.Data:1 +% % col 10: Temp.Heated Sample +% % col 11: none +% % col 12: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5 +% % col 13: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6 +% % col 14: Flow He(cryoholder) - l/min -> ts.Data:7 +% % col 15: none +% % col 16: none +% % +% % Data structure after 4.3.2019 +% % col 1: Date +% % col 2: Time +% % col 3: Reservoir "Pressure 1" - Unit "1 water" - mbar -> ts.Data:2 +% % col 4: Reservoir "Pressure 2" - Unit "2 other" - mbar -> ts.Data:3 +% % col 5: Baratron 1 - cell - 1mbar max - mbar -> ts.Data:4 +% % col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8 +% % col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9 +% % col 8: Baratron 4 - - mbar +% % col 9: Temp.Ice Sample - °C -> ts.Data:1 +% % col 10: Temp.Heated Sample +% % col 11: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5 +% % col 12: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6 +% % col 13: Flow He(cryoholder) - l/min -> ts.Data:7 +% % col 14: Flow gas 1 +% % col 15: Pressure chamber - mbar +% % col 16: x-position of cryoholder - mm -> ts.Data:10 +% % col 17: y-position of cryoholder - mm -> ts.Data:11 +% % col 18: z-position of cryoholder - mm -> ts.Data:12 +% % col 19: nothing +% % col 20: Temp.Sealing - °C -> ts.Data:13 +% +% +% %% +% +% % Create an InputParser object +% p = inputParser; +% % Add inputs to the scheme. +% defaultAppendToStructure = 0; +% addRequired(p,'fullFileName',@isfile); +% addOptional(p,'appendToStructure',defaultAppendToStructure,@isstruct); +% % Parse the inputs. +% parse(p,fullFileName,varargin{:}) +% +% %% +% % get filedate +% [~,name,~] = fileparts(p.Results.fullFileName); +% tmp = strsplit(name,'_PC'); +% filedate = datenum(tmp{1},'yyyy-mm-dd_HH.MM.SS'); +% +% clearvars name tmp +% +% %% +% % parse structure to append to +% +% if isstruct(p.Results.appendToStructure) +% LabView = p.Results.appendToStructure; +% end +% %% +% if datenum(filedate) <= datenum('04-Mar-2019 13:30:00') +% +% fid = fopen(p.Results.fullFileName); +% data = textscan(fid, ... +% '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... +% 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns +% fclose(fid); +% % define time series +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... +% = timeseries([data{9} data{3} data{4} data{5} data{12} ... +% data{13} data{14} data{6} data{7}] , ... +% datestr(datenum([char(data{1}), char(data{2})],... +% 'dd.mm.yyyyHH:MM:SS'),0)); +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... +% = 'Readout Napp Chamber raw signal'; +% clear fid ans formatOut nans t x +% +% elseif datenum(filedate) >= datenum('15-Sep-2021 01:00:00') && datenum(filedate) <= datenum('01-Apr-2023 01:00:00') +% % during this time, date and time were in american format. +% fid = fopen(p.Results.fullFileName); +% data = textscan(fid, ... +% '%s %s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... +% 'HeaderLines',1); % +% +% fclose(fid); +% % the date formating in the dat file is really messed up. Try to make +% % it consitent with MATLAB. The problem is that MATLAB does not +% % understand 1 digit month and hours. +% % +% % +% % Split the date in column 1 into year,month,day +% newDateSplit = cellfun(@(x) split(char(x),'/'),data{1},'UniformOutput', false); +% % Split the time in column 2 into hours, minutes, seconds +% newTimeSplit = cellfun(@(x) split(char(x),':'),data{2},'UniformOutput', false); +% % combine both with the am/pm string in column 3 to a new datestring +% customDateStr = cellfun(@(x,y,z) [datestr(datenum([str2double(x{3}),str2double(x{1}),str2double(x{2}),str2double(y{1}),str2double(y{2}),str2double(y{3})])), ' ', char(z)], newDateSplit,newTimeSplit,data{3}, 'uniformOutput',false) ; +% +% % define time series +% LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]) ... +% = timeseries([data{10} data{4} data{5} data{6} data{12} ... +% data{13} data{14} data{7} data{8} data{17} data{18} data{19} data{21}], ... +% customDateStr); +% LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]).Name ... +% = 'Readout Napp Chamber raw signal'; +% clearvars fid ans formatOut nans t x new* custom* +% +% +% elseif datenum(filedate) >= datenum('01-Apr-2023 02:00:00') +% +% fid = fopen(p.Results.fullFileName); +% data = textscan(fid, ... +% '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... +% 'HeaderLines',1); +% % data file has 2 text columns, followed by 18 numeric columns (THE AM +% % PM IS MISSING and some changes to the LabView file. Confirm with Luca....) +% fclose(fid); +% % define time series +% +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... +% = timeseries([data{9} data{3} data{4} data{5} data{11} ... +% data{12} data{20} data{6} data{7} data{15} data{16} data{17} data{19}] , ... +% datestr(datenum([char(data{1}), char(data{2})],... +% 'dd.mm.yyyyHH:MM:SS'),0)); +% +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... +% = 'Readout Napp Chamber raw signal'; +% clear fid ans formatOut nans t x +% +% else +% fid = fopen(p.Results.fullFileName); +% data = textscan(fid, ... +% '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',... +% 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns +% fclose(fid); +% % define time series +% +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ... +% = timeseries([data{9} data{3} data{4} data{5} data{11} ... +% data{12} data{13} data{6} data{7} data{16} data{17} data{18} data{20}] , ... +% datestr(datenum([char(data{1}), char(data{2})],... +% 'dd.mm.yyyyHH:MM:SS'),0)); +% +% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ... +% = 'Readout Napp Chamber raw signal'; +% clear fid ans formatOut nans t x +% end +% end function data_structure = LabView_combine(data_structure) % LabView_combine can be used to merge time series into one. diff --git a/beamtime/plotCryoData.m b/beamtime/plotCryoData.m index cdd90df..acd1404 100644 --- a/beamtime/plotCryoData.m +++ b/beamtime/plotCryoData.m @@ -3,6 +3,18 @@ function ax = plotCryoData(tbl, tStart, tEnd) % % [ax] = plotCryoData(tbl, tStart, tEnd) % Returns struct with handles to the 3 axes for easy limit modification. +% +% The first input may be EITHER: +% * a table with the columns datetime, pressure_cell_hPa_, +% temperature__C_, x_mm_, y_mm_, z_mm_, flowCryo_l_min_, OR +% * a LabView structure produced by LabView_importData (optionally +% combined with LabView_combine). In that case it is converted to the +% required table on the fly (see the local function labviewToTable). + + %--- Accept a LabView timeseries structure and convert it to a table --- + if isstruct(tbl) + tbl = labviewToTable(tbl); + end %--- Extract datetime column --- dt = tbl.datetime; @@ -67,3 +79,86 @@ function ax = plotCryoData(tbl, tStart, tEnd) % Improve datetime formatting on x-axis ax.flow.XAxis.TickLabelFormat = 'dd-MMM HH:mm'; end + + +% ========================================================================= +% LOCAL FUNCTIONS +% ========================================================================= + +function tbl = labviewToTable(LabView, tsName) +%LABVIEWTOTABLE Convert a LabView cryo-log timeseries into a plotCryoData table. +% +% Uses LabView.merge if present (created by LabView_combine); otherwise the +% timeseries field named tsName, or the first timeseries in the structure. +% The mapping uses the FIXED ts.Data column order that LabView_importData +% produces for every file format: +% [IceTemp, ResP1, ResP2, cell/Baratron1, Cooler1, Cooler2, FlowHe, ... +% Baratron2, Baratron3, x, y, z, Tsealing] +% so it works for old and new files alike. Channels a given file does not +% contain (e.g. x/y/z in pre-2019 logs) are returned as NaN. + + %--- Select which timeseries to convert --- + if nargin >= 2 && ~isempty(tsName) + if ~isfield(LabView, tsName) + error('plotCryoData:noSuchField', ... + 'LabView has no timeseries field "%s".', tsName); + end + ts = LabView.(tsName); + elseif isfield(LabView, 'merge') + ts = LabView.merge; % combined log (preferred) + else + fn = fieldnames(LabView); + if isempty(fn) + error('plotCryoData:emptyStruct', ... + 'LabView structure contains no timeseries.'); + end + ts = LabView.(fn{1}); % single-file case + end + + %--- Absolute time as a datetime column --- + try + dt = datetime(getabstime(ts)); + catch + dt = datetime(ts.TimeInfo.StartDate) + ... + seconds(ts.Time * unitToSeconds(ts.TimeInfo.Units)); + end + dt = dt(:); + + %--- Pull channels by their fixed ts.Data column index --- + D = ts.Data; + + tbl = table( ... + dt, ... + getCol(D, 4), ... % pressure_cell (hPa) <- cell / Baratron-1 slot + getCol(D, 1), ... % temperature (degC) <- ice sample + getCol(D, 10), ... % x (mm) + getCol(D, 11), ... % y (mm) + getCol(D, 12), ... % z (mm) + getCol(D, 7), ... % flow cryo (l/min) <- He cryoholder + 'VariableNames', {'datetime', 'pressure_cell_hPa_', 'temperature__C_', ... + 'x_mm_', 'y_mm_', 'z_mm_', 'flowCryo_l_min_'}); +end + + +function c = getCol(D, k) +% Return column k of D, or a NaN column if that channel is absent +% (e.g. pre-2019 logs have only 9 columns, so no x/y/z). + if size(D, 2) >= k + c = D(:, k); + else + c = nan(size(D, 1), 1); + end +end + + +function s = unitToSeconds(u) +% Convert timeseries TimeInfo.Units to seconds (fallback path only). + switch lower(u) + case 'seconds', s = 1; + case 'minutes', s = 60; + case 'hours', s = 3600; + case 'days', s = 86400; + case 'milliseconds', s = 1e-3; + otherwise, s = 1; % assume seconds + end +end diff --git a/beamtime/plotRgaData.m b/beamtime/plotRgaData.m new file mode 100644 index 0000000..f86f2f8 --- /dev/null +++ b/beamtime/plotRgaData.m @@ -0,0 +1,67 @@ +function ax = plotRgaData(tbl, tStart, tEnd) +%PLOTRGADATA Plot RGA partial pressures vs time (companion to plotCryoData). +% +% ax = plotRgaData(tbl, tStart, tEnd) +% Plots every channel column of an rgaImportData table against its shared +% datetime axis. tStart/tEnd (datetime) optionally subset the range, just +% like plotCryoData. Returns a struct with the axes handle. +% +% By default the y-axis is linear (RGA scans contain small near-noise +% negatives that a log axis cannot show). To switch to the classic +% semilog view afterwards: +% set(ax.partial, 'YScale', 'log'); +% (values <= 0 are simply not drawn on a log axis). +% +% EXAMPLE +% tbl = rgaImportData('rga1_Jul_01_2026_08-23-06_AM.txt'); +% ax = plotRgaData(tbl); +% ax = plotRgaData(tbl, t1, t2); % zoom to a window +% +% See also RGAIMPORTDATA, PLOTCRYODATA + + %--- Extract datetime column --- + dt = tbl.datetime; + + %--- If start/stop provided, subset the table --- + if nargin >= 3 && ~isempty(tStart) && ~isempty(tEnd) + mask = dt >= tStart & dt <= tEnd; + tbl = tbl(mask, :); + dt = dt(mask); + end + + %--- Channel columns = everything except datetime & time_s --- + chVars = setdiff(tbl.Properties.VariableNames, {'datetime','time_s'}, 'stable'); + + units = 'a.u.'; + if isfield(tbl.Properties.UserData, 'units') + units = tbl.Properties.UserData.units; + end + + %--- Plot --- + figure; + tl = tiledlayout(1, 1); + tl.TileSpacing = 'compact'; + tl.Padding = 'compact'; + + ax.partial = nexttile; + hold on; + for k = 1:numel(chVars) + plot(dt, tbl.(chVars{k}), '-', 'LineWidth', 1, ... + 'DisplayName', prettyName(chVars{k})); + end + grid on; + ylabel(sprintf('Partial pressure [%s]', units)); + xlabel('Date & Time'); + title('RGA partial pressures'); + legend('Location', 'best', 'Interpreter', 'none'); + + % Match plotCryoData's datetime tick style + ax.partial.XAxis.TickLabelFormat = 'dd-MMM HH:mm'; +end + +% ------------------------------------------------------------------------- +function s = prettyName(v) +% "Water_m18" -> "Water (m18)" + s = regexprep(v, '_m(\d+)$', ' (m$1)'); + s = strrep(s, '_', ' '); +end diff --git a/import/rgaImportData.m b/import/rgaImportData.m new file mode 100644 index 0000000..eeead46 --- /dev/null +++ b/import/rgaImportData.m @@ -0,0 +1,97 @@ +function tbl = rgaImportData(fullFileName) +%RGAIMPORTDATA Import an SRS RGA "Pressure vs Time" scan (.txt) into a table. +% +% tbl = rgaImportData(fullFileName) reads an RGA ASCII log and returns a +% table ready for plotRgaData, with: +% datetime - absolute time (start time + Time(s)) +% time_s - elapsed scan time in seconds +% one column per active channel, named _m (e.g. Water_m18), +% holding the partial pressure in the file's units (usually Torr). +% +% The scan units and channel metadata are stored in +% tbl.Properties.VariableUnits and tbl.Properties.UserData. +% +% Two quirks of this file format are handled: +% * multi-word species names in the channel table (e.g. "Carbn dio") +% * adjacent negative values in the data run together with no space +% (e.g. "-2.8663e-010-2.3529e-010"); values are recovered by matching +% scientific-notation tokens rather than by splitting on whitespace. +% +% EXAMPLE +% tbl = rgaImportData('rga1_Jul_01_2026_08-23-06_AM.txt'); +% ax = plotRgaData(tbl); +% +% See also PLOTRGADATA, PLOTCRYODATA + + lines = readlines(fullFileName); % string array, keeps blanks + + %--- Locate the data block header "Time(s) Channel#1 ..." --- + iData = find(startsWith(strip(lines),'Time(s)'), 1); + if isempty(iData) + error('rgaImportData:noData','Could not find the "Time(s)" data header.'); + end + header = lines(1:iData-1); % metadata + channel table + + %--- Start time (absolute) --- + iStart = find(startsWith(strip(header),'Start time,'), 1); + if isempty(iStart) + startStr = strip(header(1)); % fall back to first line + else + startStr = strip(extractAfter(header(iStart),',')); + end + startStr = regexprep(startStr,'\s+',' '); % collapse the double space + startTime = datetime(startStr, ... + 'InputFormat','MMM d, yyyy h:mm:ss a', 'Locale','en_US'); + + %--- Units --- + units = 'a.u.'; + iu = find(startsWith(strip(header),'Units,'), 1); + if ~isempty(iu) + p = strip(split(header(iu),',')); + units = char(p(end)); + end + + %--- Channel definitions (e.g. "1 28.00 Nitrogen 1.00 3 OFF") --- + chanPat = '^\s*(\d+)\s+([\d.]+)\s+(.+?)\s+[\d.]+\s+\d+\s+\w+\s*$'; + tok = regexp(header, chanPat, 'tokens', 'once'); + tok = tok(~cellfun(@isempty, tok)); + nCh = numel(tok); + if nCh == 0 + error('rgaImportData:noChannels','No channel definitions found.'); + end + chMass = zeros(nCh,1); chName = strings(nCh,1); + for k = 1:nCh + chMass(k) = str2double(tok{k}{2}); + chName(k) = strip(string(tok{k}{3})); + end + + %--- Data: time before the comma + N scientific-notation values --- + dataLines = lines(iData+1:end); + dataLines = dataLines(strlength(strip(dataLines)) > 0); + n = numel(dataLines); + + time_s = str2double(extractBefore(dataLines, ',')); + matches = regexp(dataLines, '[-+]?\d+\.\d+[eE][-+]?\d+', 'match'); + + vals = nan(n, nCh); + for r = 1:n + v = str2double(matches{r}); + nn = min(numel(v), nCh); + vals(r,1:nn) = v(1:nn); + end + + %--- Assemble table --- + dt = startTime + seconds(time_s); + varNames = arrayfun(@(k) matlab.lang.makeValidName( ... + sprintf('%s_m%d', chName(k), round(chMass(k)))), 1:nCh, 'UniformOutput', false); + + tbl = table(dt, time_s, 'VariableNames', {'datetime','time_s'}); + for k = 1:nCh + tbl.(varNames{k}) = vals(:,k); + end + + tbl.Properties.VariableUnits = [{'', 's'}, repmat({char(units)}, 1, nCh)]; + tbl.Properties.UserData = struct( ... + 'units', char(units), 'startTime', startTime, ... + 'channelName', chName, 'channelMass', chMass); +end