Files
imdd_silas/projects/HighSpeedExperiment_2024/Auswertung_JLT/plot_measurements_gpt.m
Silas Oettinghaus 0080cb2264 ML Equalizer works now.
Not yet perfectly integrated into all the routines
2025-11-12 09:24:02 +01:00

358 lines
11 KiB
Matlab

function h = plot_measurements_gpt(T, cfg)
% Versatile plotting from your DB table (with cbrewer2 'Paired' palette).
%
% Usage:
% h = plot_measurements_flex(dataTable, cfg)
%% ---- Defaults
if nargin < 2, cfg = struct; end
defaults = struct( ...
'x_axis' , 'symbolrate', ...
'y_axis' , 'BER', ...
'y_scale' , 'auto', ...
'group_by' , {{'equalizer_structure','pre_emph'}}, ...
'filters' , struct, ...
'agg' , 'mean', ...
'outlier' , 'auto', ...
'mad_z' , 3, ...
'pct_limits' , [2.5 97.5], ...
'min_pts_x' , 3, ...
'show_raw' , true, ...
'show_precoded', [], ...
'show_spread' , 'none', ...
'fec_lines' , [2.2e-4 4.85e-3 2e-2], ...
'plot', struct() ...
);
cfg = filldefaults(cfg, defaults);
% ---- Plot defaults (new)
plotdefs = struct( ...
'use_cbrewer2' , true, ...
'colormap' , 'Paired', ... % ColorBrewer 'Paired'
'paired_dark_first' , true, ... % dark for lines, light for scatter
'lineWidth' , 1.8, ...
'errWidth' , 1.0, ...
'scatterSize' , 14, ...
'scatterAlpha' , 0.35, ...
'marker' , 'o', ...
'marker_precoded' , 's', ...
'lineStyle_pre_emph_on' , '--', ...
'lineStyle_pre_emph_off', '-', ...
'legendLocation' , 'best', ...
'fecLineWidth' , 2.2, ... % thicker FEC limits
'fecColor' , [0.25 0.25 0.25], ...
'capSize' , 6, ...
'lineStyle_default' , '-', ...
'use_pre_emph_styling' , true ...
);
cfg.plot = filldefaults(cfg.plot, plotdefs);
%% ---- Derived/prep columns
if ~ismember('pre_emph', T.Properties.VariableNames)
if ~ismember('db_mode', T.Properties.VariableNames)
error('Missing column "db_mode" for pre_emph derivation.');
end
T.pre_emph = T.db_mode == 0;
end
if ~ismember(cfg.y_axis, T.Properties.VariableNames)
error('y_axis "%s" not found in table.', cfg.y_axis);
end
isBER = startsWith(cfg.y_axis, "BER", 'IgnoreCase', true);
if strcmpi(cfg.y_scale,'auto'), cfg.y_scale = tern(isBER, 'log', 'linear'); end
if strcmpi(cfg.outlier,'auto'), cfg.outlier = tern(isBER, 'mad', 'none'); end
if isempty(cfg.show_precoded)
cfg.show_precoded = isBER && ismember('BER_precoded', T.Properties.VariableNames);
end
%% ---- Filters
T = applyFilters(T, cfg.filters);
[x_raw, x_label] = computeX(T, cfg.x_axis);
y_raw = T.(cfg.y_axis);
validXY = isfinite(x_raw) & isfinite(y_raw);
T = T(validXY, :);
x_raw = x_raw(validXY);
y_raw = y_raw(validXY);
if cfg.show_precoded && ismember('BER_precoded', T.Properties.VariableNames)
y_raw_p = T.BER_precoded(validXY);
else
y_raw_p = [];
end
%% ---- Grouping
group_by = cfg.group_by;
if ~all(ismember(group_by, T.Properties.VariableNames))
error('Some group_by columns are missing in table.');
end
[G, grpTbl] = findgroups(T(:, group_by));
nG = max(G);
% ==== Colors (cbrewer2 'Paired' with dark/ light pairs) ====
[cols_line, cols_scatter] = buildGroupColors(nG, cfg.plot);
%% ---- Plotting
figure; hold on; grid on;
h.lines = gobjects(nG,1);
h.err = gobjects(nG,1);
h.scat = gobjects(nG,1);
h.lines_p = gobjects(nG,1);
for gi = 1:nG
idx = (G==gi);
Ti = T(idx,:);
xi = x_raw(idx);
yi = y_raw(idx);
% Aggregate per unique x
[xu, ia, iu] = unique(xi);
yu = nan(size(xu));
ylo = nan(size(xu));
yhi = nan(size(xu));
for k = 1:numel(xu)
bin = (iu==k);
yy = yi(bin);
yy = yy(isfinite(yy));
if isempty(yy), continue; end
km = outlierMask(yy, cfg, strcmpi(cfg.y_scale,'log'));
if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end
yy = yy(km);
if strcmpi(cfg.agg,'median'), yu(k)=median(yy,'omitnan'); elseif strcmpi(cfg.agg,'mean'), yu(k)=mean(yy,'omitnan'); elseif strcmpi(cfg.agg,'min'), yu(k)=min(yy); end
if strcmpi(cfg.show_spread,'iqr')
q = prctile(yy,[25 75]);
ylo(k) = max(yu(k)-q(1), eps);
yhi(k) = max(q(2)-yu(k), eps);
end
end
% sort
[xu, ord] = sort(xu);
yu = yu(ord); ylo = ylo(ord); yhi = yhi(ord);
% Styles
% Decide if we style by pre_emph
canStyleByPre = cfg.plot.use_pre_emph_styling && ismember('pre_emph', T.Properties.VariableNames);
if canStyleByPre
if any(strcmp(group_by,'pre_emph'))
% pre_emph is an explicit grouping key -> take it from the group table
pre = logical(grpTbl.pre_emph(gi));
else
% pre_emph not grouped, but available in the rows -> infer from the members of this group
pre = logical(mode(T.pre_emph(G==gi)));
end
ls = tern(pre, cfg.plot.lineStyle_pre_emph_on, cfg.plot.lineStyle_pre_emph_off);
else
% no pre-emph styling → use a single default style
ls = cfg.plot.lineStyle_default;
end
lbl = buildLabel(grpTbl(gi,:), group_by);
% Main line (dark)
colL = cols_line(gi,:);
h.lines(gi) = plot(xu, yu, ...
'LineWidth', cfg.plot.lineWidth, ...
'Marker', cfg.plot.marker, 'MarkerSize', 5, ...
'Color', colL, 'LineStyle', ls, ...
'DisplayName', char(lbl));
% Spread (IQR) in line color
if strcmpi(cfg.show_spread,'iqr') && any(isfinite(ylo)) && any(isfinite(yhi))
h.err(gi) = errorbar(xu, yu, ylo, yhi, 'LineStyle','none', ...
'Color', colL, 'CapSize', cfg.plot.capSize, 'HandleVisibility','off');
h.err(gi).LineWidth = cfg.plot.errWidth;
end
% Raw kept scatter (light)
if cfg.show_raw
keep_all = false(size(yi));
for k = 1:numel(xu)
bin = (iu==k);
yy = yi(bin);
km = outlierMask(yy, cfg, strcmpi(cfg.y_scale,'log'));
if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end
keep_all(bin) = km;
end
colS = cols_scatter(gi,:);
scatter(xi(keep_all), yi(keep_all), cfg.plot.scatterSize, colS, 'filled', ...
'MarkerFaceAlpha', cfg.plot.scatterAlpha, 'MarkerEdgeAlpha', cfg.plot.scatterAlpha, ...
'HandleVisibility','off');
end
% Precoded overlay (dotted, squares), in line color
if cfg.show_precoded && ~isempty(y_raw_p) && strcmpi(cfg.y_axis,'BER')
ypi = y_raw_p(idx);
ypu = nan(size(xu));
for k = 1:numel(xu)
bin = (iu==k);
yy = ypi(bin);
yy = yy(isfinite(yy));
if isempty(yy), continue; end
km = outlierMask(yy, cfg, true);
if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end
yy = yy(km);
if strcmpi(cfg.agg,'median'), ypu(k)=median(yy,'omitnan'); elseif strcmpi(cfg.agg,'mean'), ypu(k)=mean(yy,'omitnan'); elseif strcmpi(cfg.agg,'min'), ypu(k)=min(yy); end
end
h.lines_p(gi) = plot(xu, ypu, ...
'LineWidth', max(1.2, cfg.plot.lineWidth-0.2), ...
'Marker', cfg.plot.marker_precoded, 'MarkerSize', 5, ...
'Color', colL, 'LineStyle', ':', ...
'DisplayName', [char(lbl) ' (precoded)']);
end
end
%% ---- Axes / Labels / FEC
ylabel(cfg.y_axis, 'Interpreter','none');
xlabel(x_label, 'Interpreter','none');
set(gca, 'YScale', cfg.y_scale, 'FontSize', 11);
legend('Location', cfg.plot.legendLocation); box on;
if startsWith(cfg.y_axis,"BER",'IgnoreCase',true)
for v = cfg.fec_lines
yline(v, '--', 'Color', cfg.plot.fecColor, ...
'LineWidth', cfg.plot.fecLineWidth, 'HandleVisibility','off');
end
ylim([1e-4, 0.3]);
end
end % ===== main =====
%% ===================== Helpers =====================
function cfg = filldefaults(cfg, defs)
fn = fieldnames(defs);
for i = 1:numel(fn)
f = fn{i};
if ~isfield(cfg, f) || isempty(cfg.(f))
cfg.(f) = defs.(f);
elseif isstruct(defs.(f)) && isstruct(cfg.(f))
cfg.(f) = filldefaults(cfg.(f), defs.(f)); % recursive for structs
end
end
end
function out = tern(cond, a, b)
if cond, out = a; else, out = b; end
end
function T2 = applyFilters(T, filters)
if isempty(filters), T2 = T; return; end
keep = true(height(T),1);
fns = fieldnames(filters);
for i = 1:numel(fns)
name = fns{i};
if ~ismember(name, T.Properties.VariableNames)
warning('Filter column "%s" not found. Ignored.', name); %#ok<*WNTAG>
continue
end
val = filters.(name);
col = T.(name);
if isa(val,'function_handle')
m = val(col);
if ~islogical(m) || ~isequal(size(m), size(col))
error('Filter for %s must return logical mask of same size.', name);
end
keep = keep & m;
else
keep = keep & ismember(col, val);
end
end
T2 = T(keep,:);
end
function [x, label] = computeX(T, whichX)
switch lower(whichX)
case {'symbolrate','baudrate'}
x = T.symbolrate * 1e-9;
label = 'Symbol rate [GBd]';
case 'bitrate'
if ~ismember('pam_level', T.Properties.VariableNames)
error('bitrate requires "pam_level" column.');
end
bits = log2(double(T.pam_level));
x = (T.symbolrate .* bits) * 1e-9;
label = 'Bitrate [Gb/s]';
otherwise
if ~ismember(whichX, T.Properties.VariableNames)
error('x_axis "%s" not found in table.', whichX);
end
x = T.(whichX);
label = whichX;
end
x = double(x(:));
end
function keep = outlierMask(y, cfg, useLog)
if isempty(y), keep = false(size(y)); return; end
y = y(:);
switch lower(cfg.outlier)
case 'none'
keep = true(size(y)); return
case 'mad'
z = tern(useLog, log10(y), y);
med = median(z,'omitnan');
madv = median(abs(z-med),'omitnan');
if ~(isfinite(madv) && madv>0)
keep = true(size(y)); return
end
sigma = 1.4826*madv;
zz = tern(useLog, log10(y), y);
keep = abs(zz - med) <= cfg.mad_z*sigma;
case 'pctl'
pr = prctile(y, cfg.pct_limits);
keep = (y >= pr(1)) & (y <= pr(2));
otherwise
error('Unknown outlier mode "%s".', cfg.outlier);
end
end
function s = buildLabel(grpRow, group_by)
parts = strings(1, numel(group_by));
for i = 1:numel(group_by)
key = group_by{i};
val = grpRow.(key);
if iscell(val), val = val{1}; end
if islogical(val), val = tern(val,'pre-emph on','pre-emph off'); end
parts(i) = sprintf('%s=%s', key, string(val));
end
s = strjoin(parts, ', ');
end
function [cols_line, cols_scatter] = buildGroupColors(nG, plotcfg)
% Build paired colors (dark for lines, light for scatter) using cbrewer2('Paired')
useBrewer = plotcfg.use_cbrewer2 && exist('cbrewer2','file')==2;
if useBrewer
N = max(2*nG, 12); % ensure pairs available
C = cbrewer2(plotcfg.colormap, N);
cols_line = zeros(nG,3);
cols_scatter = zeros(nG,3);
for i = 1:nG
if plotcfg.paired_dark_first
dark = C(2*i-1, :); light = C(2*i, :);
else
light = C(2*i-1, :); dark = C(2*i, :);
end
cols_line(i,:) = dark;
cols_scatter(i,:) = light;
end
else
% Fallback: lines() + lightened scatter
C = lines(max(nG,7));
cols_line = C(1:nG,:);
cols_scatter = zeros(nG,3);
for i = 1:nG
cols_scatter(i,:) = lightenColor(cols_line(i,:), 0.5); % 50% toward white
end
end
end
function c2 = lightenColor(c, fracTowardWhite)
c = c(:).';
c2 = (1-fracTowardWhite)*c + fracTowardWhite*1;
end