From 378d17673961a9b613e50f70a76e45698c5fe651 Mon Sep 17 00:00:00 2001 From: ThorBarRa Date: Fri, 10 Jul 2026 10:08:07 +0200 Subject: [PATCH] Added import and plot for flow data that is now logged for the MFC --- .gitignore | 13 +++ beamtime/issFlowImportData.m | 104 ++++++++++++++++++++ beamtime/issPlotFlowData.m | 183 +++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 beamtime/issFlowImportData.m create mode 100644 beamtime/issPlotFlowData.m diff --git a/.gitignore b/.gitignore index 92061b1..2d6009c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.gitignore # Autosave files *.asv *.m~ @@ -31,3 +32,15 @@ codegen/ # Cloud based storage dotfile .MATLABDriveTag + +# MacOS system files +.DS_Store +lopap/3.Skripte/.DS_Store + +# error messages +utils/distFig_Error.mat +utils\\distFig_Update.txt + +beamtime/clean_file.p +beamtime/requirements.txt + diff --git a/beamtime/issFlowImportData.m b/beamtime/issFlowImportData.m new file mode 100644 index 0000000..ca0455a --- /dev/null +++ b/beamtime/issFlowImportData.m @@ -0,0 +1,104 @@ +function tbl = issFlowImportData(fullFileName) +%FLOWIMPORTDATA Import the MFC flow-log channels from a new-format LabView file. +% +% tbl = flowImportData(fullFileName) reads the mid-2026 LabView chamber log +% (tab-delimited, 2 header lines, ISO-8859-1) and returns a table of the +% 7 MFC flow channels: +% datetime - absolute time (date + time columns) +% MFC4 .. MFC10 - one column per mass-flow controller, named by its +% physical MFC number (as on the gas panel). +% MFC4 [l/min] - cryo cooling flow (the old "flow cryo" channel) +% MFC5..MFC10 [sccm] - dosing/carrier gas flows (the old flow1..flow6) +% +% MFC <-> COLUMN MAPPING +% The file's header line 1 names 7 controllers in order (MFC4..MFC10, +% with a gas type each), and the data columns log 7 flow channels in the +% same order (flow-cryo, then flow1..flow6). This function maps them +% 1:1 by that shared order: +% MFC4 <- "flow cryo (l/min)" (col 9) +% MFC5 <- "flow1 (sccm)" (col 11) +% MFC6 <- "flow2 (sccm)" (col 12) +% MFC7 <- "flow3 (sccm)" (col 13) +% MFC8 <- "flow4 (sccm)" (col 14) +% MFC9 <- "flow5 (sccm)" (col 15) +% MFC10 <- "flow6 (sccm)" (col 16) +% This is an ORDER-MATCH inference (the file does not literally print +% "flow3 = MFC7"), so double-check it once against the gas panel / +% LabView front panel if in doubt. The parsed gas type per MFC (from the +% header) is stored in tbl.Properties.UserData.mfcMap for that check. +% +% OUTPUT +% tbl.Properties.UserData.mfcMap - table with MFC, SourceColumn, Gas, +% Unit, MfcVarName for all 7 channels +% (for verification and lookup) +% +% EXAMPLE +% tbl = flowImportData('01-07-2026_11-28datafile.txt'); +% tbl.Properties.UserData.mfcMap % inspect / verify the mapping +% ax = plotFlowData(tbl, [4 7 10]); % plot by MFC number +% ax = plotFlowData(tbl, 'flow3'); % or by the raw logged tag +% +% See also PLOTFLOWDATA, LABVIEW_IMPORTDATA + + lines = readlines(fullFileName, 'Encoding', 'ISO-8859-1'); + lines = lines(strlength(lines) > 0); % drop trailing blank from final CRLF + + if numel(lines) < 3 + error('flowImportData:tooShort', 'File does not look like a valid log.'); + end + + hdr1 = lines(1); % gas name line + hdr2 = lines(2); % column names + data = lines(3:end); + + if ~contains(hdr2, 'pressure_cell') && ~contains(hdr1, 'gas name') + error('flowImportData:wrongFormat', ... + 'This does not look like the new-format LabView file (no "gas name"/"pressure_cell" header found).'); + end + + %--- Parse gas names/types from header line 1: "gas name MFCn:Gas" --- + tok = regexp(hdr1, 'gas name (MFC\d+):(\S+)', 'tokens'); + mfcNum = cellfun(@(t) str2double(erase(t{1},'MFC')), tok); + gasType = cellfun(@(t) string(t{2}), tok); + [mfcNum, order] = sort(mfcNum); + gasType = gasType(order); + + if numel(mfcNum) ~= 7 + warning('flowImportData:unexpectedMfcCount', ... + 'Expected 7 named MFCs in the header, found %d. Mapping may be wrong.', numel(mfcNum)); + end + + %--- Parse the data rows --- + parts = split(data, sprintf('\t')); + if size(parts, 2) < 16 + error('flowImportData:tooFewColumns', 'Fewer columns than expected in data rows.'); + end + + dateStr = parts(:,1); + timeStr = parts(:,2); + dt = datetime(strcat(dateStr, timeStr), 'InputFormat', 'dd.MM.yyyyHH:mm:ss'); + + flowCryo = str2double(parts(:,9)); % "flow cryo (l/min)" + flowN = str2double(parts(:,11:16)); % flow1..flow6 (sccm) + + %--- Order-matched MFC <-> column mapping --- + columnData = [{flowCryo}, num2cell(flowN,1)]; % 1x7 cell of column vectors + columnNames = {'flowCryo','flow1','flow2','flow3','flow4','flow5','flow6'}; + columnUnits = [{'l/min'}, repmat({'sccm'}, 1, 6)]; + + tbl = table(dt, 'VariableNames', {'datetime'}); + nCh = min(7, numel(mfcNum)); + mfcVarNames = strings(1, nCh); + for k = 1:nCh + varName = sprintf('MFC%d', mfcNum(k)); + tbl.(varName) = columnData{k}; + mfcVarNames(k) = varName; + end + + mfcMap = table(mfcNum(1:nCh)', columnNames(1:nCh)', gasType(1:nCh)', columnUnits(1:nCh)', ... + 'VariableNames', {'MFC','SourceColumn','Gas','Unit'}); + mfcMap.MfcVarName = cellstr(mfcVarNames(:)); + + tbl.Properties.VariableUnits = [{''}, columnUnits(1:nCh)]; + tbl.Properties.UserData = struct('mfcMap', mfcMap); +end diff --git a/beamtime/issPlotFlowData.m b/beamtime/issPlotFlowData.m new file mode 100644 index 0000000..8fd874e --- /dev/null +++ b/beamtime/issPlotFlowData.m @@ -0,0 +1,183 @@ +function sp = issPlotFlowData(tbl, channels, options) +%PLOTFLOWDATA Stacked plot of selected MFC flow channels vs lab time. +% +% sp = plotFlowData(tbl) +% sp = plotFlowData(tbl, channels) +% sp = plotFlowData(tbl, channels, TimeWindow=timeWindow) +% sp = plotFlowData(tbl, channels, MfcNames=mfcNames) +% sp = plotFlowData(tbl, channels, TimeWindow=timeWindow, MfcNames=mfcNames) +% +% Plots one stacked panel per selected MFC flow channel (via stackedplot), +% all sharing a linked lab-time x-axis. Each panel keeps its own y-scale, +% so channels in different units (l/min vs sccm) sit cleanly on their own +% axis without needing yyaxis. +% +% TimeWindow and MfcNames are name-value arguments (order-independent, +% optional, with autocomplete/tooltips in the Editor and Live Script). +% +% INPUTS +% tbl table from flowImportData (columns MFC4..MFC10) +% channels which channel(s) to plot. Any of: +% * [] -> all channels present (default) +% * MFC numbers, e.g. [4 7 10] or 7 (physical MFC number) +% * flow tags, e.g. {'flow1','flow2'} or 'flow3' +% * MFC names, e.g. {'MFC4','MFC9'} or 'MFC7' +% * YOUR custom names from MfcNames, e.g. 'Water dosing' +% or {'Cryo cooling (LN2)','Water dosing'} — only +% resolvable if MfcNames is also supplied in this call. +% All selection styles resolve to the same underlying +% channel — pick whichever is most convenient. Pass multiple +% as a CELL ARRAY of char, e.g. {'flow1','flow2'} — NOT +% ['flow1','flow2'], which MATLAB concatenates into the +% single char 'flow1flow2'. +% +% NAME-VALUE ARGUMENTS +% TimeWindow date-string window as a 2-element cell/string array, +% {'2026-07-01 11:30:00','2026-07-01 18:30:00'}. Default: the +% full available range. +% MfcNames your own display names for the panels (and, per above, +% usable to SELECT channels too), as a containers.Map keyed +% by MFC NUMBER (double), e.g.: +% mfcNames = containers.Map('KeyType','double','ValueType','char'); +% mfcNames(4) = 'Cryo cooling (LN2)'; +% mfcNames(7) = 'Water dosing'; +% Panel labels always show the MFC identifier for +% traceability, e.g. "MFC7: Water dosing [sccm]". Channels +% without an entry fall back to "MFC7 (N2) [sccm]" using the +% gas type parsed from the file header. +% +% OUTPUT +% sp the StackedLineChart object. To further customize (e.g. the x-axis +% tick format, since stackedplot is a chart, not a plain axes): +% axs = findobj(sp.NodeChildren, 'Type', 'Axes'); +% axs(end).XAxis.TickLabelFormat = 'dd-MMM HH:mm'; % bottom panel +% +% EXAMPLE +% tbl = flowImportData('01-07-2026_11-28datafile.txt'); +% tbl.Properties.UserData.mfcMap % check the mapping once +% +% mfcNames = containers.Map('KeyType','double','ValueType','char'); +% mfcNames(4) = 'Cryo cooling (LN2)'; +% mfcNames(7) = 'Water dosing'; +% +% sp = plotFlowData(tbl, 'Water dosing', MfcNames=mfcNames); % by custom name +% sp = plotFlowData(tbl, {'flow1','flow2'}, MfcNames=mfcNames); +% sp = plotFlowData(tbl, {'MFC4','MFC9'}); % by MFC name +% sp = plotFlowData(tbl, [4 7], ... +% TimeWindow={'2026-07-01 11:30:00','2026-07-01 18:30:00'}, ... +% MfcNames=mfcNames); +% +% See also FLOWIMPORTDATA + + arguments + tbl table + channels = [] + options.TimeWindow = [] + options.MfcNames containers.Map = containers.Map('KeyType','double','ValueType','char') + end + + mfcMap = tbl.Properties.UserData.mfcMap; % MFC, SourceColumn, Gas, Unit, MfcVarName + mfcNames = options.MfcNames; + + %--- Resolve requested channels into MfcVarName column names --- + if isempty(channels) + selVars = mfcMap.MfcVarName; + elseif isnumeric(channels) + selVars = arrayfun(@(n) resolveByMfcNumber(mfcMap, n), channels, 'UniformOutput', false); + elseif ischar(channels) + selVars = {resolveChannel(mfcMap, mfcNames, channels)}; + elseif iscellstr(channels) || isstring(channels) %#ok + c = cellstr(channels); + selVars = cellfun(@(v) resolveChannel(mfcMap, mfcNames, v), c, 'UniformOutput', false); + else + error('plotFlowData:badChannels', ... + 'channels must be [] , MFC numbers, or flow/MFC/custom name(s).'); + end + selVars = cellstr(string(selVars)); + + %--- Resolve the time window (date strings) --- + if isempty(options.TimeWindow) + tStart = min(tbl.datetime); + tEnd = max(tbl.datetime); + else + tw = cellstr(options.TimeWindow); + if numel(tw) ~= 2 + error('plotFlowData:badTimeWindow', ... + 'TimeWindow must be a 2-element cell/string array {start, end}.'); + end + tStart = datetime(tw{1}); + tEnd = datetime(tw{2}); + end + mask = tbl.datetime >= tStart & tbl.datetime <= tEnd; + tblSub = tbl(mask, [{'datetime'}, selVars]); + + %--- Panel labels: always "MFCn: " or "MFCn ()" as fallback --- + labels = cellfun(@(v) labelFor(mfcMap, mfcNames, v), selVars, 'UniformOutput', false); + + %--- Units per panel, for the y-axis label --- + units = cellfun(@(v) mfcMap.Unit{strcmp(mfcMap.MfcVarName, v)}, selVars, 'UniformOutput', false); + dispLabels = cellfun(@(l,u) sprintf('%s [%s]', l, u), labels, units, 'UniformOutput', false); + + %--- Stacked plot --- + figure; + sp = stackedplot(tblSub, selVars, 'XVariable', 'datetime', ... + 'DisplayLabels', dispLabels); + sp.Title = 'MFC flow channels'; + sp.GridVisible = 'on'; + sp.XLimits = [tStart tEnd]; + + % Format the shared x-axis date ticks (stackedplot is a chart object, + % so its axes are reached via NodeChildren) + axs = findobj(sp.NodeChildren, 'Type', 'Axes'); + if ~isempty(axs) + axs(end).XAxis.TickLabelFormat = 'dd-MMM HH:mm'; + end +end + +% ------------------------------------------------------------------------- +function varName = resolveByMfcNumber(mfcMap, n) + idx = find(mfcMap.MFC == n, 1); + if isempty(idx) + error('plotFlowData:unknownMFC', 'MFC%d not found in this table.', n); + end + varName = mfcMap.MfcVarName{idx}; +end + +% ------------------------------------------------------------------------- +function varName = resolveChannel(mfcMap, mfcNames, v) + v = char(v); + + % 1) MFC var name, e.g. 'MFC7' + idx = find(strcmpi(mfcMap.MfcVarName, v), 1); + if ~isempty(idx), varName = mfcMap.MfcVarName{idx}; return; end + + % 2) raw source column tag, e.g. 'flow3', 'flowCryo' + idx = find(strcmpi(mfcMap.SourceColumn, v), 1); + if ~isempty(idx), varName = mfcMap.MfcVarName{idx}; return; end + + % 3) your custom display name, e.g. 'Water dosing' (only if MfcNames given) + if mfcNames.Count > 0 + ks = cell2mat(keys(mfcNames)); + vs = values(mfcNames); + idxName = find(strcmpi(vs, v), 1); + if ~isempty(idxName) + mfcNum = ks(idxName); + idx = find(mfcMap.MFC == mfcNum, 1); + if ~isempty(idx), varName = mfcMap.MfcVarName{idx}; return; end + end + end + + error('plotFlowData:unknownChannel', ... + 'Channel "%s" not recognized as an MFC name, flow tag, or custom name in MfcNames.', v); +end + +% ------------------------------------------------------------------------- +function s = labelFor(mfcMap, mfcNames, varName) + idx = find(strcmp(mfcMap.MfcVarName, varName), 1); + mfcNum = mfcMap.MFC(idx); + if isKey(mfcNames, mfcNum) + s = sprintf('%s: %s', varName, mfcNames(mfcNum)); + else + s = sprintf('%s (%s)', varName, mfcMap.Gas(idx)); + end +end