Added import and plot for flow data that is now logged for the MFC
This commit is contained in:
1 parent
3ad96b3890
commit
378d176739
3 files changed
+300
No files matched your search
+13
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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<ISCLSTR>
|
||||
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: <name>" or "MFCn (<gas>)" 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
|
||||
Reference in new issue
Block a user