From 83ce6dcd44f5eb314e650c1e92505034f8edfc19 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 10:05:36 +0100 Subject: [PATCH 01/80] feat(ui): add centralized dark theme system (PStheme) --- src/util/PSstyleAxes.m | 7 +++++ src/util/PSstyleFig.m | 6 ++++ src/util/PSstyleLegend.m | 5 ++++ src/util/PStheme.m | 59 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 src/util/PSstyleAxes.m create mode 100644 src/util/PSstyleFig.m create mode 100644 src/util/PSstyleLegend.m create mode 100644 src/util/PStheme.m diff --git a/src/util/PSstyleAxes.m b/src/util/PSstyleAxes.m new file mode 100644 index 0000000..a7f3c9b --- /dev/null +++ b/src/util/PSstyleAxes.m @@ -0,0 +1,7 @@ +function PSstyleAxes(ax, th) +%% PSstyleAxes - apply dark theme to axes +if nargin < 2, th = PStheme(); end +set(ax, 'Color', th.axesBg, 'XColor', th.axesFg, 'YColor', th.axesFg, ... + 'GridColor', th.gridColor, 'FontWeight', 'bold'); +grid(ax, 'on'); +end diff --git a/src/util/PSstyleFig.m b/src/util/PSstyleFig.m new file mode 100644 index 0000000..4dd6405 --- /dev/null +++ b/src/util/PSstyleFig.m @@ -0,0 +1,6 @@ +function PSstyleFig(fig, titleStr) +%% PSstyleFig - apply dark theme to figure +th = PStheme(); +set(fig, 'Color', th.figBg, 'InvertHardcopy', 'off'); +if nargin >= 2, set(fig, 'Name', titleStr, 'NumberTitle', 'off'); end +end diff --git a/src/util/PSstyleLegend.m b/src/util/PSstyleLegend.m new file mode 100644 index 0000000..916bc62 --- /dev/null +++ b/src/util/PSstyleLegend.m @@ -0,0 +1,5 @@ +function PSstyleLegend(lg, th) +%% PSstyleLegend - apply dark theme to legend +if nargin < 2, th = PStheme(); end +set(lg, 'TextColor', th.legendFg, 'Color', th.legendBg, 'EdgeColor', th.legendEdge); +end diff --git a/src/util/PStheme.m b/src/util/PStheme.m new file mode 100644 index 0000000..e513c02 --- /dev/null +++ b/src/util/PStheme.m @@ -0,0 +1,59 @@ +function th = PStheme() +%% PStheme - central UI theme for PIDscope (dark) + +% figure & panel +th.figBg = [.18 .18 .20]; +th.panelBg = [.22 .22 .24]; +th.panelFg = [.90 .90 .90]; +th.panelBorder = [.35 .35 .38]; + +% axes +th.axesBg = [.10 .10 .12]; +th.axesFg = [.75 .75 .75]; +th.gridColor = [.28 .28 .30]; + +% text +th.textPrimary = [.90 .90 .90]; +th.textSecondary = [.65 .65 .65]; +th.textAccent = [.40 .80 1.0]; + +% legend +th.legendBg = [.16 .16 .18]; +th.legendFg = [.80 .80 .80]; +th.legendEdge = [.35 .35 .38]; + +% epoch shading (trim regions) +th.epochFill = [.30 .30 .32]; +th.epochAlpha = 0.7; + +% buttons +th.btnRun = [.20 .70 .30]; +th.btnReset = [.85 .55 .15]; +th.btnSave = [.65 .65 .65]; +th.btnDash1 = [.85 .25 .25]; % Spectral Analyzer +th.btnDash2 = [.25 .50 .90]; % Step Response +th.btnDash3 = [.85 .55 .15]; % Filter Sim +th.btnDash4 = [.20 .70 .30]; % Motor Noise +th.btnDash5 = [.20 .80 .80]; % Chirp Analysis +th.btnLink = [.85 .55 .15]; % Support PIDscope + +% checkbox / input bg +th.checkBg = [.18 .18 .20]; +th.inputBg = [.14 .14 .16]; +th.inputFg = [.90 .90 .90]; + +% signal colors (bright for dark bg) +th.sigDebug = [.50 .50 .50]; +th.sigGyro = [.85 .85 .85]; +th.sigPterm = [.20 .85 .20]; +th.sigIterm = [.90 .75 .20]; +th.sigDprefilt = [.45 .80 .95]; +th.sigDterm = [.30 .40 .95]; +th.sigFterm = [.75 .45 .45]; +th.sigSetpoint = [.90 .25 .35]; +th.sigPIDsum = [1.0 .35 .90]; +th.sigPIDerr = [.55 .20 .95]; +th.sigMotor = {[.20 .85 .30], [.85 .70 .15], [.20 .40 .95], [.30 .95 .85]}; +th.sigThrottle = [.85 .85 .85]; + +end From 20c38172ef8647a82d3490d731e3befd0176ed85 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 10:07:02 +0100 Subject: [PATCH 02/80] perf(parser): replace eval() with dynamic field access in PSload --- src/core/PSload.m | 63 ++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index f254aa5..74d1d91 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -198,55 +198,56 @@ isArduPilot = strcmpi(sfext, '.bin'); + Nsamples = length(T{fcnt}.loopIteration); + isINAV = (get(guiHandles.Firmware, 'Value') == 3); for k = 0 : 3 if ~isArduPilot - try - eval(['T{fcnt}.debug_' int2str(k) '_(1);']) - catch - eval(['T{fcnt}.(''debug_' int2str(k) '_'')' '= zeros(length(T{fcnt}.loopIteration),1);']) ; + dbg_f = ['debug_' int2str(k) '_']; + if ~isfield(T{fcnt}, dbg_f) + T{fcnt}.(dbg_f) = zeros(Nsamples, 1); end - try - eval(['T{fcnt}.axisF_' int2str(k) '_(1);']) - catch - eval(['T{fcnt}.(''axisF_' int2str(k) '_'')' '= zeros(length(T{fcnt}.loopIteration),1);']); + axF_f = ['axisF_' int2str(k) '_']; + if ~isfield(T{fcnt}, axF_f) + T{fcnt}.(axF_f) = zeros(Nsamples, 1); end - if get(guiHandles.Firmware, 'Value') == 3 % INAV - try - eval(['T{fcnt}.motor_' int2str(k) '_ = ((T{fcnt}.motor_' int2str(k) '_ - 1000)) / 10;'])% scale motor sigs to % - catch, end - try - eval(['T{fcnt}.motor_' int2str(k+4) '_ = ((T{fcnt}.motor_' int2str(k+4) '_ - 1000)) / 10;'])% scale motor sigs 4-7 for x8 configuration - catch + mot_f = ['motor_' int2str(k) '_']; + mot8_f = ['motor_' int2str(k+4) '_']; + if isINAV + if isfield(T{fcnt}, mot_f) + T{fcnt}.(mot_f) = (T{fcnt}.(mot_f) - 1000) / 10; + end + if isfield(T{fcnt}, mot8_f) + T{fcnt}.(mot8_f) = (T{fcnt}.(mot8_f) - 1000) / 10; end else - try - eval(['T{fcnt}.motor_' int2str(k) '_ = ((T{fcnt}.motor_' int2str(k) '_) / 2000) * 100;'])% scale motor sigs to % - catch, end - try - eval(['T{fcnt}.motor_' int2str(k+4) '_ = ((T{fcnt}.motor_' int2str(k+4) '_) / 2000) * 100;'])% scale motor sigs 4-7 for x8 configuration - catch + if isfield(T{fcnt}, mot_f) + T{fcnt}.(mot_f) = T{fcnt}.(mot_f) / 2000 * 100; + end + if isfield(T{fcnt}, mot8_f) + T{fcnt}.(mot8_f) = T{fcnt}.(mot8_f) / 2000 * 100; end end end % ~isArduPilot if k < 3 - if k < 2 % compute prefiltered dterm and scale + ks = int2str(k); + if k < 2 % compute prefiltered dterm try - eval(['T{fcnt}.axisDpf_' int2str(k) '_ = -[0; diff(T{fcnt}.gyroADC_' int2str(k) '_)];']) - clear d1 d2 d3 sclr - eval(['d1 = smooth(T{fcnt}.axisDpf_' int2str(k) '_, 100);']) - eval(['d2 = smooth(T{fcnt}.axisD_' int2str(k) '_, 100);']) - d3 = (d2 ./ d1); + dpf_f = ['axisDpf_' ks '_']; + T{fcnt}.(dpf_f) = -[0; diff(T{fcnt}.(['gyroADC_' ks '_']))]; + d1 = smooth(T{fcnt}.(dpf_f), 100); + d2 = smooth(T{fcnt}.(['axisD_' ks '_']), 100); + d3 = d2 ./ d1; sclr = nanmedian(d3(~isinf(d3) & d3 > 0)); - eval(['T{fcnt}.axisDpf_' int2str(k) '_ = T{fcnt}.axisDpf_' int2str(k) '_ * sclr;']) + T{fcnt}.(dpf_f) = T{fcnt}.(dpf_f) * sclr; catch, end end - eval(['T{fcnt}.(''piderr_' int2str(k) '_'') = T{fcnt}.gyroADC_' int2str(k) '_ - T{fcnt}.setpoint_' int2str(k) '_;']) + T{fcnt}.(['piderr_' ks '_']) = T{fcnt}.(['gyroADC_' ks '_']) - T{fcnt}.(['setpoint_' ks '_']); try - eval(['T{fcnt}.(''pidsum_' int2str(k) '_'') = T{fcnt}.axisP_' int2str(k) '_ + T{fcnt}.axisI_' int2str(k) '_ + T{fcnt}.axisD_' int2str(k) '_ + T{fcnt}.axisF_' int2str(k) '_;']) + T{fcnt}.(['pidsum_' ks '_']) = T{fcnt}.(['axisP_' ks '_']) + T{fcnt}.(['axisI_' ks '_']) + T{fcnt}.(['axisD_' ks '_']) + T{fcnt}.(['axisF_' ks '_']); catch - eval(['T{fcnt}.(''pidsum_' int2str(k) '_'') = T{fcnt}.axisP_' int2str(k) '_ + T{fcnt}.axisI_' int2str(k) '_ + T{fcnt}.axisF_' int2str(k) '_;']) + T{fcnt}.(['pidsum_' ks '_']) = T{fcnt}.(['axisP_' ks '_']) + T{fcnt}.(['axisI_' ks '_']) + T{fcnt}.(['axisF_' ks '_']); end end end From 2db494dd62cb81952a44ce08d9da5813d6ad0118 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 11:02:23 +0100 Subject: [PATCH 03/80] chore: fix CRLF line endings, add .gitattributes --- .gitattributes | 8 + src/compat/PSdatatipSetup.m | 94 +- src/compat/PSuigetfile.m | 190 +-- src/compat/cell2table.m | 92 +- src/compat/contains.m | 86 +- src/compat/detectImportOptions.m | 80 +- src/compat/dkl2rgb.m | 560 +++---- src/compat/fields.m | 12 +- src/compat/finddelay.m | 50 +- src/compat/nanmean.m | 48 +- src/compat/nanmedian.m | 94 +- src/compat/readtable.m | 358 ++--- src/compat/smooth.m | 144 +- src/compat/writetable.m | 118 +- src/core/PSPercent.m | 14 +- src/core/PSSpec2d.m | 70 +- src/core/PSbfFilters.m | 110 +- src/core/PSestimateRPM.m | 150 +- src/core/PSimport.m | 138 +- src/core/PSparseBFversion.m | 114 +- src/core/PSprocess.m | 244 +-- src/core/PSquicJson2csv.m | 322 ++-- src/core/PSstepcalc.m | 174 +-- src/core/PSthrSpec.m | 174 +-- src/core/PStimeFreqCalc.m | 88 +- src/plot/PSdispSetupInfo.m | 120 +- src/plot/PSplotDynNotchOverlay.m | 156 +- src/plot/PSplotLogViewer.m | 590 ++++---- src/plot/PSplotPIDerror.m | 386 ++--- src/plot/PSplotRPMOverlay.m | 166 +-- src/plot/PSplotSpec.m | 730 ++++----- src/plot/PSplotStats.m | 2302 ++++++++++++++--------------- src/plot/PStuningParams.m | 452 +++--- src/ui/PScolormap.m | 74 +- src/ui/PSdispSetupInfoUIcontrol.m | 96 +- src/ui/PSerrUIcontrol.m | 134 +- src/ui/PSspecUIcontrol.m | 588 ++++---- src/ui/PSstatsUIcontrol.m | 244 +-- src/ui/PStuneUIcontrol.m | 292 ++-- src/ui/PSviewerUIcontrol.m | 260 ++-- src/util/PSerrorMessages.m | 40 +- src/util/PSsaveFig.m | 112 +- src/util/PSsaveSettings.m | 290 ++-- src/util/b2r.m | 270 ++-- tests/run_tests.m | 138 +- tests/test_PSSpec2d.m | 84 +- tests/test_PSbfFilters.m | 118 +- tests/test_PSdebugModeIndices.m | 68 +- tests/test_PSestimateRPM.m | 70 +- tests/test_PSparseBFversion.m | 94 +- tests/test_PSquicJson2csv.m | 208 +-- tests/test_PSstepcalc.m | 108 +- tests/test_PSthrSpec.m | 72 +- tests/test_PStimeFreqCalc.m | 90 +- tests/test_compat.m | 120 +- tests/test_helpers.m | 96 +- 56 files changed, 6054 insertions(+), 6046 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..88a91dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +*.m text eol=lf +*.sh text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.md text eol=lf +*.txt text eol=lf +Makefile text eol=lf +Dockerfile text eol=lf diff --git a/src/compat/PSdatatipSetup.m b/src/compat/PSdatatipSetup.m index 58fb127..23e1654 100644 --- a/src/compat/PSdatatipSetup.m +++ b/src/compat/PSdatatipSetup.m @@ -1,47 +1,47 @@ -function PSdatatipSetup(h) -% PSdatatipSetup - Set up click-to-show-value on axes (Octave) or figure (MATLAB) -% -% In Octave: sets ButtonDownFcn on each axes + HitTest='off' on children -% In MATLAB: uses datacursormode with @PSdatatip callback (original behavior) -% -% Usage: -% PSdatatipSetup(fig) - find all axes in figure and set up each (Octave) -% - set datacursormode (MATLAB) -% PSdatatipSetup(ax) - set up single axes (Octave only) -% -% Call this AFTER plots are created (in plotting functions, not UI setup). - - if ~exist('OCTAVE_VERSION', 'builtin') - % MATLAB: datacursormode on figure (only if figure handle) - if strcmp(get(h, 'Type'), 'figure') - dcm_obj = datacursormode(h); - set(dcm_obj, 'UpdateFcn', @PSdatatip); - end - return - end - - % Octave: axes-level ButtonDownFcn - t = get(h, 'Type'); - if strcmp(t, 'figure') - % Find all axes in figure and set up each - allax = findobj(h, 'Type', 'axes'); - for k = 1:length(allax) - setup_axes(allax(k)); - end - elseif strcmp(t, 'axes') - setup_axes(h); - end -end - - -function setup_axes(ax) - set(ax, 'ButtonDownFcn', @(src, ~) PSdatatip_click(src)); - % Make children pass clicks through to axes ButtonDownFcn - ch = get(ax, 'Children'); - for k = 1:length(ch) - try - set(ch(k), 'HitTest', 'off'); - catch - end - end -end +function PSdatatipSetup(h) +% PSdatatipSetup - Set up click-to-show-value on axes (Octave) or figure (MATLAB) +% +% In Octave: sets ButtonDownFcn on each axes + HitTest='off' on children +% In MATLAB: uses datacursormode with @PSdatatip callback (original behavior) +% +% Usage: +% PSdatatipSetup(fig) - find all axes in figure and set up each (Octave) +% - set datacursormode (MATLAB) +% PSdatatipSetup(ax) - set up single axes (Octave only) +% +% Call this AFTER plots are created (in plotting functions, not UI setup). + + if ~exist('OCTAVE_VERSION', 'builtin') + % MATLAB: datacursormode on figure (only if figure handle) + if strcmp(get(h, 'Type'), 'figure') + dcm_obj = datacursormode(h); + set(dcm_obj, 'UpdateFcn', @PSdatatip); + end + return + end + + % Octave: axes-level ButtonDownFcn + t = get(h, 'Type'); + if strcmp(t, 'figure') + % Find all axes in figure and set up each + allax = findobj(h, 'Type', 'axes'); + for k = 1:length(allax) + setup_axes(allax(k)); + end + elseif strcmp(t, 'axes') + setup_axes(h); + end +end + + +function setup_axes(ax) + set(ax, 'ButtonDownFcn', @(src, ~) PSdatatip_click(src)); + % Make children pass clicks through to axes ButtonDownFcn + ch = get(ax, 'Children'); + for k = 1:length(ch) + try + set(ch(k), 'HitTest', 'off'); + catch + end + end +end diff --git a/src/compat/PSuigetfile.m b/src/compat/PSuigetfile.m index 7f41841..384cd68 100644 --- a/src/compat/PSuigetfile.m +++ b/src/compat/PSuigetfile.m @@ -1,95 +1,95 @@ -function [filenames, filepath] = PSuigetfile(filter_spec, dialog_title, initial_dir, varargin) -% PSuigetfile - File selection dialog with Flatpak zenity fallback -% -% Octave 10.x uigetfile in Flatpak returns the INITIAL directory instead of -% the directory the user navigated to. This wrapper uses zenity --file-selection -% when running inside a Flatpak sandbox (detected via /.flatpak-info). -% -% Usage is identical to uigetfile: -% [filename, filepath] = PSuigetfile(filter, title, startdir, 'MultiSelect','on') - - multi_select = false; - for k = 1:2:numel(varargin) - if strcmpi(varargin{k}, 'MultiSelect') && strcmpi(varargin{k+1}, 'on') - multi_select = true; - end - end - - % Use zenity in Flatpak (/.flatpak-info always exists inside sandbox) - if exist('/.flatpak-info', 'file') - [filenames, filepath] = zenity_dialog(filter_spec, dialog_title, initial_dir, multi_select); - else - if multi_select - [filenames, filepath] = uigetfile(filter_spec, dialog_title, initial_dir, 'MultiSelect', 'on'); - else - [filenames, filepath] = uigetfile(filter_spec, dialog_title, initial_dir); - end - end -end - - -function [filenames, filepath] = zenity_dialog(filter_spec, title, initial_dir, multi_select) - cmd = 'zenity --file-selection'; - - if nargin >= 2 && ~isempty(title) - cmd = [cmd ' --title="' title '"']; - end - - if multi_select - cmd = [cmd ' --multiple --separator="|"']; - end - - if nargin >= 3 && ~isempty(initial_dir) && exist(initial_dir, 'dir') - if initial_dir(end) ~= filesep - initial_dir = [initial_dir filesep]; - end - cmd = [cmd ' --filename="' initial_dir '"']; - end - - % Convert uigetfile filter to zenity format - % uigetfile: {'*.bbl;*.BBL;*.bfl;*.BFL', 'Blackbox Logs'} - % zenity: --file-filter="Blackbox Logs | *.bbl *.BBL *.bfl *.BFL" - if iscell(filter_spec) && size(filter_spec, 2) >= 2 - patterns = strsplit(filter_spec{1,1}, ';'); - filter_name = filter_spec{1,2}; - zf = ['"' filter_name ' |']; - for k = 1:numel(patterns) - zf = [zf ' ' strtrim(patterns{k})]; - end - zf = [zf '"']; - cmd = [cmd ' --file-filter=' zf]; - end - cmd = [cmd ' --file-filter="All files | *"']; - - [status, result] = system(cmd); - result = strtrim(result); - - if status ~= 0 || isempty(result) - filenames = 0; - filepath = 0; - return; - end - - if multi_select - paths = strsplit(result, '|'); - else - paths = {result}; - end - - % Extract directory from first file - [dirpart, ~, ~] = fileparts(paths{1}); - filepath = [dirpart filesep]; - - % Extract just filenames - fnames = cell(1, numel(paths)); - for k = 1:numel(paths) - [~, name, ext] = fileparts(paths{k}); - fnames{k} = [name ext]; - end - - if numel(fnames) == 1 && ~multi_select - filenames = fnames{1}; - else - filenames = fnames; - end -end +function [filenames, filepath] = PSuigetfile(filter_spec, dialog_title, initial_dir, varargin) +% PSuigetfile - File selection dialog with Flatpak zenity fallback +% +% Octave 10.x uigetfile in Flatpak returns the INITIAL directory instead of +% the directory the user navigated to. This wrapper uses zenity --file-selection +% when running inside a Flatpak sandbox (detected via /.flatpak-info). +% +% Usage is identical to uigetfile: +% [filename, filepath] = PSuigetfile(filter, title, startdir, 'MultiSelect','on') + + multi_select = false; + for k = 1:2:numel(varargin) + if strcmpi(varargin{k}, 'MultiSelect') && strcmpi(varargin{k+1}, 'on') + multi_select = true; + end + end + + % Use zenity in Flatpak (/.flatpak-info always exists inside sandbox) + if exist('/.flatpak-info', 'file') + [filenames, filepath] = zenity_dialog(filter_spec, dialog_title, initial_dir, multi_select); + else + if multi_select + [filenames, filepath] = uigetfile(filter_spec, dialog_title, initial_dir, 'MultiSelect', 'on'); + else + [filenames, filepath] = uigetfile(filter_spec, dialog_title, initial_dir); + end + end +end + + +function [filenames, filepath] = zenity_dialog(filter_spec, title, initial_dir, multi_select) + cmd = 'zenity --file-selection'; + + if nargin >= 2 && ~isempty(title) + cmd = [cmd ' --title="' title '"']; + end + + if multi_select + cmd = [cmd ' --multiple --separator="|"']; + end + + if nargin >= 3 && ~isempty(initial_dir) && exist(initial_dir, 'dir') + if initial_dir(end) ~= filesep + initial_dir = [initial_dir filesep]; + end + cmd = [cmd ' --filename="' initial_dir '"']; + end + + % Convert uigetfile filter to zenity format + % uigetfile: {'*.bbl;*.BBL;*.bfl;*.BFL', 'Blackbox Logs'} + % zenity: --file-filter="Blackbox Logs | *.bbl *.BBL *.bfl *.BFL" + if iscell(filter_spec) && size(filter_spec, 2) >= 2 + patterns = strsplit(filter_spec{1,1}, ';'); + filter_name = filter_spec{1,2}; + zf = ['"' filter_name ' |']; + for k = 1:numel(patterns) + zf = [zf ' ' strtrim(patterns{k})]; + end + zf = [zf '"']; + cmd = [cmd ' --file-filter=' zf]; + end + cmd = [cmd ' --file-filter="All files | *"']; + + [status, result] = system(cmd); + result = strtrim(result); + + if status ~= 0 || isempty(result) + filenames = 0; + filepath = 0; + return; + end + + if multi_select + paths = strsplit(result, '|'); + else + paths = {result}; + end + + % Extract directory from first file + [dirpart, ~, ~] = fileparts(paths{1}); + filepath = [dirpart filesep]; + + % Extract just filenames + fnames = cell(1, numel(paths)); + for k = 1:numel(paths) + [~, name, ext] = fileparts(paths{k}); + fnames{k} = [name ext]; + end + + if numel(fnames) == 1 && ~multi_select + filenames = fnames{1}; + else + filenames = fnames; + end +end diff --git a/src/compat/cell2table.m b/src/compat/cell2table.m index 0003c72..9e5b765 100644 --- a/src/compat/cell2table.m +++ b/src/compat/cell2table.m @@ -1,46 +1,46 @@ -function T = cell2table(C, varargin) -%% CELL2TABLE - Octave-compatible cell array to table conversion -% Drop-in replacement for MATLAB's cell2table() returning a struct. -% -% Usage: -% T = cell2table(C, 'VariableNames', {'col1', 'col2', ...}) -% -% C is a cell array where each column becomes a field in the struct. - - % Parse VariableNames - var_names = {}; - for i = 1:2:length(varargin) - if strcmpi(varargin{i}, 'VariableNames') - var_names = varargin{i+1}; - if ~iscell(var_names) - var_names = cellstr(var_names); - end - var_names = var_names(:)'; % ensure row vector - end - end - - ncols = size(C, 2); - if isempty(var_names) - var_names = arrayfun(@(x) sprintf('Var%d', x), 1:ncols, 'UniformOutput', false); - end - - T = struct(); - for j = 1:ncols - col_data = C(:, j); - % Check if all entries are numeric - all_numeric = true; - for k = 1:length(col_data) - if ~isnumeric(col_data{k}) - all_numeric = false; - break; - end - end - if all_numeric - T.(var_names{j}) = cell2mat(col_data); - else - T.(var_names{j}) = col_data; - end - end - T.Properties = struct('VariableNames', {var_names}); - -end +function T = cell2table(C, varargin) +%% CELL2TABLE - Octave-compatible cell array to table conversion +% Drop-in replacement for MATLAB's cell2table() returning a struct. +% +% Usage: +% T = cell2table(C, 'VariableNames', {'col1', 'col2', ...}) +% +% C is a cell array where each column becomes a field in the struct. + + % Parse VariableNames + var_names = {}; + for i = 1:2:length(varargin) + if strcmpi(varargin{i}, 'VariableNames') + var_names = varargin{i+1}; + if ~iscell(var_names) + var_names = cellstr(var_names); + end + var_names = var_names(:)'; % ensure row vector + end + end + + ncols = size(C, 2); + if isempty(var_names) + var_names = arrayfun(@(x) sprintf('Var%d', x), 1:ncols, 'UniformOutput', false); + end + + T = struct(); + for j = 1:ncols + col_data = C(:, j); + % Check if all entries are numeric + all_numeric = true; + for k = 1:length(col_data) + if ~isnumeric(col_data{k}) + all_numeric = false; + break; + end + end + if all_numeric + T.(var_names{j}) = cell2mat(col_data); + else + T.(var_names{j}) = col_data; + end + end + T.Properties = struct('VariableNames', {var_names}); + +end diff --git a/src/compat/contains.m b/src/compat/contains.m index 1bb9c1e..ddae551 100644 --- a/src/compat/contains.m +++ b/src/compat/contains.m @@ -1,43 +1,43 @@ -function tf = contains(str, pat, varargin) -% contains - Octave compatibility shim for MATLAB's contains() -% Copyright (C) 2026 Grzegorz Sterniczuk -% License: GPL v3 (see LICENSE) - - ignorecase = false; - for i = 1:2:numel(varargin) - if strcmpi(varargin{i}, 'IgnoreCase') - ignorecase = varargin{i+1}; - end - end - - if iscell(str) - tf = false(size(str)); - for k = 1:numel(str) - tf(k) = check_contains(str{k}, pat, ignorecase); - end - else - tf = check_contains(str, pat, ignorecase); - end -end - -function tf = check_contains(s, pat, ignorecase) - if iscell(pat) - tf = false; - for k = 1:numel(pat) - if check_one(s, pat{k}, ignorecase) - tf = true; - return; - end - end - else - tf = check_one(s, pat, ignorecase); - end -end - -function tf = check_one(s, p, ignorecase) - if ignorecase - tf = ~isempty(strfind(lower(s), lower(p))); - else - tf = ~isempty(strfind(s, p)); - end -end +function tf = contains(str, pat, varargin) +% contains - Octave compatibility shim for MATLAB's contains() +% Copyright (C) 2026 Grzegorz Sterniczuk +% License: GPL v3 (see LICENSE) + + ignorecase = false; + for i = 1:2:numel(varargin) + if strcmpi(varargin{i}, 'IgnoreCase') + ignorecase = varargin{i+1}; + end + end + + if iscell(str) + tf = false(size(str)); + for k = 1:numel(str) + tf(k) = check_contains(str{k}, pat, ignorecase); + end + else + tf = check_contains(str, pat, ignorecase); + end +end + +function tf = check_contains(s, pat, ignorecase) + if iscell(pat) + tf = false; + for k = 1:numel(pat) + if check_one(s, pat{k}, ignorecase) + tf = true; + return; + end + end + else + tf = check_one(s, pat, ignorecase); + end +end + +function tf = check_one(s, p, ignorecase) + if ignorecase + tf = ~isempty(strfind(lower(s), lower(p))); + else + tf = ~isempty(strfind(s, p)); + end +end diff --git a/src/compat/detectImportOptions.m b/src/compat/detectImportOptions.m index c16b7a9..2d64759 100644 --- a/src/compat/detectImportOptions.m +++ b/src/compat/detectImportOptions.m @@ -1,40 +1,40 @@ -function opts = detectImportOptions(filename) -%% DETECTIMPORTOPTIONS - Octave-compatible import options detector -% Simplified drop-in for MATLAB's detectImportOptions(). -% Reads the header row of a CSV file and returns options with VariableNames. -% -% Usage: -% opts = detectImportOptions(filename) - - fid = fopen(filename, 'r'); - if fid == -1 - error('detectImportOptions: cannot open file %s', filename); - end - - header_line = fgetl(fid); - fclose(fid); - - if ~ischar(header_line) - opts = struct('VariableNames', {{}}); - return; - end - - col_names = strsplit(strtrim(header_line), ','); - clean_names = cell(size(col_names)); - for i = 1:length(col_names) - s = strtrim(col_names{i}); - s = regexprep(s, '[\[\]\(\) ]+', '_'); - s = regexprep(s, '[^a-zA-Z0-9_]', '_'); - if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') - s = ['x' s]; - end - s = regexprep(s, '_+', '_'); - if isempty(s) - s = sprintf('Var%d', i); - end - clean_names{i} = s; - end - - opts = struct('VariableNames', {clean_names}); - -end +function opts = detectImportOptions(filename) +%% DETECTIMPORTOPTIONS - Octave-compatible import options detector +% Simplified drop-in for MATLAB's detectImportOptions(). +% Reads the header row of a CSV file and returns options with VariableNames. +% +% Usage: +% opts = detectImportOptions(filename) + + fid = fopen(filename, 'r'); + if fid == -1 + error('detectImportOptions: cannot open file %s', filename); + end + + header_line = fgetl(fid); + fclose(fid); + + if ~ischar(header_line) + opts = struct('VariableNames', {{}}); + return; + end + + col_names = strsplit(strtrim(header_line), ','); + clean_names = cell(size(col_names)); + for i = 1:length(col_names) + s = strtrim(col_names{i}); + s = regexprep(s, '[\[\]\(\) ]+', '_'); + s = regexprep(s, '[^a-zA-Z0-9_]', '_'); + if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') + s = ['x' s]; + end + s = regexprep(s, '_+', '_'); + if isempty(s) + s = sprintf('Var%d', i); + end + clean_names{i} = s; + end + + opts = struct('VariableNames', {clean_names}); + +end diff --git a/src/compat/dkl2rgb.m b/src/compat/dkl2rgb.m index 60b6d2a..38f8ddc 100644 --- a/src/compat/dkl2rgb.m +++ b/src/compat/dkl2rgb.m @@ -1,280 +1,280 @@ -function rgb = dkl2rgb(dkl) -% dkl2rgb - convert DKL cone opponent colors to RGB -% rgb = dkl2rgb(dkl) -% input (dkl) is [luminance; red/green; blue/yellow] -% -% Copyright (C) 2026 Grzegorz Sterniczuk -% License: GPL v3 (see LICENSE) -% Extracted from PScolormap.m for Octave compatibility - -M_dkl2rgb = [ - 1.0000 1.0000 -0.3495 - 1.0000 -0.3203 0.3113 - 1.0000 0.0133 -1.0000 - ]; - - rgb = 0.5 + M_dkl2rgb*(dkl')/2; - rgb = uint8(rgb*255)+1; - - gamma_corr=[ 0 0 0 - 11 5 1 - 16 8 2 - 20 11 3 - 24 15 5 - 27 18 5 - 30 20 6 - 33 23 7 - 35 25 8 - 38 27 9 - 40 29 10 - 42 31 11 - 45 33 13 - 47 35 14 - 49 37 15 - 51 40 16 - 53 41 17 - 54 43 18 - 56 45 20 - 58 46 21 - 60 48 22 - 61 50 23 - 63 51 23 - 65 53 24 - 66 54 25 - 68 56 26 - 69 57 28 - 71 58 29 - 72 60 30 - 74 61 31 - 75 63 32 - 77 65 33 - 78 66 34 - 79 68 36 - 81 69 36 - 82 70 37 - 83 72 38 - 85 73 39 - 86 74 40 - 87 75 41 - 89 77 43 - 90 78 44 - 91 79 45 - 92 80 46 - 94 81 47 - 95 83 47 - 96 84 48 - 97 85 49 - 98 86 51 - 99 87 52 - 101 88 53 - 102 90 54 - 103 92 55 - 104 93 56 - 105 94 57 - 106 95 57 - 107 96 59 - 108 97 60 - 109 98 61 - 110 99 62 - 112 100 63 - 113 101 64 - 114 102 65 - 115 103 67 - 116 104 67 - 117 105 68 - 118 106 69 - 119 107 70 - 120 108 71 - 121 109 72 - 122 110 74 - 123 111 75 - 124 112 76 - 125 113 76 - 127 114 77 - 128 116 78 - 128 117 79 - 130 118 80 - 131 119 82 - 131 120 83 - 132 121 84 - 133 122 84 - 134 123 85 - 135 124 86 - 136 125 87 - 137 126 88 - 138 127 90 - 139 128 91 - 140 129 92 - 141 129 92 - 141 130 93 - 142 131 94 - 143 132 95 - 144 133 96 - 145 134 98 - 146 135 99 - 147 136 100 - 148 137 100 - 148 137 101 - 149 138 102 - 150 139 103 - 151 141 105 - 152 142 106 - 153 143 107 - 154 144 108 - 154 145 108 - 155 145 109 - 156 146 110 - 157 147 111 - 158 148 113 - 158 149 114 - 159 150 115 - 160 150 116 - 161 151 116 - 162 152 117 - 163 153 118 - 163 154 119 - 164 155 121 - 165 155 122 - 166 156 123 - 166 157 123 - 167 158 124 - 168 159 125 - 169 159 126 - 170 160 128 - 170 161 129 - 171 162 130 - 172 163 130 - 173 163 131 - 173 164 132 - 174 165 133 - 175 167 134 - 176 168 136 - 176 168 137 - 177 169 138 - 178 170 138 - 179 171 139 - 179 171 140 - 180 172 141 - 181 173 142 - 182 174 144 - 182 175 145 - 183 175 145 - 184 176 146 - 185 177 147 - 185 178 148 - 186 178 149 - 187 179 150 - 187 180 152 - 188 181 152 - 189 181 153 - 190 182 154 - 190 183 155 - 191 184 156 - 192 184 157 - 192 185 157 - 193 186 159 - 194 186 160 - 194 187 161 - 195 188 162 - 196 189 163 - 197 189 164 - 197 190 164 - 198 192 165 - 199 193 167 - 199 193 168 - 200 194 169 - 201 195 170 - 201 195 171 - 202 196 171 - 203 197 172 - 203 198 173 - 204 198 175 - 205 199 176 - 205 200 177 - 206 200 178 - 207 201 178 - 207 202 179 - 208 202 180 - 209 203 181 - 209 204 183 - 210 205 184 - 211 205 184 - 211 206 185 - 212 207 186 - 213 207 187 - 213 208 188 - 214 209 190 - 215 209 191 - 215 210 191 - 216 211 192 - 217 211 193 - 217 212 194 - 218 213 195 - 218 213 196 - 219 214 196 - 220 215 198 - 220 215 199 - 221 216 200 - 222 218 201 - 222 219 202 - 223 219 203 - 224 220 203 - 224 221 204 - 225 221 206 - 225 222 207 - 226 222 208 - 227 223 209 - 227 224 209 - 228 224 210 - 228 225 211 - 229 226 212 - 230 226 214 - 230 227 215 - 231 228 215 - 232 228 216 - 232 229 217 - 233 230 218 - 233 230 219 - 234 231 221 - 235 232 222 - 235 232 222 - 236 233 223 - 236 234 224 - 237 234 225 - 238 235 226 - 238 235 227 - 239 236 227 - 239 237 229 - 240 237 230 - 241 238 231 - 241 239 232 - 242 239 233 - 242 240 233 - 243 241 234 - 244 241 235 - 244 243 237 - 245 243 238 - 245 244 239 - 246 245 239 - 246 245 240 - 247 246 241 - 248 247 242 - 248 247 244 - 249 248 245 - 249 248 245 - 250 249 246 - 250 250 247 - 251 250 248 - 252 251 249 - 252 251 250 - 253 252 250 - 253 253 252 - 254 253 253 - 254 254 254 - 255 255 255]; - - rgb = [gamma_corr(rgb(1),1); gamma_corr(rgb(2),2); gamma_corr(rgb(3),3)]; - rgb(rgb<0)=0; - rgb(rgb>255)=255; - -end +function rgb = dkl2rgb(dkl) +% dkl2rgb - convert DKL cone opponent colors to RGB +% rgb = dkl2rgb(dkl) +% input (dkl) is [luminance; red/green; blue/yellow] +% +% Copyright (C) 2026 Grzegorz Sterniczuk +% License: GPL v3 (see LICENSE) +% Extracted from PScolormap.m for Octave compatibility + +M_dkl2rgb = [ + 1.0000 1.0000 -0.3495 + 1.0000 -0.3203 0.3113 + 1.0000 0.0133 -1.0000 + ]; + + rgb = 0.5 + M_dkl2rgb*(dkl')/2; + rgb = uint8(rgb*255)+1; + + gamma_corr=[ 0 0 0 + 11 5 1 + 16 8 2 + 20 11 3 + 24 15 5 + 27 18 5 + 30 20 6 + 33 23 7 + 35 25 8 + 38 27 9 + 40 29 10 + 42 31 11 + 45 33 13 + 47 35 14 + 49 37 15 + 51 40 16 + 53 41 17 + 54 43 18 + 56 45 20 + 58 46 21 + 60 48 22 + 61 50 23 + 63 51 23 + 65 53 24 + 66 54 25 + 68 56 26 + 69 57 28 + 71 58 29 + 72 60 30 + 74 61 31 + 75 63 32 + 77 65 33 + 78 66 34 + 79 68 36 + 81 69 36 + 82 70 37 + 83 72 38 + 85 73 39 + 86 74 40 + 87 75 41 + 89 77 43 + 90 78 44 + 91 79 45 + 92 80 46 + 94 81 47 + 95 83 47 + 96 84 48 + 97 85 49 + 98 86 51 + 99 87 52 + 101 88 53 + 102 90 54 + 103 92 55 + 104 93 56 + 105 94 57 + 106 95 57 + 107 96 59 + 108 97 60 + 109 98 61 + 110 99 62 + 112 100 63 + 113 101 64 + 114 102 65 + 115 103 67 + 116 104 67 + 117 105 68 + 118 106 69 + 119 107 70 + 120 108 71 + 121 109 72 + 122 110 74 + 123 111 75 + 124 112 76 + 125 113 76 + 127 114 77 + 128 116 78 + 128 117 79 + 130 118 80 + 131 119 82 + 131 120 83 + 132 121 84 + 133 122 84 + 134 123 85 + 135 124 86 + 136 125 87 + 137 126 88 + 138 127 90 + 139 128 91 + 140 129 92 + 141 129 92 + 141 130 93 + 142 131 94 + 143 132 95 + 144 133 96 + 145 134 98 + 146 135 99 + 147 136 100 + 148 137 100 + 148 137 101 + 149 138 102 + 150 139 103 + 151 141 105 + 152 142 106 + 153 143 107 + 154 144 108 + 154 145 108 + 155 145 109 + 156 146 110 + 157 147 111 + 158 148 113 + 158 149 114 + 159 150 115 + 160 150 116 + 161 151 116 + 162 152 117 + 163 153 118 + 163 154 119 + 164 155 121 + 165 155 122 + 166 156 123 + 166 157 123 + 167 158 124 + 168 159 125 + 169 159 126 + 170 160 128 + 170 161 129 + 171 162 130 + 172 163 130 + 173 163 131 + 173 164 132 + 174 165 133 + 175 167 134 + 176 168 136 + 176 168 137 + 177 169 138 + 178 170 138 + 179 171 139 + 179 171 140 + 180 172 141 + 181 173 142 + 182 174 144 + 182 175 145 + 183 175 145 + 184 176 146 + 185 177 147 + 185 178 148 + 186 178 149 + 187 179 150 + 187 180 152 + 188 181 152 + 189 181 153 + 190 182 154 + 190 183 155 + 191 184 156 + 192 184 157 + 192 185 157 + 193 186 159 + 194 186 160 + 194 187 161 + 195 188 162 + 196 189 163 + 197 189 164 + 197 190 164 + 198 192 165 + 199 193 167 + 199 193 168 + 200 194 169 + 201 195 170 + 201 195 171 + 202 196 171 + 203 197 172 + 203 198 173 + 204 198 175 + 205 199 176 + 205 200 177 + 206 200 178 + 207 201 178 + 207 202 179 + 208 202 180 + 209 203 181 + 209 204 183 + 210 205 184 + 211 205 184 + 211 206 185 + 212 207 186 + 213 207 187 + 213 208 188 + 214 209 190 + 215 209 191 + 215 210 191 + 216 211 192 + 217 211 193 + 217 212 194 + 218 213 195 + 218 213 196 + 219 214 196 + 220 215 198 + 220 215 199 + 221 216 200 + 222 218 201 + 222 219 202 + 223 219 203 + 224 220 203 + 224 221 204 + 225 221 206 + 225 222 207 + 226 222 208 + 227 223 209 + 227 224 209 + 228 224 210 + 228 225 211 + 229 226 212 + 230 226 214 + 230 227 215 + 231 228 215 + 232 228 216 + 232 229 217 + 233 230 218 + 233 230 219 + 234 231 221 + 235 232 222 + 235 232 222 + 236 233 223 + 236 234 224 + 237 234 225 + 238 235 226 + 238 235 227 + 239 236 227 + 239 237 229 + 240 237 230 + 241 238 231 + 241 239 232 + 242 239 233 + 242 240 233 + 243 241 234 + 244 241 235 + 244 243 237 + 245 243 238 + 245 244 239 + 246 245 239 + 246 245 240 + 247 246 241 + 248 247 242 + 248 247 244 + 249 248 245 + 249 248 245 + 250 249 246 + 250 250 247 + 251 250 248 + 252 251 249 + 252 251 250 + 253 252 250 + 253 253 252 + 254 253 253 + 254 254 254 + 255 255 255]; + + rgb = [gamma_corr(rgb(1),1); gamma_corr(rgb(2),2); gamma_corr(rgb(3),3)]; + rgb(rgb<0)=0; + rgb(rgb>255)=255; + +end diff --git a/src/compat/fields.m b/src/compat/fields.m index ef31e83..25041ee 100644 --- a/src/compat/fields.m +++ b/src/compat/fields.m @@ -1,6 +1,6 @@ -function f = fields(s) -% fields - Octave compatibility shim, calls fieldnames -% Copyright (C) 2026 Grzegorz Sterniczuk -% License: GPL v3 (see LICENSE) - f = fieldnames(s); -end +function f = fields(s) +% fields - Octave compatibility shim, calls fieldnames +% Copyright (C) 2026 Grzegorz Sterniczuk +% License: GPL v3 (see LICENSE) + f = fieldnames(s); +end diff --git a/src/compat/finddelay.m b/src/compat/finddelay.m index 2c03da0..721d3ed 100644 --- a/src/compat/finddelay.m +++ b/src/compat/finddelay.m @@ -1,25 +1,25 @@ -function d = finddelay(x, y, maxlag) -%% FINDDELAY - Octave-compatible delay estimation via cross-correlation -% Drop-in replacement for MATLAB Signal Processing Toolbox finddelay() -% -% Usage: -% d = finddelay(x, y) - estimate delay between x and y -% d = finddelay(x, y, maxlag) - with maximum lag constraint -% -% Returns positive d when y is a delayed copy of x. -% Uses xcorr from the Octave signal package. - - if nargin < 3 - maxlag = max(length(x), length(y)) - 1; - end - - x = x(:); - y = y(:); - - % Octave's xcorr lag convention is opposite to MATLAB's finddelay: - % xcorr(x,y) peaks at lag=-D when y is delayed by D relative to x - [c, lags] = xcorr(x, y, maxlag); - [~, idx] = max(abs(c)); - d = -lags(idx); - -end +function d = finddelay(x, y, maxlag) +%% FINDDELAY - Octave-compatible delay estimation via cross-correlation +% Drop-in replacement for MATLAB Signal Processing Toolbox finddelay() +% +% Usage: +% d = finddelay(x, y) - estimate delay between x and y +% d = finddelay(x, y, maxlag) - with maximum lag constraint +% +% Returns positive d when y is a delayed copy of x. +% Uses xcorr from the Octave signal package. + + if nargin < 3 + maxlag = max(length(x), length(y)) - 1; + end + + x = x(:); + y = y(:); + + % Octave's xcorr lag convention is opposite to MATLAB's finddelay: + % xcorr(x,y) peaks at lag=-D when y is delayed by D relative to x + [c, lags] = xcorr(x, y, maxlag); + [~, idx] = max(abs(c)); + d = -lags(idx); + +end diff --git a/src/compat/nanmean.m b/src/compat/nanmean.m index 0ec1724..5822783 100644 --- a/src/compat/nanmean.m +++ b/src/compat/nanmean.m @@ -1,24 +1,24 @@ -function m = nanmean(x, dim) -%% NANMEAN - Mean value ignoring NaN entries -% Drop-in replacement for MATLAB/Statistics Toolbox nanmean() -% -% Usage: -% m = nanmean(x) - mean along first non-singleton dimension -% m = nanmean(x, dim) - mean along specified dimension - - if nargin < 2 - if isvector(x) - dim = find(size(x) > 1, 1); - if isempty(dim), dim = 1; end - else - dim = 1; - end - end - - mask = ~isnan(x); - x(~mask) = 0; - n = sum(mask, dim); - n(n == 0) = NaN; - m = sum(x, dim) ./ n; - -end +function m = nanmean(x, dim) +%% NANMEAN - Mean value ignoring NaN entries +% Drop-in replacement for MATLAB/Statistics Toolbox nanmean() +% +% Usage: +% m = nanmean(x) - mean along first non-singleton dimension +% m = nanmean(x, dim) - mean along specified dimension + + if nargin < 2 + if isvector(x) + dim = find(size(x) > 1, 1); + if isempty(dim), dim = 1; end + else + dim = 1; + end + end + + mask = ~isnan(x); + x(~mask) = 0; + n = sum(mask, dim); + n(n == 0) = NaN; + m = sum(x, dim) ./ n; + +end diff --git a/src/compat/nanmedian.m b/src/compat/nanmedian.m index e1d3138..48a1f84 100644 --- a/src/compat/nanmedian.m +++ b/src/compat/nanmedian.m @@ -1,47 +1,47 @@ -function m = nanmedian(x, dim) -%% NANMEDIAN - Median value ignoring NaN entries -% Drop-in replacement for MATLAB/Statistics Toolbox nanmedian() -% -% Usage: -% m = nanmedian(x) - median along first non-singleton dimension -% m = nanmedian(x, dim) - median along specified dimension - - if nargin < 2 - % Vector case - just filter NaN and take median - if isvector(x) - x = x(~isnan(x)); - if isempty(x) - m = NaN; - else - m = median(x); - end - return; - end - dim = 1; - end - - sz = size(x); - % Build output size (collapse dim to 1) - out_sz = sz; - out_sz(dim) = 1; - m = NaN(out_sz); - - % Iterate over all slices along the target dimension - n_slices = prod(sz) / sz(dim); - % Reshape to put target dim first - perm = [dim, 1:dim-1, dim+1:ndims(x)]; - xp = permute(x, perm); - xp = reshape(xp, sz(dim), []); - - mv = NaN(1, size(xp, 2)); - for j = 1:size(xp, 2) - col = xp(:, j); - col = col(~isnan(col)); - if ~isempty(col) - mv(j) = median(col); - end - end - - m = reshape(mv, out_sz); - -end +function m = nanmedian(x, dim) +%% NANMEDIAN - Median value ignoring NaN entries +% Drop-in replacement for MATLAB/Statistics Toolbox nanmedian() +% +% Usage: +% m = nanmedian(x) - median along first non-singleton dimension +% m = nanmedian(x, dim) - median along specified dimension + + if nargin < 2 + % Vector case - just filter NaN and take median + if isvector(x) + x = x(~isnan(x)); + if isempty(x) + m = NaN; + else + m = median(x); + end + return; + end + dim = 1; + end + + sz = size(x); + % Build output size (collapse dim to 1) + out_sz = sz; + out_sz(dim) = 1; + m = NaN(out_sz); + + % Iterate over all slices along the target dimension + n_slices = prod(sz) / sz(dim); + % Reshape to put target dim first + perm = [dim, 1:dim-1, dim+1:ndims(x)]; + xp = permute(x, perm); + xp = reshape(xp, sz(dim), []); + + mv = NaN(1, size(xp, 2)); + for j = 1:size(xp, 2) + col = xp(:, j); + col = col(~isnan(col)); + if ~isempty(col) + mv(j) = median(col); + end + end + + m = reshape(mv, out_sz); + +end diff --git a/src/compat/readtable.m b/src/compat/readtable.m index 62ddef1..348e331 100644 --- a/src/compat/readtable.m +++ b/src/compat/readtable.m @@ -1,179 +1,179 @@ -function T = readtable(filename, varargin) -%% READTABLE - Octave-compatible CSV/text file reader -% Drop-in replacement for MATLAB's readtable() returning a struct -% with columns accessible via dot notation (T.colname). -% -% Copyright (C) 2026 Grzegorz Sterniczuk -% License: GPL v3 (see LICENSE) -% -% Usage: -% T = readtable(filename) -% T = readtable(filename, 'HeaderLines', N) -% T = readtable(filename, 'Format', fmt) -% -% Returns a struct where each field is a column vector. -% T.Properties.VariableNames contains the column names. - - % Parse optional arguments - headerlines = 0; - fmt = ''; - for i = 1:2:length(varargin) - key = varargin{i}; - val = varargin{i+1}; - if strcmpi(key, 'HeaderLines') - headerlines = val; - elseif strcmpi(key, 'Format') - fmt = val; - end - end - - fid = fopen(filename, 'r'); - if fid == -1 - error('readtable: cannot open file %s', filename); - end - - % Skip header lines - for i = 1:headerlines - fgetl(fid); - end - - % Read the first non-skipped line as column headers - header_line = fgetl(fid); - if ~ischar(header_line) - fclose(fid); - T = struct(); - T.Properties = struct('VariableNames', {{}}); - return; - end - - % Parse column names from header - col_names = strsplit(strtrim(header_line), ','); - clean_names = sanitize_varnames(col_names); - ncols = length(clean_names); - - % Read first data line to detect column types - first_data_line = fgetl(fid); - if ~ischar(first_data_line) - fclose(fid); - T = struct(); - for j = 1:ncols - T.(clean_names{j}) = []; - end - T.Properties = struct('VariableNames', {clean_names}); - return; - end - - % Detect which columns are numeric vs string - is_numeric = true(1, ncols); - first_fields = strsplit(first_data_line, ','); - for j = 1:min(length(first_fields), ncols) - val = strtrim(first_fields{j}); - num = str2double(val); - % Non-numeric value, or column header contains "flags" -> force string - if (isnan(num) && ~strcmpi(val, 'nan')) || ~isempty(regexpi(col_names{j}, 'flags')) - is_numeric(j) = false; - end - end - - % Build format string based on detected types - fmt_parts = cell(1, ncols); - for j = 1:ncols - if is_numeric(j) - fmt_parts{j} = '%f'; - else - fmt_parts{j} = '%s'; - end - end - fmt_str = strjoin(fmt_parts, ''); - - % Rewind to start of data (skip headerlines + column header line) - frewind(fid); - for i = 1:(headerlines + 1) - fgetl(fid); - end - - % Use textscan directly on file handle - orders of magnitude faster - % than reading lines into cells and joining them - C = textscan(fid, fmt_str, 'Delimiter', ',', 'EmptyValue', NaN); - fclose(fid); - - if length(C) == ncols && ~isempty(C{1}) - T = struct(); - for j = 1:ncols - T.(clean_names{j}) = C{j}; - end - T.Properties = struct('VariableNames', {clean_names}); - return; - end - - % Fallback: reopen and parse line by line (slow, for edge cases) - warning('readtable: textscan returned %d/%d columns, falling back to line-by-line', length(C), ncols); - fid = fopen(filename, 'r'); - for i = 1:(headerlines + 1) - fgetl(fid); - end - - data_lines = {}; - while ~feof(fid) - line = fgetl(fid); - if ischar(line) && ~isempty(strtrim(line)) - data_lines{end+1} = line; - end - end - fclose(fid); - - nrows = length(data_lines); - string_cols = cell(nrows, ncols); - numeric_cols = NaN(nrows, ncols); - - for i = 1:nrows - fields = strsplit(data_lines{i}, ','); - for j = 1:min(length(fields), ncols) - val = strtrim(fields{j}); - num = str2double(val); - if ~isnan(num) || strcmpi(val, 'nan') - numeric_cols(i, j) = num; - else - is_numeric(j) = false; - string_cols{i, j} = val; - end - end - end - - T = struct(); - for j = 1:ncols - if is_numeric(j) - T.(clean_names{j}) = numeric_cols(:, j); - else - T.(clean_names{j}) = string_cols(:, j); - end - end - T.Properties = struct('VariableNames', {clean_names}); - -end - - -function names = sanitize_varnames(raw_names) - % Convert raw CSV header names to valid MATLAB/Octave variable names - % Mimics MATLAB's readtable sanitization: - % 'time (us)' -> 'time_us_' - % 'axisP[0]' -> 'axisP_0_' - names = cell(size(raw_names)); - for i = 1:length(raw_names) - s = strtrim(raw_names{i}); - % Replace brackets, spaces, parentheses with underscores - s = regexprep(s, '[\[\]\(\) ]+', '_'); - % Replace any remaining non-alphanumeric/underscore chars - s = regexprep(s, '[^a-zA-Z0-9_]', '_'); - % Ensure doesn't start with a number - if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') - s = ['x' s]; - end - % Collapse multiple underscores - s = regexprep(s, '_+', '_'); - if isempty(s) - s = sprintf('Var%d', i); - end - names{i} = s; - end -end +function T = readtable(filename, varargin) +%% READTABLE - Octave-compatible CSV/text file reader +% Drop-in replacement for MATLAB's readtable() returning a struct +% with columns accessible via dot notation (T.colname). +% +% Copyright (C) 2026 Grzegorz Sterniczuk +% License: GPL v3 (see LICENSE) +% +% Usage: +% T = readtable(filename) +% T = readtable(filename, 'HeaderLines', N) +% T = readtable(filename, 'Format', fmt) +% +% Returns a struct where each field is a column vector. +% T.Properties.VariableNames contains the column names. + + % Parse optional arguments + headerlines = 0; + fmt = ''; + for i = 1:2:length(varargin) + key = varargin{i}; + val = varargin{i+1}; + if strcmpi(key, 'HeaderLines') + headerlines = val; + elseif strcmpi(key, 'Format') + fmt = val; + end + end + + fid = fopen(filename, 'r'); + if fid == -1 + error('readtable: cannot open file %s', filename); + end + + % Skip header lines + for i = 1:headerlines + fgetl(fid); + end + + % Read the first non-skipped line as column headers + header_line = fgetl(fid); + if ~ischar(header_line) + fclose(fid); + T = struct(); + T.Properties = struct('VariableNames', {{}}); + return; + end + + % Parse column names from header + col_names = strsplit(strtrim(header_line), ','); + clean_names = sanitize_varnames(col_names); + ncols = length(clean_names); + + % Read first data line to detect column types + first_data_line = fgetl(fid); + if ~ischar(first_data_line) + fclose(fid); + T = struct(); + for j = 1:ncols + T.(clean_names{j}) = []; + end + T.Properties = struct('VariableNames', {clean_names}); + return; + end + + % Detect which columns are numeric vs string + is_numeric = true(1, ncols); + first_fields = strsplit(first_data_line, ','); + for j = 1:min(length(first_fields), ncols) + val = strtrim(first_fields{j}); + num = str2double(val); + % Non-numeric value, or column header contains "flags" -> force string + if (isnan(num) && ~strcmpi(val, 'nan')) || ~isempty(regexpi(col_names{j}, 'flags')) + is_numeric(j) = false; + end + end + + % Build format string based on detected types + fmt_parts = cell(1, ncols); + for j = 1:ncols + if is_numeric(j) + fmt_parts{j} = '%f'; + else + fmt_parts{j} = '%s'; + end + end + fmt_str = strjoin(fmt_parts, ''); + + % Rewind to start of data (skip headerlines + column header line) + frewind(fid); + for i = 1:(headerlines + 1) + fgetl(fid); + end + + % Use textscan directly on file handle - orders of magnitude faster + % than reading lines into cells and joining them + C = textscan(fid, fmt_str, 'Delimiter', ',', 'EmptyValue', NaN); + fclose(fid); + + if length(C) == ncols && ~isempty(C{1}) + T = struct(); + for j = 1:ncols + T.(clean_names{j}) = C{j}; + end + T.Properties = struct('VariableNames', {clean_names}); + return; + end + + % Fallback: reopen and parse line by line (slow, for edge cases) + warning('readtable: textscan returned %d/%d columns, falling back to line-by-line', length(C), ncols); + fid = fopen(filename, 'r'); + for i = 1:(headerlines + 1) + fgetl(fid); + end + + data_lines = {}; + while ~feof(fid) + line = fgetl(fid); + if ischar(line) && ~isempty(strtrim(line)) + data_lines{end+1} = line; + end + end + fclose(fid); + + nrows = length(data_lines); + string_cols = cell(nrows, ncols); + numeric_cols = NaN(nrows, ncols); + + for i = 1:nrows + fields = strsplit(data_lines{i}, ','); + for j = 1:min(length(fields), ncols) + val = strtrim(fields{j}); + num = str2double(val); + if ~isnan(num) || strcmpi(val, 'nan') + numeric_cols(i, j) = num; + else + is_numeric(j) = false; + string_cols{i, j} = val; + end + end + end + + T = struct(); + for j = 1:ncols + if is_numeric(j) + T.(clean_names{j}) = numeric_cols(:, j); + else + T.(clean_names{j}) = string_cols(:, j); + end + end + T.Properties = struct('VariableNames', {clean_names}); + +end + + +function names = sanitize_varnames(raw_names) + % Convert raw CSV header names to valid MATLAB/Octave variable names + % Mimics MATLAB's readtable sanitization: + % 'time (us)' -> 'time_us_' + % 'axisP[0]' -> 'axisP_0_' + names = cell(size(raw_names)); + for i = 1:length(raw_names) + s = strtrim(raw_names{i}); + % Replace brackets, spaces, parentheses with underscores + s = regexprep(s, '[\[\]\(\) ]+', '_'); + % Replace any remaining non-alphanumeric/underscore chars + s = regexprep(s, '[^a-zA-Z0-9_]', '_'); + % Ensure doesn't start with a number + if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') + s = ['x' s]; + end + % Collapse multiple underscores + s = regexprep(s, '_+', '_'); + if isempty(s) + s = sprintf('Var%d', i); + end + names{i} = s; + end +end diff --git a/src/compat/smooth.m b/src/compat/smooth.m index 0e6903a..69275da 100644 --- a/src/compat/smooth.m +++ b/src/compat/smooth.m @@ -1,72 +1,72 @@ -function ys = smooth(y, span, method) -%% SMOOTH - Octave-compatible data smoothing function -% Drop-in replacement for MATLAB Curve Fitting Toolbox smooth() -% -% Usage: -% ys = smooth(y) - 5-point moving average -% ys = smooth(y, span) - span-point moving average -% ys = smooth(y, span, method) - with method: 'moving', 'lowess', 'loess' -% -% Methods: -% 'moving' - Simple moving average (default) -% 'lowess' - Locally weighted linear regression (tri-cube weights) -% 'loess' - Locally weighted quadratic regression (tri-cube weights) - - if nargin < 2, span = 5; end - if nargin < 3, method = 'moving'; end - - y = y(:); % ensure column vector - n = length(y); - - % Clamp span - span = min(round(span), n); - span = max(span, 1); - % Ensure span is odd for symmetric window - if mod(span, 2) == 0 - span = span + 1; - end - - switch lower(method) - case 'moving' - ys = smooth_moving(y, n, span); - case 'lowess' - ys = smooth_loess(y, n, span, 1); - case 'loess' - ys = smooth_loess(y, n, span, 2); - otherwise - ys = smooth_moving(y, n, span); - end - -end - - -function ys = smooth_moving(y, n, span) - % Moving average with edge handling (shrinking window at boundaries) - ys = zeros(n, 1); - half = floor(span / 2); - % Use cumulative sum for O(n) moving average - cs = [0; cumsum(y)]; - for i = 1:n - lo = max(1, i - half); - hi = min(n, i + half); - ys(i) = (cs(hi + 1) - cs(lo)) / (hi - lo + 1); - end -end - - -function ys = smooth_loess(y, n, span, degree) - % Local regression approximation using Savitzky-Golay filter - % Much faster than per-point weighted least squares (O(n) vs O(n*span)) - if span <= degree - ys = y; - return; - end - try - ys = sgolayfilt(y, degree, span); - catch - % Fallback: triple-pass moving average (approximates local regression) - ys = smooth_moving(y, n, span); - ys = smooth_moving(ys, n, span); - ys = smooth_moving(ys, n, span); - end -end +function ys = smooth(y, span, method) +%% SMOOTH - Octave-compatible data smoothing function +% Drop-in replacement for MATLAB Curve Fitting Toolbox smooth() +% +% Usage: +% ys = smooth(y) - 5-point moving average +% ys = smooth(y, span) - span-point moving average +% ys = smooth(y, span, method) - with method: 'moving', 'lowess', 'loess' +% +% Methods: +% 'moving' - Simple moving average (default) +% 'lowess' - Locally weighted linear regression (tri-cube weights) +% 'loess' - Locally weighted quadratic regression (tri-cube weights) + + if nargin < 2, span = 5; end + if nargin < 3, method = 'moving'; end + + y = y(:); % ensure column vector + n = length(y); + + % Clamp span + span = min(round(span), n); + span = max(span, 1); + % Ensure span is odd for symmetric window + if mod(span, 2) == 0 + span = span + 1; + end + + switch lower(method) + case 'moving' + ys = smooth_moving(y, n, span); + case 'lowess' + ys = smooth_loess(y, n, span, 1); + case 'loess' + ys = smooth_loess(y, n, span, 2); + otherwise + ys = smooth_moving(y, n, span); + end + +end + + +function ys = smooth_moving(y, n, span) + % Moving average with edge handling (shrinking window at boundaries) + ys = zeros(n, 1); + half = floor(span / 2); + % Use cumulative sum for O(n) moving average + cs = [0; cumsum(y)]; + for i = 1:n + lo = max(1, i - half); + hi = min(n, i + half); + ys(i) = (cs(hi + 1) - cs(lo)) / (hi - lo + 1); + end +end + + +function ys = smooth_loess(y, n, span, degree) + % Local regression approximation using Savitzky-Golay filter + % Much faster than per-point weighted least squares (O(n) vs O(n*span)) + if span <= degree + ys = y; + return; + end + try + ys = sgolayfilt(y, degree, span); + catch + % Fallback: triple-pass moving average (approximates local regression) + ys = smooth_moving(y, n, span); + ys = smooth_moving(ys, n, span); + ys = smooth_moving(ys, n, span); + end +end diff --git a/src/compat/writetable.m b/src/compat/writetable.m index 2a78481..68932fd 100644 --- a/src/compat/writetable.m +++ b/src/compat/writetable.m @@ -1,59 +1,59 @@ -function writetable(T, filename) -%% WRITETABLE - Octave-compatible table-to-file writer -% Drop-in replacement for MATLAB's writetable(). -% -% Usage: -% writetable(T, filename) -% -% Writes the struct-based table T to a text file. -% Appends .txt extension if no extension present. - - % Ensure .txt extension - if isempty(strfind(filename, '.')) - filename = [filename '.txt']; - end - - var_names = T.Properties.VariableNames; - ncols = length(var_names); - - fid = fopen(filename, 'w'); - if fid == -1 - error('writetable: cannot open file %s for writing', filename); - end - - % Write header - fprintf(fid, '%s', var_names{1}); - for j = 2:ncols - fprintf(fid, ',%s', var_names{j}); - end - fprintf(fid, '\n'); - - % Determine number of rows - first_col = T.(var_names{1}); - if iscell(first_col) - nrows = length(first_col); - else - nrows = size(first_col, 1); - end - - % Write data rows - for i = 1:nrows - for j = 1:ncols - col = T.(var_names{j}); - if j > 1 - fprintf(fid, ','); - end - if iscell(col) - fprintf(fid, '%s', col{i}); - elseif isnumeric(col) - fprintf(fid, '%g', col(i)); - else - fprintf(fid, '%s', col(i)); - end - end - fprintf(fid, '\n'); - end - - fclose(fid); - -end +function writetable(T, filename) +%% WRITETABLE - Octave-compatible table-to-file writer +% Drop-in replacement for MATLAB's writetable(). +% +% Usage: +% writetable(T, filename) +% +% Writes the struct-based table T to a text file. +% Appends .txt extension if no extension present. + + % Ensure .txt extension + if isempty(strfind(filename, '.')) + filename = [filename '.txt']; + end + + var_names = T.Properties.VariableNames; + ncols = length(var_names); + + fid = fopen(filename, 'w'); + if fid == -1 + error('writetable: cannot open file %s for writing', filename); + end + + % Write header + fprintf(fid, '%s', var_names{1}); + for j = 2:ncols + fprintf(fid, ',%s', var_names{j}); + end + fprintf(fid, '\n'); + + % Determine number of rows + first_col = T.(var_names{1}); + if iscell(first_col) + nrows = length(first_col); + else + nrows = size(first_col, 1); + end + + % Write data rows + for i = 1:nrows + for j = 1:ncols + col = T.(var_names{j}); + if j > 1 + fprintf(fid, ','); + end + if iscell(col) + fprintf(fid, '%s', col{i}); + elseif isnumeric(col) + fprintf(fid, '%g', col(i)); + else + fprintf(fid, '%s', col(i)); + end + end + fprintf(fid, '\n'); + end + + fclose(fid); + +end diff --git a/src/core/PSPercent.m b/src/core/PSPercent.m index af58d5c..91db68a 100644 --- a/src/core/PSPercent.m +++ b/src/core/PSPercent.m @@ -1,7 +1,7 @@ -function [P] = PSPercent(X) -% converts raw RCcommand to percent - rcCommandf = abs(X); - P = ((rcCommandf-0) / (500)) * 100; -end - - +function [P] = PSPercent(X) +% converts raw RCcommand to percent + rcCommandf = abs(X); + P = ((rcCommandf-0) / (500)) * 100; +end + + diff --git a/src/core/PSSpec2d.m b/src/core/PSSpec2d.m index 2bd3c74..a5ce14f 100644 --- a/src/core/PSSpec2d.m +++ b/src/core/PSSpec2d.m @@ -1,35 +1,35 @@ -function [Fs spec] = PSSpec2d(Y, F, psd) -%% [Fs spec] = PSSpec2d(Y, F, psd) -% computes standard fft on input data Y. F is sample frequency in Hz. - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if psd - % N = length(Y); - % [psdx,Fs] = periodogram(Y,[], N-1, F*1000,'psd'); % power psd - % [spec,Fs] = pspectrum(Y, F*1000, 'FrequencyResolution', 10); - - N = length(Y); - Fs = ((F*1000)*(0:(N/2))/N); - Y = Y.*hann(N)'; - Y = fft(Y); - psdx = abs(Y).^2 / (F*1000*N); % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to - psdx(2:end-1) = 2*psdx(2:end-1); - psdx = psdx(1:N/2+1); -% % scale to dB - spec = 10 * log10(psdx)'; -else - N = length(Y); - Fs = ((F*1000)*(0:(N/2))/N); - Y = Y.*hann(N)'; - Y = fft(Y); - spec = abs(Y) / N; % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to - spec = spec(1:N/2+1)'; -end - -end +function [Fs spec] = PSSpec2d(Y, F, psd) +%% [Fs spec] = PSSpec2d(Y, F, psd) +% computes standard fft on input data Y. F is sample frequency in Hz. + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if psd + % N = length(Y); + % [psdx,Fs] = periodogram(Y,[], N-1, F*1000,'psd'); % power psd + % [spec,Fs] = pspectrum(Y, F*1000, 'FrequencyResolution', 10); + + N = length(Y); + Fs = ((F*1000)*(0:(N/2))/N); + Y = Y.*hann(N)'; + Y = fft(Y); + psdx = abs(Y).^2 / (F*1000*N); % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to + psdx(2:end-1) = 2*psdx(2:end-1); + psdx = psdx(1:N/2+1); +% % scale to dB + spec = 10 * log10(psdx)'; +else + N = length(Y); + Fs = ((F*1000)*(0:(N/2))/N); + Y = Y.*hann(N)'; + Y = fft(Y); + spec = abs(Y) / N; % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to + spec = spec(1:N/2+1)'; +end + +end diff --git a/src/core/PSbfFilters.m b/src/core/PSbfFilters.m index e4f1dfe..a67fa53 100644 --- a/src/core/PSbfFilters.m +++ b/src/core/PSbfFilters.m @@ -1,55 +1,55 @@ -function [b, a] = PSbfFilters(type, fc, Fs, Q) -%% PSbfFilters - BF-compatible discrete filter coefficients -% type: 'pt1','pt2','pt3','biquad','notch' -% fc: cutoff/center freq (Hz) -% Fs: sample rate (Hz) -% Q: quality factor (notch only, default 3.5 = BF dyn_notch_q/100) - -if nargin < 4, Q = 1/sqrt(2); end -if fc <= 0 || fc >= Fs/2 - b = 1; a = 1; return; -end - -Ts = 1/Fs; - -switch lower(type) - case 'pt1' - RC = 1/(2*pi*fc); - k = Ts/(RC + Ts); - b = [k 0]; a = [1 -(1-k)]; - - case 'pt2' - fc_corr = fc * 1.553773974; - RC = 1/(2*pi*fc_corr); - k = Ts/(RC + Ts); - b1 = [k 0]; a1 = [1 -(1-k)]; - b = conv(b1, b1); a = conv(a1, a1); - - case 'pt3' - fc_corr = fc * 1.961459177; - RC = 1/(2*pi*fc_corr); - k = Ts/(RC + Ts); - b1 = [k 0]; a1 = [1 -(1-k)]; - b = conv(conv(b1, b1), b1); a = conv(conv(a1, a1), a1); - - case 'biquad' - w0 = 2*pi*fc/Fs; - alpha = sin(w0) / (2 * (1/sqrt(2))); - b0 = (1 - cos(w0)) / 2; - b1 = 1 - cos(w0); - b2 = (1 - cos(w0)) / 2; - a0 = 1 + alpha; - b = [b0 b1 b2] / a0; - a = [1 -2*cos(w0)/a0 (1-alpha)/a0]; - - case 'notch' - w0 = 2*pi*fc/Fs; - alpha = sin(w0) / (2*Q); - a0 = 1 + alpha; - b = [1 -2*cos(w0) 1] / a0; - a = [1 -2*cos(w0)/a0 (1-alpha)/a0]; - - otherwise - b = 1; a = 1; -end -end +function [b, a] = PSbfFilters(type, fc, Fs, Q) +%% PSbfFilters - BF-compatible discrete filter coefficients +% type: 'pt1','pt2','pt3','biquad','notch' +% fc: cutoff/center freq (Hz) +% Fs: sample rate (Hz) +% Q: quality factor (notch only, default 3.5 = BF dyn_notch_q/100) + +if nargin < 4, Q = 1/sqrt(2); end +if fc <= 0 || fc >= Fs/2 + b = 1; a = 1; return; +end + +Ts = 1/Fs; + +switch lower(type) + case 'pt1' + RC = 1/(2*pi*fc); + k = Ts/(RC + Ts); + b = [k 0]; a = [1 -(1-k)]; + + case 'pt2' + fc_corr = fc * 1.553773974; + RC = 1/(2*pi*fc_corr); + k = Ts/(RC + Ts); + b1 = [k 0]; a1 = [1 -(1-k)]; + b = conv(b1, b1); a = conv(a1, a1); + + case 'pt3' + fc_corr = fc * 1.961459177; + RC = 1/(2*pi*fc_corr); + k = Ts/(RC + Ts); + b1 = [k 0]; a1 = [1 -(1-k)]; + b = conv(conv(b1, b1), b1); a = conv(conv(a1, a1), a1); + + case 'biquad' + w0 = 2*pi*fc/Fs; + alpha = sin(w0) / (2 * (1/sqrt(2))); + b0 = (1 - cos(w0)) / 2; + b1 = 1 - cos(w0); + b2 = (1 - cos(w0)) / 2; + a0 = 1 + alpha; + b = [b0 b1 b2] / a0; + a = [1 -2*cos(w0)/a0 (1-alpha)/a0]; + + case 'notch' + w0 = 2*pi*fc/Fs; + alpha = sin(w0) / (2*Q); + a0 = 1 + alpha; + b = [1 -2*cos(w0) 1] / a0; + a = [1 -2*cos(w0)/a0 (1-alpha)/a0]; + + otherwise + b = 1; a = 1; +end +end diff --git a/src/core/PSestimateRPM.m b/src/core/PSestimateRPM.m index 648a574..eb8c6c4 100644 --- a/src/core/PSestimateRPM.m +++ b/src/core/PSestimateRPM.m @@ -1,75 +1,75 @@ -function [fundFreq, harmonics] = PSestimateRPM(freqAxis, ampMatrix, nHarmonics) -%% PSestimateRPM - estimate motor fundamental frequency from throttle-binned spectra -% freqAxis - 1×M frequency vector in Hz (from PSthrSpec freq(1,:)) -% ampMatrix - 100×M amplitude matrix (throttle bins × freq bins) -% nHarmonics - number of harmonics to return (default 3) -% -% Returns: -% fundFreq - 100×1 estimated fundamental freq per throttle bin (Hz), NaN where unknown -% harmonics - 100×nHarmonics matrix (Hz) - -if nargin < 3, nHarmonics = 3; end - -nBins = size(ampMatrix, 1); -fundFreq = NaN(nBins, 1); - -% motor noise band limits -fLo = 80; -fHi = 500; -fMask = freqAxis >= fLo & freqAxis <= fHi; -fIdx = find(fMask); - -if isempty(fIdx), harmonics = NaN(nBins, nHarmonics); return; end - -for t = 1:nBins - spec = ampMatrix(t, :); - if all(spec == 0), continue; end - - band = spec(fIdx); - noiseFloor = median(band); - threshold = noiseFloor + (max(band) - noiseFloor) * 0.3; - - % find local maxima above threshold - pkIdx = []; - pkVal = []; - for j = 2:length(band)-1 - if band(j) > band(j-1) && band(j) > band(j+1) && band(j) > threshold - pkIdx(end+1) = j; - pkVal(end+1) = band(j); - end - end - if isempty(pkIdx), continue; end - - % sort by amplitude, take top candidate - [~, si] = sort(pkVal, 'descend'); - f0 = freqAxis(fIdx(pkIdx(si(1)))); - - % check for harmonic confirmation: peak near 2*f0 - f2lo = f0 * 1.8; - f2hi = f0 * 2.2; - f2mask = freqAxis >= f2lo & freqAxis <= f2hi; - if any(f2mask) && max(spec(f2mask)) > noiseFloor * 1.5 - fundFreq(t) = f0; - else - % might be a harmonic itself — check if f0/2 has a peak - fhlo = f0 * 0.4; - fhhi = f0 * 0.6; - fhmask = freqAxis >= fhlo & freqAxis <= fhhi & fMask; - if any(fhmask) && max(spec(fhmask)) > noiseFloor * 1.3 - fundFreq(t) = freqAxis(fIdx(pkIdx(si(1)))) / 2; - else - fundFreq(t) = f0; - end - end -end - -% smooth across throttle bins to reduce jitter -validMask = ~isnan(fundFreq); -if sum(validMask) > 5 - fundFreq(validMask) = smooth(fundFreq(validMask), 5, 'moving'); -end - -% generate harmonics -harmonics = fundFreq .* (1:nHarmonics); - -end +function [fundFreq, harmonics] = PSestimateRPM(freqAxis, ampMatrix, nHarmonics) +%% PSestimateRPM - estimate motor fundamental frequency from throttle-binned spectra +% freqAxis - 1×M frequency vector in Hz (from PSthrSpec freq(1,:)) +% ampMatrix - 100×M amplitude matrix (throttle bins × freq bins) +% nHarmonics - number of harmonics to return (default 3) +% +% Returns: +% fundFreq - 100×1 estimated fundamental freq per throttle bin (Hz), NaN where unknown +% harmonics - 100×nHarmonics matrix (Hz) + +if nargin < 3, nHarmonics = 3; end + +nBins = size(ampMatrix, 1); +fundFreq = NaN(nBins, 1); + +% motor noise band limits +fLo = 80; +fHi = 500; +fMask = freqAxis >= fLo & freqAxis <= fHi; +fIdx = find(fMask); + +if isempty(fIdx), harmonics = NaN(nBins, nHarmonics); return; end + +for t = 1:nBins + spec = ampMatrix(t, :); + if all(spec == 0), continue; end + + band = spec(fIdx); + noiseFloor = median(band); + threshold = noiseFloor + (max(band) - noiseFloor) * 0.3; + + % find local maxima above threshold + pkIdx = []; + pkVal = []; + for j = 2:length(band)-1 + if band(j) > band(j-1) && band(j) > band(j+1) && band(j) > threshold + pkIdx(end+1) = j; + pkVal(end+1) = band(j); + end + end + if isempty(pkIdx), continue; end + + % sort by amplitude, take top candidate + [~, si] = sort(pkVal, 'descend'); + f0 = freqAxis(fIdx(pkIdx(si(1)))); + + % check for harmonic confirmation: peak near 2*f0 + f2lo = f0 * 1.8; + f2hi = f0 * 2.2; + f2mask = freqAxis >= f2lo & freqAxis <= f2hi; + if any(f2mask) && max(spec(f2mask)) > noiseFloor * 1.5 + fundFreq(t) = f0; + else + % might be a harmonic itself — check if f0/2 has a peak + fhlo = f0 * 0.4; + fhhi = f0 * 0.6; + fhmask = freqAxis >= fhlo & freqAxis <= fhhi & fMask; + if any(fhmask) && max(spec(fhmask)) > noiseFloor * 1.3 + fundFreq(t) = freqAxis(fIdx(pkIdx(si(1)))) / 2; + else + fundFreq(t) = f0; + end + end +end + +% smooth across throttle bins to reduce jitter +validMask = ~isnan(fundFreq); +if sum(validMask) > 5 + fundFreq(validMask) = smooth(fundFreq(validMask), 5, 'moving'); +end + +% generate harmonics +harmonics = fundFreq .* (1:nHarmonics); + +end diff --git a/src/core/PSimport.m b/src/core/PSimport.m index 8dfd550..d707661 100644 --- a/src/core/PSimport.m +++ b/src/core/PSimport.m @@ -1,69 +1,69 @@ -function [DAT newFileName] = PSimport(csvFname, BBLFileName) -%% [DAT newFileName] = PSimport(filename, BBLFileName) -% Imports log data in multiple formats (fw=selected firmware/logfile type). Default is .csv, but if using .bbl or .bfl files, - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -newFileName=csvFname; -T=readtable(csvFname); - -delete(csvFname);% we r done with this-dont wanna leave junk on main directory - -% get header info from .bbl/.bfl file directly -a=strfind(BBLFileName,'.'); -fid = fopen(BBLFileName, 'r'); -c = textscan(fid, '%s', 'Delimiter','\n','CollectOutput',1); %c2 = native2unicode(c) -fclose(fid); - -logStartPoints=[]; -logEndPoints=[]; - -a=strfind(c{1},'Firmware version'); -if isempty(find(cellfun(@(a)~isempty(a)&&a>0,a))) - a=strfind(c{1},'Firmware revision'); - logStartPoints=find(cellfun(@(a)~isempty(a)&&a>0,a)); -else - a=strfind(c{1},'Firmware version'); - logStartPoints=find(cellfun(@(a)~isempty(a)&&a>0,a)); -end - -endStr={'throttle_boost_cutoff'; 'debug_mode' ; 'motor_pwm_rate' ; 'use_unsynced_pwm' ; 'gyro_32khz_hardware_lpf' ; 'gyro_hardware_lpf' ; 'yaw_deadband' ; 'deadband' ; 'pidsum_limit_yaw' ; 'pidsum_limit' ; 'energyCumulative (mAh)'; 'looptime'};% strings not in all revisions, try a few -m=1; a=strfind(c{1},endStr{m}); -while isempty(find(cellfun(@(a)~isempty(a)&&a>0,a))) - a=strfind(c{1},endStr{m}); - m=m+1; -end -logEndPoints = find(cellfun(@(a)~isempty(a)&&a>0,a)); - -relevantLogNum=str2num(csvFname(end-5:end-4)); -s=c{1}(logStartPoints(relevantLogNum):logEndPoints(relevantLogNum)); -n=1; -for m=1:size(s,1) - if size(strsplit(char(s(m)),':'),2)==2 - SetupInfo(n,:)=(strsplit(char(s(m)),':')); - a=char(SetupInfo(n,1)); - if strcmp(a(1:2),'H ') - SetupInfo(n,1)={a(3:end)}; - end - n=n+1; - end -end - - -% -DAT.T=T; -DAT.mainFname=BBLFileName; -DAT.csvFname=csvFname; -DAT.SetupInfo=SetupInfo; -try -DAT.blackbox_decode_results=result; -catch -end - -end - +function [DAT newFileName] = PSimport(csvFname, BBLFileName) +%% [DAT newFileName] = PSimport(filename, BBLFileName) +% Imports log data in multiple formats (fw=selected firmware/logfile type). Default is .csv, but if using .bbl or .bfl files, + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +newFileName=csvFname; +T=readtable(csvFname); + +delete(csvFname);% we r done with this-dont wanna leave junk on main directory + +% get header info from .bbl/.bfl file directly +a=strfind(BBLFileName,'.'); +fid = fopen(BBLFileName, 'r'); +c = textscan(fid, '%s', 'Delimiter','\n','CollectOutput',1); %c2 = native2unicode(c) +fclose(fid); + +logStartPoints=[]; +logEndPoints=[]; + +a=strfind(c{1},'Firmware version'); +if isempty(find(cellfun(@(a)~isempty(a)&&a>0,a))) + a=strfind(c{1},'Firmware revision'); + logStartPoints=find(cellfun(@(a)~isempty(a)&&a>0,a)); +else + a=strfind(c{1},'Firmware version'); + logStartPoints=find(cellfun(@(a)~isempty(a)&&a>0,a)); +end + +endStr={'throttle_boost_cutoff'; 'debug_mode' ; 'motor_pwm_rate' ; 'use_unsynced_pwm' ; 'gyro_32khz_hardware_lpf' ; 'gyro_hardware_lpf' ; 'yaw_deadband' ; 'deadband' ; 'pidsum_limit_yaw' ; 'pidsum_limit' ; 'energyCumulative (mAh)'; 'looptime'};% strings not in all revisions, try a few +m=1; a=strfind(c{1},endStr{m}); +while isempty(find(cellfun(@(a)~isempty(a)&&a>0,a))) + a=strfind(c{1},endStr{m}); + m=m+1; +end +logEndPoints = find(cellfun(@(a)~isempty(a)&&a>0,a)); + +relevantLogNum=str2num(csvFname(end-5:end-4)); +s=c{1}(logStartPoints(relevantLogNum):logEndPoints(relevantLogNum)); +n=1; +for m=1:size(s,1) + if size(strsplit(char(s(m)),':'),2)==2 + SetupInfo(n,:)=(strsplit(char(s(m)),':')); + a=char(SetupInfo(n,1)); + if strcmp(a(1:2),'H ') + SetupInfo(n,1)={a(3:end)}; + end + n=n+1; + end +end + + +% +DAT.T=T; +DAT.mainFname=BBLFileName; +DAT.csvFname=csvFname; +DAT.SetupInfo=SetupInfo; +try +DAT.blackbox_decode_results=result; +catch +end + +end + diff --git a/src/core/PSparseBFversion.m b/src/core/PSparseBFversion.m index 5a73ce2..5ee4042 100644 --- a/src/core/PSparseBFversion.m +++ b/src/core/PSparseBFversion.m @@ -1,57 +1,57 @@ -function [fwType, fwMajor, fwMinor] = PSparseBFversion(setupInfo) -%% PSparseBFversion - parse firmware type and version from SetupInfo cell array -% [fwType, fwMajor, fwMinor] = PSparseBFversion(setupInfo) -% setupInfo: Nx2 cell array (column 1 = param name, column 2 = value) -% Returns: fwType string ('Betaflight','INAV','Emuflight',...), fwMajor, fwMinor numbers - -fwType = 'Unknown'; -fwMajor = 0; -fwMinor = 0; - -try - % Find 'Firmware version' or 'Firmware revision' row - idx = find(strcmp(setupInfo(:,1), 'Firmware version')); - if isempty(idx) - idx = find(strcmp(setupInfo(:,1), 'Firmware revision')); - end - if isempty(idx) - return; - end - - verStr = strtrim(char(setupInfo(idx(1), 2))); - % Example: " Betaflight / STM32F405 4.5.3 Dec 14 2024 / 11:27:01" - % Example: " Betaflight / STM32F405 2025.12.2 Jan 5 2026 / 09:15:00" - % Example: " INAV / STM32F405 7.1.0 ..." - % Example: " Emuflight / STM32F405 0.4.1 ..." - % Example: "QuickSilver" (from PSquicJson2csv synthetic header) - - % Extract firmware type (first word before ' /' or end of string) - parts = strsplit(verStr, '/'); - fwType = strtrim(parts{1}); - - % Extract version: find pattern X.Y.Z or X.Y anywhere in string - tok = regexp(verStr, '(\d+)\.(\d+)\.(\d+)', 'tokens'); - if ~isempty(tok) - % Could have multiple matches (e.g. board name with numbers) - use last one - % Actually first match after '/' is the version - for t = 1:numel(tok) - maj = str2double(tok{t}{1}); - mnr = str2double(tok{t}{2}); - % Heuristic: version is the token with major >= 2 (BF 2025.x or 3.x/4.x) - % or after the board name - if maj >= 2 - fwMajor = maj; - fwMinor = mnr; - break; - end - end - % If no match with major >= 2, use first token - if fwMajor == 0 && ~isempty(tok) - fwMajor = str2double(tok{1}{1}); - fwMinor = str2double(tok{1}{2}); - end - end -catch -end - -end +function [fwType, fwMajor, fwMinor] = PSparseBFversion(setupInfo) +%% PSparseBFversion - parse firmware type and version from SetupInfo cell array +% [fwType, fwMajor, fwMinor] = PSparseBFversion(setupInfo) +% setupInfo: Nx2 cell array (column 1 = param name, column 2 = value) +% Returns: fwType string ('Betaflight','INAV','Emuflight',...), fwMajor, fwMinor numbers + +fwType = 'Unknown'; +fwMajor = 0; +fwMinor = 0; + +try + % Find 'Firmware version' or 'Firmware revision' row + idx = find(strcmp(setupInfo(:,1), 'Firmware version')); + if isempty(idx) + idx = find(strcmp(setupInfo(:,1), 'Firmware revision')); + end + if isempty(idx) + return; + end + + verStr = strtrim(char(setupInfo(idx(1), 2))); + % Example: " Betaflight / STM32F405 4.5.3 Dec 14 2024 / 11:27:01" + % Example: " Betaflight / STM32F405 2025.12.2 Jan 5 2026 / 09:15:00" + % Example: " INAV / STM32F405 7.1.0 ..." + % Example: " Emuflight / STM32F405 0.4.1 ..." + % Example: "QuickSilver" (from PSquicJson2csv synthetic header) + + % Extract firmware type (first word before ' /' or end of string) + parts = strsplit(verStr, '/'); + fwType = strtrim(parts{1}); + + % Extract version: find pattern X.Y.Z or X.Y anywhere in string + tok = regexp(verStr, '(\d+)\.(\d+)\.(\d+)', 'tokens'); + if ~isempty(tok) + % Could have multiple matches (e.g. board name with numbers) - use last one + % Actually first match after '/' is the version + for t = 1:numel(tok) + maj = str2double(tok{t}{1}); + mnr = str2double(tok{t}{2}); + % Heuristic: version is the token with major >= 2 (BF 2025.x or 3.x/4.x) + % or after the board name + if maj >= 2 + fwMajor = maj; + fwMinor = mnr; + break; + end + end + % If no match with major >= 2, use first token + if fwMajor == 0 && ~isempty(tok) + fwMajor = str2double(tok{1}{1}); + fwMinor = str2double(tok{1}{2}); + end + end +catch +end + +end diff --git a/src/core/PSprocess.m b/src/core/PSprocess.m index 5727d83..092c5c7 100644 --- a/src/core/PSprocess.m +++ b/src/core/PSprocess.m @@ -1,122 +1,122 @@ -%% PSprocess - script that extracts subset of total data based on highlighted epoch in main fig - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -try - -if ~isempty(filenameA) || ~isempty(filenameB) - -downsampleMultiplier=5;% 5th of the resolution for faster plotting, display only - -set(PSfig, 'pointer', 'watch') - if ~isempty(filenameA) - if isempty(epoch1_A) || isempty(epoch2_A) - epoch1_A=round(tta(1)/us2sec)+2; - epoch2_A=round(tta(end)/us2sec)-2; - guiHandles.Epoch1_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_A_Input],... - 'callback','@textinput_call; epoch1_A=str2num(get(guiHandles.Epoch1_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); - guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... - 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); - end - if (epoch2_A>round(tta(end)/us2sec)) - epoch2_A=round(tta(end)/us2sec); - guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... - 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); - end - x=[epoch1_A*us2sec epoch2_A*us2sec]; - x2=tta>tta(find(tta>x(1),1)) & ttax(2),1)); - Time_A=tta(x2,1)/us2sec; - Time_A=Time_A-Time_A(1); - DATtmpA.GyroFilt=DATmainA.GyroFilt(:,x2); - DATtmpA.debug=DATmainA.debug(:,x2); - DATtmpA.RCcommand=DATmainA.RCcommand(:,x2); - DATtmpA.Pterm=DATmainA.Pterm(:,x2); - DATtmpA.Iterm=DATmainA.Iterm(:,x2); - DATtmpA.DtermRaw=DATmainA.DtermRaw(:,x2); - DATtmpA.DtermFilt=DATmainA.DtermFilt(:,x2); - DATtmpA.Fterm=DATmainA.Fterm(:,x2); - DATtmpA.PIDsum=DATmainA.PIDsum(:,x2); - DATtmpA.RCRate=DATmainA.RCRate(:,x2); - DATtmpA.PIDerr=DATmainA.PIDerr(:,x2); - DATtmpA.Motor12=DATmainA.Motor(1:2,x2); - DATtmpA.Motor34=DATmainA.Motor(3:4,x2); - DATtmpA.debug12=DATmainA.debug(1:2,x2); - DATtmpA.debug34=DATmainA.debug(3:4,x2); - - dnsampleFactor=A_lograte*downsampleMultiplier;% 5 times less resolution for faster plotting, display only - DATdnsmplA.tta=downsample(((tta-tta(1))/us2sec), dnsampleFactor)'; - DATdnsmplA.GyroFilt=downsample(DATmainA.GyroFilt', dnsampleFactor)'; - DATdnsmplA.debug=downsample(DATmainA.debug', dnsampleFactor)'; - DATdnsmplA.RCcommand=downsample(DATmainA.RCcommand', dnsampleFactor)'; - DATdnsmplA.Pterm=downsample(DATmainA.Pterm', dnsampleFactor)'; - DATdnsmplA.Iterm=downsample(DATmainA.Iterm', dnsampleFactor)'; - DATdnsmplA.DtermRaw=downsample(DATmainA.DtermRaw', dnsampleFactor)'; - DATdnsmplA.DtermFilt=downsample(DATmainA.DtermFilt', dnsampleFactor)'; - DATdnsmplA.Fterm=downsample(DATmainA.Fterm', dnsampleFactor)'; - DATdnsmplA.RCRate=downsample(DATmainA.RCRate', dnsampleFactor)'; - DATdnsmplA.PIDsum=downsample(DATmainA.PIDsum', dnsampleFactor)'; - DATdnsmplA.PIDerr=downsample(DATmainA.PIDerr', dnsampleFactor)'; - DATdnsmplA.Motor=downsample(DATmainA.Motor', dnsampleFactor)'; - end - - if ~isempty(filenameB) - if isempty(epoch1_B) || isempty(epoch2_B) - epoch1_B=round(ttb(1)/us2sec)+2; - epoch2_B=round(ttb(end)/us2sec)-2; - guiHandles.Epoch1_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_B_Input],... - 'callback','@textinput_call; epoch1_B=str2num(get(guiHandles.Epoch1_B_Input, ''String''));PSprocess;PSplotLogViewer; '); - guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... - 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String''));PSprocess;PSplotLogViewer; '); - end - if (epoch2_B>round(ttb(end)/us2sec)) - epoch2_B=round(ttb(end)/us2sec); - guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... - 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String'')); PSprocess;PSplotLogViewer;'); - end - x=[epoch1_B*us2sec epoch2_B*us2sec]; - x2=ttb>ttb(find(ttb>x(1),1)) & ttbx(2),1)); - Time_B=ttb(x2,1)/us2sec; - Time_B=Time_B-Time_B(1); - DATtmpB.GyroFilt=DATmainB.GyroFilt(:,x2); - DATtmpB.debug=DATmainB.debug(:,x2); - DATtmpB.RCcommand=DATmainB.RCcommand(:,x2); - DATtmpB.Pterm=DATmainB.Pterm(:,x2); - DATtmpB.Iterm=DATmainB.Iterm(:,x2); - DATtmpB.DtermRaw=DATmainB.DtermRaw(:,x2); - DATtmpB.DtermFilt=DATmainB.DtermFilt(:,x2); - DATtmpB.Fterm=DATmainB.Fterm(:,x2); - DATtmpB.PIDsum=DATmainB.PIDsum(:,x2); - DATtmpB.RCRate=DATmainB.RCRate(:,x2); - DATtmpB.PIDerr=DATmainB.PIDerr(:,x2); - DATtmpB.Motor12=DATmainB.Motor(1:2,x2); - DATtmpB.Motor34=DATmainB.Motor(3:4,x2); - DATtmpB.debug12=DATmainB.debug(1:2,x2); - DATtmpB.debug34=DATmainB.debug(3:4,x2); - - - dnsampleFactor=B_lograte*downsampleMultiplier;% 5 times less resolution for faster plotting, display only - DATdnsmplB.ttb=downsample(((ttb-ttb(1))/us2sec), dnsampleFactor)'; - DATdnsmplB.GyroFilt=downsample(DATmainB.GyroFilt', dnsampleFactor)'; - DATdnsmplB.debug=downsample(DATmainB.debug', dnsampleFactor)'; - DATdnsmplB.RCcommand=downsample(DATmainB.RCcommand', dnsampleFactor)'; - DATdnsmplB.Pterm=downsample(DATmainB.Pterm', dnsampleFactor)'; - DATdnsmplB.Iterm=downsample(DATmainB.Iterm', dnsampleFactor)'; - DATdnsmplB.DtermRaw=downsample(DATmainB.DtermRaw', dnsampleFactor)'; - DATdnsmplB.DtermFilt=downsample(DATmainB.DtermFilt', dnsampleFactor)'; - DATdnsmplB.Fterm=downsample(DATmainB.Fterm', dnsampleFactor)'; - DATdnsmplB.RCRate=downsample(DATmainB.RCRate', dnsampleFactor)'; - DATdnsmplB.PIDsum=downsample(DATmainB.PIDsum', dnsampleFactor)'; - DATdnsmplB.PIDerr=downsample(DATmainB.PIDerr', dnsampleFactor)'; - DATdnsmplB.Motor=downsample(DATmainB.Motor', dnsampleFactor)'; - end -set(PSfig, 'pointer', 'arrow') -end - -catch ME - errmsg.PSprocess=PSerrorMessages('PSprocess', ME); -end +%% PSprocess - script that extracts subset of total data based on highlighted epoch in main fig + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +try + +if ~isempty(filenameA) || ~isempty(filenameB) + +downsampleMultiplier=5;% 5th of the resolution for faster plotting, display only + +set(PSfig, 'pointer', 'watch') + if ~isempty(filenameA) + if isempty(epoch1_A) || isempty(epoch2_A) + epoch1_A=round(tta(1)/us2sec)+2; + epoch2_A=round(tta(end)/us2sec)-2; + guiHandles.Epoch1_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_A_Input],... + 'callback','@textinput_call; epoch1_A=str2num(get(guiHandles.Epoch1_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... + 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + end + if (epoch2_A>round(tta(end)/us2sec)) + epoch2_A=round(tta(end)/us2sec); + guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... + 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + end + x=[epoch1_A*us2sec epoch2_A*us2sec]; + x2=tta>tta(find(tta>x(1),1)) & ttax(2),1)); + Time_A=tta(x2,1)/us2sec; + Time_A=Time_A-Time_A(1); + DATtmpA.GyroFilt=DATmainA.GyroFilt(:,x2); + DATtmpA.debug=DATmainA.debug(:,x2); + DATtmpA.RCcommand=DATmainA.RCcommand(:,x2); + DATtmpA.Pterm=DATmainA.Pterm(:,x2); + DATtmpA.Iterm=DATmainA.Iterm(:,x2); + DATtmpA.DtermRaw=DATmainA.DtermRaw(:,x2); + DATtmpA.DtermFilt=DATmainA.DtermFilt(:,x2); + DATtmpA.Fterm=DATmainA.Fterm(:,x2); + DATtmpA.PIDsum=DATmainA.PIDsum(:,x2); + DATtmpA.RCRate=DATmainA.RCRate(:,x2); + DATtmpA.PIDerr=DATmainA.PIDerr(:,x2); + DATtmpA.Motor12=DATmainA.Motor(1:2,x2); + DATtmpA.Motor34=DATmainA.Motor(3:4,x2); + DATtmpA.debug12=DATmainA.debug(1:2,x2); + DATtmpA.debug34=DATmainA.debug(3:4,x2); + + dnsampleFactor=A_lograte*downsampleMultiplier;% 5 times less resolution for faster plotting, display only + DATdnsmplA.tta=downsample(((tta-tta(1))/us2sec), dnsampleFactor)'; + DATdnsmplA.GyroFilt=downsample(DATmainA.GyroFilt', dnsampleFactor)'; + DATdnsmplA.debug=downsample(DATmainA.debug', dnsampleFactor)'; + DATdnsmplA.RCcommand=downsample(DATmainA.RCcommand', dnsampleFactor)'; + DATdnsmplA.Pterm=downsample(DATmainA.Pterm', dnsampleFactor)'; + DATdnsmplA.Iterm=downsample(DATmainA.Iterm', dnsampleFactor)'; + DATdnsmplA.DtermRaw=downsample(DATmainA.DtermRaw', dnsampleFactor)'; + DATdnsmplA.DtermFilt=downsample(DATmainA.DtermFilt', dnsampleFactor)'; + DATdnsmplA.Fterm=downsample(DATmainA.Fterm', dnsampleFactor)'; + DATdnsmplA.RCRate=downsample(DATmainA.RCRate', dnsampleFactor)'; + DATdnsmplA.PIDsum=downsample(DATmainA.PIDsum', dnsampleFactor)'; + DATdnsmplA.PIDerr=downsample(DATmainA.PIDerr', dnsampleFactor)'; + DATdnsmplA.Motor=downsample(DATmainA.Motor', dnsampleFactor)'; + end + + if ~isempty(filenameB) + if isempty(epoch1_B) || isempty(epoch2_B) + epoch1_B=round(ttb(1)/us2sec)+2; + epoch2_B=round(ttb(end)/us2sec)-2; + guiHandles.Epoch1_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_B_Input],... + 'callback','@textinput_call; epoch1_B=str2num(get(guiHandles.Epoch1_B_Input, ''String''));PSprocess;PSplotLogViewer; '); + guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... + 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String''));PSprocess;PSplotLogViewer; '); + end + if (epoch2_B>round(ttb(end)/us2sec)) + epoch2_B=round(ttb(end)/us2sec); + guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... + 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String'')); PSprocess;PSplotLogViewer;'); + end + x=[epoch1_B*us2sec epoch2_B*us2sec]; + x2=ttb>ttb(find(ttb>x(1),1)) & ttbx(2),1)); + Time_B=ttb(x2,1)/us2sec; + Time_B=Time_B-Time_B(1); + DATtmpB.GyroFilt=DATmainB.GyroFilt(:,x2); + DATtmpB.debug=DATmainB.debug(:,x2); + DATtmpB.RCcommand=DATmainB.RCcommand(:,x2); + DATtmpB.Pterm=DATmainB.Pterm(:,x2); + DATtmpB.Iterm=DATmainB.Iterm(:,x2); + DATtmpB.DtermRaw=DATmainB.DtermRaw(:,x2); + DATtmpB.DtermFilt=DATmainB.DtermFilt(:,x2); + DATtmpB.Fterm=DATmainB.Fterm(:,x2); + DATtmpB.PIDsum=DATmainB.PIDsum(:,x2); + DATtmpB.RCRate=DATmainB.RCRate(:,x2); + DATtmpB.PIDerr=DATmainB.PIDerr(:,x2); + DATtmpB.Motor12=DATmainB.Motor(1:2,x2); + DATtmpB.Motor34=DATmainB.Motor(3:4,x2); + DATtmpB.debug12=DATmainB.debug(1:2,x2); + DATtmpB.debug34=DATmainB.debug(3:4,x2); + + + dnsampleFactor=B_lograte*downsampleMultiplier;% 5 times less resolution for faster plotting, display only + DATdnsmplB.ttb=downsample(((ttb-ttb(1))/us2sec), dnsampleFactor)'; + DATdnsmplB.GyroFilt=downsample(DATmainB.GyroFilt', dnsampleFactor)'; + DATdnsmplB.debug=downsample(DATmainB.debug', dnsampleFactor)'; + DATdnsmplB.RCcommand=downsample(DATmainB.RCcommand', dnsampleFactor)'; + DATdnsmplB.Pterm=downsample(DATmainB.Pterm', dnsampleFactor)'; + DATdnsmplB.Iterm=downsample(DATmainB.Iterm', dnsampleFactor)'; + DATdnsmplB.DtermRaw=downsample(DATmainB.DtermRaw', dnsampleFactor)'; + DATdnsmplB.DtermFilt=downsample(DATmainB.DtermFilt', dnsampleFactor)'; + DATdnsmplB.Fterm=downsample(DATmainB.Fterm', dnsampleFactor)'; + DATdnsmplB.RCRate=downsample(DATmainB.RCRate', dnsampleFactor)'; + DATdnsmplB.PIDsum=downsample(DATmainB.PIDsum', dnsampleFactor)'; + DATdnsmplB.PIDerr=downsample(DATmainB.PIDerr', dnsampleFactor)'; + DATdnsmplB.Motor=downsample(DATmainB.Motor', dnsampleFactor)'; + end +set(PSfig, 'pointer', 'arrow') +end + +catch ME + errmsg.PSprocess=PSerrorMessages('PSprocess', ME); +end diff --git a/src/core/PSquicJson2csv.m b/src/core/PSquicJson2csv.m index 3649445..0ba3901 100644 --- a/src/core/PSquicJson2csv.m +++ b/src/core/PSquicJson2csv.m @@ -1,161 +1,161 @@ -function [headerFile, csvFile] = PSquicJson2csv(jsonFile) -%% PSquicJson2csv - Convert QuickSilver JSON blackbox export to CSV -% -% Reads JSON exported by BossHobby Configurator ("QUIC download") and -% produces CSV + synthetic header compatible with PSimport/PSload pipeline. -% -% Unit conversions applied: -% gyro/setpoint RPY: rad/s -> deg/s (multiply by 180/pi) -% yaw: negated (QS convention opposite to BF) -% motor: 0-1 range -> 0-2000 (BF scale, PSload does /2000*100) -% PID terms: divided by 1000 (stored as int16*1000) -% throttle setpoint: 0-1 -> 0-1000 (BF scale) -% -% [headerFile, csvFile] = PSquicJson2csv(jsonFile) - - RAD2DEG = 180 / pi; - - % Read and parse JSON - jsonText = fileread(jsonFile); - data = jsondecode(jsonText); - - % Extract metadata - if isfield(data, 'looptime') - looptime_us = data.looptime; - else - looptime_us = 125; % default 8kHz - end - if isfield(data, 'blackbox_rate') - bb_rate = data.blackbox_rate; - else - bb_rate = 1; - end - - % Get field list - if iscell(data.fields) - fields = data.fields; - else - fields = cellstr(data.fields); - end - - % Get entries - entries = data.entries; - if ~iscell(entries) - % If jsondecode made it a matrix (unlikely for mixed types), convert - entries = num2cell(entries, 2); - end - nEntries = numel(entries); - - % Initialize output arrays - loopIter = zeros(nEntries, 1); - time_us = zeros(nEntries, 1); - gyro = zeros(nEntries, 3); % RPY filtered - gyro_raw = zeros(nEntries, 3); % RPY raw - setpt = zeros(nEntries, 4); % RPY + throttle - axisP = zeros(nEntries, 3); - axisI = zeros(nEntries, 3); - axisD = zeros(nEntries, 3); - motors = zeros(nEntries, 4); - rx_cmd = zeros(nEntries, 4); - dbg = zeros(nEntries, 4); - - % Parse entries - each entry is ordered by fields list - for i = 1:nEntries - entry = entries{i}; - if ~iscell(entry) - entry = num2cell(entry); - end - - col = 0; - for f = 1:numel(fields) - col = col + 1; - if col > numel(entry), break; end - val = entry{col}; - if iscell(val), val = cell2mat(val); end - - switch fields{f} - case 'loop' - loopIter(i) = val; - case 'time' - time_us(i) = val; - case 'pid_p_term' - axisP(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; - case 'pid_i_term' - axisI(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; - case 'pid_d_term' - axisD(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; - case 'rx' - rx_cmd(i, 1:min(4,numel(val))) = val(1:min(4,numel(val))) / 1000; - case 'setpoint' - n = min(4, numel(val)); - sp = val(1:n) / 1000; - % RPY: rad/s -> deg/s, negate yaw - if n >= 1, sp(1) = sp(1) * RAD2DEG; end - if n >= 2, sp(2) = sp(2) * RAD2DEG; end - if n >= 3, sp(3) = -sp(3) * RAD2DEG; end % negate yaw - % Throttle: 0-1 -> 0-1000 - if n >= 4, sp(4) = sp(4) * 1000; end - setpt(i, 1:n) = sp; - case 'gyro_filter' - n = min(3, numel(val)); - g = val(1:n) / 1000 * RAD2DEG; - if n >= 3, g(3) = -g(3); end % negate yaw - gyro(i, 1:n) = g; - case 'gyro_raw' - n = min(3, numel(val)); - g = val(1:n) / 1000 * RAD2DEG; - if n >= 3, g(3) = -g(3); end % negate yaw - gyro_raw(i, 1:n) = g; - case 'motor' - n = min(4, numel(val)); - % 0-1 range * 1000 -> 0-2000 (BF scale) - motors(i, 1:n) = val(1:n) / 1000 * 2000; - case 'accel_raw' - % not used by PIDscope, skip - case 'accel_filter' - % not used by PIDscope, skip - case 'cpu_load' - % not used by PIDscope, skip - case 'debug' - n = min(4, numel(val)); - dbg(i, 1:n) = val(1:n); - end - end - end - - % If no gyro_filter but have gyro_raw, use raw as filtered - if all(gyro(:) == 0) && any(gyro_raw(:) ~= 0) - gyro = gyro_raw; - end - - % Build CSV output - [fdir, fname, ~] = fileparts(jsonFile); - csvFile = fullfile(fdir, [fname '.01.csv']); - - % Column names matching blackbox_decode output - header = 'loopIteration,time (us),axisP[0],axisP[1],axisP[2],axisI[0],axisI[1],axisI[2],axisD[0],axisD[1],axisD[2],gyroADC[0],gyroADC[1],gyroADC[2],rcCommand[0],rcCommand[1],rcCommand[2],rcCommand[3],setpoint[0],setpoint[1],setpoint[2],setpoint[3],motor[0],motor[1],motor[2],motor[3],debug[0],debug[1],debug[2],debug[3]'; - - % Build data matrix - M = [loopIter, time_us, axisP, axisI, axisD, gyro, rx_cmd, setpt, motors, dbg]; - - % Write CSV - fid = fopen(csvFile, 'w'); - fprintf(fid, '%s\n', header); - fclose(fid); - dlmwrite(csvFile, M, '-append', 'delimiter', ',', 'precision', '%.6g'); - - % Write synthetic header file (mimics BBL header for PSimport) - headerFile = fullfile(fdir, [fname '.quic_header.txt']); - fid = fopen(headerFile, 'w'); - fprintf(fid, 'H Firmware version:QuickSilver\n'); - fprintf(fid, 'H Firmware revision:QUICKSILVER\n'); - fprintf(fid, 'H looptime:%d\n', round(looptime_us)); - fprintf(fid, 'H rollPID:0, 0, 0\n'); - fprintf(fid, 'H pitchPID:0, 0, 0\n'); - fprintf(fid, 'H yawPID:0, 0, 0\n'); - fprintf(fid, 'H d_min:0, 0, 0\n'); - fprintf(fid, 'H feedforward_weight:0, 0, 0\n'); - fprintf(fid, 'H debug_mode:0\n'); - fclose(fid); - -end +function [headerFile, csvFile] = PSquicJson2csv(jsonFile) +%% PSquicJson2csv - Convert QuickSilver JSON blackbox export to CSV +% +% Reads JSON exported by BossHobby Configurator ("QUIC download") and +% produces CSV + synthetic header compatible with PSimport/PSload pipeline. +% +% Unit conversions applied: +% gyro/setpoint RPY: rad/s -> deg/s (multiply by 180/pi) +% yaw: negated (QS convention opposite to BF) +% motor: 0-1 range -> 0-2000 (BF scale, PSload does /2000*100) +% PID terms: divided by 1000 (stored as int16*1000) +% throttle setpoint: 0-1 -> 0-1000 (BF scale) +% +% [headerFile, csvFile] = PSquicJson2csv(jsonFile) + + RAD2DEG = 180 / pi; + + % Read and parse JSON + jsonText = fileread(jsonFile); + data = jsondecode(jsonText); + + % Extract metadata + if isfield(data, 'looptime') + looptime_us = data.looptime; + else + looptime_us = 125; % default 8kHz + end + if isfield(data, 'blackbox_rate') + bb_rate = data.blackbox_rate; + else + bb_rate = 1; + end + + % Get field list + if iscell(data.fields) + fields = data.fields; + else + fields = cellstr(data.fields); + end + + % Get entries + entries = data.entries; + if ~iscell(entries) + % If jsondecode made it a matrix (unlikely for mixed types), convert + entries = num2cell(entries, 2); + end + nEntries = numel(entries); + + % Initialize output arrays + loopIter = zeros(nEntries, 1); + time_us = zeros(nEntries, 1); + gyro = zeros(nEntries, 3); % RPY filtered + gyro_raw = zeros(nEntries, 3); % RPY raw + setpt = zeros(nEntries, 4); % RPY + throttle + axisP = zeros(nEntries, 3); + axisI = zeros(nEntries, 3); + axisD = zeros(nEntries, 3); + motors = zeros(nEntries, 4); + rx_cmd = zeros(nEntries, 4); + dbg = zeros(nEntries, 4); + + % Parse entries - each entry is ordered by fields list + for i = 1:nEntries + entry = entries{i}; + if ~iscell(entry) + entry = num2cell(entry); + end + + col = 0; + for f = 1:numel(fields) + col = col + 1; + if col > numel(entry), break; end + val = entry{col}; + if iscell(val), val = cell2mat(val); end + + switch fields{f} + case 'loop' + loopIter(i) = val; + case 'time' + time_us(i) = val; + case 'pid_p_term' + axisP(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; + case 'pid_i_term' + axisI(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; + case 'pid_d_term' + axisD(i, 1:min(3,numel(val))) = val(1:min(3,numel(val))) / 1000; + case 'rx' + rx_cmd(i, 1:min(4,numel(val))) = val(1:min(4,numel(val))) / 1000; + case 'setpoint' + n = min(4, numel(val)); + sp = val(1:n) / 1000; + % RPY: rad/s -> deg/s, negate yaw + if n >= 1, sp(1) = sp(1) * RAD2DEG; end + if n >= 2, sp(2) = sp(2) * RAD2DEG; end + if n >= 3, sp(3) = -sp(3) * RAD2DEG; end % negate yaw + % Throttle: 0-1 -> 0-1000 + if n >= 4, sp(4) = sp(4) * 1000; end + setpt(i, 1:n) = sp; + case 'gyro_filter' + n = min(3, numel(val)); + g = val(1:n) / 1000 * RAD2DEG; + if n >= 3, g(3) = -g(3); end % negate yaw + gyro(i, 1:n) = g; + case 'gyro_raw' + n = min(3, numel(val)); + g = val(1:n) / 1000 * RAD2DEG; + if n >= 3, g(3) = -g(3); end % negate yaw + gyro_raw(i, 1:n) = g; + case 'motor' + n = min(4, numel(val)); + % 0-1 range * 1000 -> 0-2000 (BF scale) + motors(i, 1:n) = val(1:n) / 1000 * 2000; + case 'accel_raw' + % not used by PIDscope, skip + case 'accel_filter' + % not used by PIDscope, skip + case 'cpu_load' + % not used by PIDscope, skip + case 'debug' + n = min(4, numel(val)); + dbg(i, 1:n) = val(1:n); + end + end + end + + % If no gyro_filter but have gyro_raw, use raw as filtered + if all(gyro(:) == 0) && any(gyro_raw(:) ~= 0) + gyro = gyro_raw; + end + + % Build CSV output + [fdir, fname, ~] = fileparts(jsonFile); + csvFile = fullfile(fdir, [fname '.01.csv']); + + % Column names matching blackbox_decode output + header = 'loopIteration,time (us),axisP[0],axisP[1],axisP[2],axisI[0],axisI[1],axisI[2],axisD[0],axisD[1],axisD[2],gyroADC[0],gyroADC[1],gyroADC[2],rcCommand[0],rcCommand[1],rcCommand[2],rcCommand[3],setpoint[0],setpoint[1],setpoint[2],setpoint[3],motor[0],motor[1],motor[2],motor[3],debug[0],debug[1],debug[2],debug[3]'; + + % Build data matrix + M = [loopIter, time_us, axisP, axisI, axisD, gyro, rx_cmd, setpt, motors, dbg]; + + % Write CSV + fid = fopen(csvFile, 'w'); + fprintf(fid, '%s\n', header); + fclose(fid); + dlmwrite(csvFile, M, '-append', 'delimiter', ',', 'precision', '%.6g'); + + % Write synthetic header file (mimics BBL header for PSimport) + headerFile = fullfile(fdir, [fname '.quic_header.txt']); + fid = fopen(headerFile, 'w'); + fprintf(fid, 'H Firmware version:QuickSilver\n'); + fprintf(fid, 'H Firmware revision:QUICKSILVER\n'); + fprintf(fid, 'H looptime:%d\n', round(looptime_us)); + fprintf(fid, 'H rollPID:0, 0, 0\n'); + fprintf(fid, 'H pitchPID:0, 0, 0\n'); + fprintf(fid, 'H yawPID:0, 0, 0\n'); + fprintf(fid, 'H d_min:0, 0, 0\n'); + fprintf(fid, 'H feedforward_weight:0, 0, 0\n'); + fprintf(fid, 'H debug_mode:0\n'); + fclose(fid); + +end diff --git a/src/core/PSstepcalc.m b/src/core/PSstepcalc.m index ae81545..f5ca1e6 100644 --- a/src/core/PSstepcalc.m +++ b/src/core/PSstepcalc.m @@ -1,87 +1,87 @@ -function [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection, smoothFactor) -%% [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection) -% this function deconvolves the step response function using -% SP = set point (input), GY = filtered gyro (output) -% returns matrix/stack of etimated stepresponse functions, time [t] in ms -% -% -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- -smoothVals = [1 20 40 60]; -GY = smooth(GY, smoothVals(smoothFactor),'lowess'); - -minInput = 20; -segment_length = (lograte*2000); % 2 sec segments -wnd = (lograte*1000) * .5; % 500ms step response function, length will depend on lograte -StepRespDuration_ms = 500; % max dur of step resp in ms for plotting -t = 0 : 1/lograte : StepRespDuration_ms;% time in ms - -fileDurSec = length(SP) / (lograte*1000); -subsampleFactor = 1; -switch subsampleFactor - case fileDurSec <= 20 - subsampleFactor = 10; - case fileDurSec > 20 & fileDurSec <= 60 - subsampleFactor = 7; - case fileDurSec > 60 - subsampleFactor = 3; -end - -segment_vector = 1 : round(segment_length/subsampleFactor) : length(SP); -NSegs = max(find((segment_vector+segment_length) < segment_vector(end))); -if NSegs > 0 - SPseg = []; GYseg = []; - j = 0; - for i = 1 : NSegs - if max(abs(SP(segment_vector(i):segment_vector(i)+segment_length))) >= minInput - j=j+1; - SPseg(j,:) = SP(segment_vector(i):segment_vector(i)+segment_length); - GYseg(j,:) = GY(segment_vector(i):segment_vector(i)+segment_length); - end - end - - padLength = 100;% 2^nextpow2(length(SPseg(i,:))); - clear resp resp2 G H Hcon imp impf a b - j=0; - if ~isempty(SPseg) - for i = 1 : size(SPseg,1) - a = GYseg(i,:).*hann(length(GYseg(i,:)))'; - b = SPseg(i,:).*hann(length(SPseg(i,:)))'; - a = fft([zeros(1,padLength) a zeros(1,padLength)]); - b = fft([zeros(1,padLength) b zeros(1,padLength)]); - G = a / length(a); - H = b / length(b); - Hcon = conj(H); - - imp = real(ifft((G .* Hcon) ./ (H .* Hcon + 0.0001 )))'; % impulse response function - resptmp(i,:) = cumsum(imp);% integrate impulse resp function - - clear a steadyStateWindow steadyStateResp yoffset - steadyStateWindow = find(t > 200 & t < StepRespDuration_ms); - steadyStateResp = resptmp(i, steadyStateWindow); - if Ycorrection - if nanmean(steadyStateResp) < 1 || nanmean(steadyStateResp) > 1 - yoffset = 1 - nanmean(steadyStateResp); - resptmp(i,:) = resptmp(i,:) * (yoffset+1); - end - steadyStateResp = resptmp(i, steadyStateWindow); - else - end - - if min(steadyStateResp) > 0.5 && max(steadyStateResp) < 3 % Quality control - j=j+1; - stepresponse(j,:)=resptmp(i,1:1+wnd); - - end - end - else - end -else -end - - - +function [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection, smoothFactor) +%% [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection) +% this function deconvolves the step response function using +% SP = set point (input), GY = filtered gyro (output) +% returns matrix/stack of etimated stepresponse functions, time [t] in ms +% +% +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- +smoothVals = [1 20 40 60]; +GY = smooth(GY, smoothVals(smoothFactor),'lowess'); + +minInput = 20; +segment_length = (lograte*2000); % 2 sec segments +wnd = (lograte*1000) * .5; % 500ms step response function, length will depend on lograte +StepRespDuration_ms = 500; % max dur of step resp in ms for plotting +t = 0 : 1/lograte : StepRespDuration_ms;% time in ms + +fileDurSec = length(SP) / (lograte*1000); +subsampleFactor = 1; +switch subsampleFactor + case fileDurSec <= 20 + subsampleFactor = 10; + case fileDurSec > 20 & fileDurSec <= 60 + subsampleFactor = 7; + case fileDurSec > 60 + subsampleFactor = 3; +end + +segment_vector = 1 : round(segment_length/subsampleFactor) : length(SP); +NSegs = max(find((segment_vector+segment_length) < segment_vector(end))); +if NSegs > 0 + SPseg = []; GYseg = []; + j = 0; + for i = 1 : NSegs + if max(abs(SP(segment_vector(i):segment_vector(i)+segment_length))) >= minInput + j=j+1; + SPseg(j,:) = SP(segment_vector(i):segment_vector(i)+segment_length); + GYseg(j,:) = GY(segment_vector(i):segment_vector(i)+segment_length); + end + end + + padLength = 100;% 2^nextpow2(length(SPseg(i,:))); + clear resp resp2 G H Hcon imp impf a b + j=0; + if ~isempty(SPseg) + for i = 1 : size(SPseg,1) + a = GYseg(i,:).*hann(length(GYseg(i,:)))'; + b = SPseg(i,:).*hann(length(SPseg(i,:)))'; + a = fft([zeros(1,padLength) a zeros(1,padLength)]); + b = fft([zeros(1,padLength) b zeros(1,padLength)]); + G = a / length(a); + H = b / length(b); + Hcon = conj(H); + + imp = real(ifft((G .* Hcon) ./ (H .* Hcon + 0.0001 )))'; % impulse response function + resptmp(i,:) = cumsum(imp);% integrate impulse resp function + + clear a steadyStateWindow steadyStateResp yoffset + steadyStateWindow = find(t > 200 & t < StepRespDuration_ms); + steadyStateResp = resptmp(i, steadyStateWindow); + if Ycorrection + if nanmean(steadyStateResp) < 1 || nanmean(steadyStateResp) > 1 + yoffset = 1 - nanmean(steadyStateResp); + resptmp(i,:) = resptmp(i,:) * (yoffset+1); + end + steadyStateResp = resptmp(i, steadyStateWindow); + else + end + + if min(steadyStateResp) > 0.5 && max(steadyStateResp) < 3 % Quality control + j=j+1; + stepresponse(j,:)=resptmp(i,1:1+wnd); + + end + end + else + end +else +end + + + diff --git a/src/core/PSthrSpec.m b/src/core/PSthrSpec.m index ebee5b2..e26ad75 100644 --- a/src/core/PSthrSpec.m +++ b/src/core/PSthrSpec.m @@ -1,87 +1,87 @@ -function [freq ampMat] = PSthrSpec(X, Y, F, psd) -%% [freq ampMat] = PSthrSpec(X, Y, F, numspectrograms) -% computes fft as function of throttle(or motor output, optional) and generates throttle/motor x freq -% matrix. X is throttle/motor output data in percent, Y is flight data (gyro, PIDerror, etc), -% RClim sets the limit on the RC data (RPY set-point) used to compute FFT, -% F is sample frequency in Hz of the input flight data. -% The function returns a throttle/motor output x freq matrix/spectrogram [ampMat] of input data X and Y. % - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - - Tr = 100;% throttle range - multiplier = 0.3; % results in 300ms segments (must be same in datatip for accurate reading) - wnd = 1; % throttle window size -> throttle +/- wnd - segment_length = F * 1000 * multiplier; % 300ms segments - - fileDurSec = length(X) / (F*1000); - if fileDurSec <= 20 - subsampleFactor = 5; - elseif fileDurSec <= 60 - subsampleFactor = 3; - else - subsampleFactor = 1; - end - - if subsampleFactor < 1, subsampleFactor = 1; end - - segment_vector = 1 : segment_length / subsampleFactor : length(Y); - for i = 1 : length(segment_vector) - subsampleFactor - 1 - Tm(i) = nanmean(X(segment_vector(i) : segment_vector(i) + segment_length)); - Yseg(i,:) = Y(segment_vector(i) : segment_vector(i) + segment_length-1); - end - - ampMat = zeros(Tr,(segment_length / 2)); - freq = zeros(Tr,(segment_length / 2)); - - Tm(find(Tm < 0)) = 0; - [Thr_sort Thr_sortInd] = sort(Tm'); - Yseg_sort = Yseg(Thr_sortInd,:);% sorted Y according to X (throttle or motor output) - - for i = 1 : Tr - clear tmp - if ~isempty(find(Thr_sort > i - wnd & Thr_sort <= i + wnd)) - ind = find(Thr_sort > i - wnd & Thr_sort <= i + wnd); - for j = 1 : length(ind) - try - clear Ytmp Ytmp2 Y N Np Fs - Ytmp = Yseg_sort(ind(j),:)'; - N = length(Ytmp); - Fs = (F * 1000) * (1 : (N / 2)) / N; - Ytmp2 = Ytmp .* hann(N);% hann taper on non padded signal - - if psd - Y = fft(Ytmp2); - psdx = abs(Y).^2 / (F * 1000 * N); % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to - psdx(2 : end - 1) = 2 * psdx(2 : end-1); - psdx = psdx(1 : N / 2); - % scale to dB - Y = 10 * log10(psdx) + 40; - else - Y = abs(fft(Ytmp2,N)) / N; - Y = Y(1 : (N / 2)); - end - - tmp(j,:) = Y; - warning off - - catch - end - end - a = nanmean(tmp,1); - ampMat(i,:) = a; - freq(i,:) = Fs; - end - end -% clear tmp -% tmp = ampMat; -% clear ampMat -% if F>2, ampMat=tmp(:,1:floor(size(tmp,2)*(1/F)*2)); end -% if F<=2, ampMat=tmp; end -end - +function [freq ampMat] = PSthrSpec(X, Y, F, psd) +%% [freq ampMat] = PSthrSpec(X, Y, F, numspectrograms) +% computes fft as function of throttle(or motor output, optional) and generates throttle/motor x freq +% matrix. X is throttle/motor output data in percent, Y is flight data (gyro, PIDerror, etc), +% RClim sets the limit on the RC data (RPY set-point) used to compute FFT, +% F is sample frequency in Hz of the input flight data. +% The function returns a throttle/motor output x freq matrix/spectrogram [ampMat] of input data X and Y. % + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + + Tr = 100;% throttle range + multiplier = 0.3; % results in 300ms segments (must be same in datatip for accurate reading) + wnd = 1; % throttle window size -> throttle +/- wnd + segment_length = F * 1000 * multiplier; % 300ms segments + + fileDurSec = length(X) / (F*1000); + if fileDurSec <= 20 + subsampleFactor = 5; + elseif fileDurSec <= 60 + subsampleFactor = 3; + else + subsampleFactor = 1; + end + + if subsampleFactor < 1, subsampleFactor = 1; end + + segment_vector = 1 : segment_length / subsampleFactor : length(Y); + for i = 1 : length(segment_vector) - subsampleFactor - 1 + Tm(i) = nanmean(X(segment_vector(i) : segment_vector(i) + segment_length)); + Yseg(i,:) = Y(segment_vector(i) : segment_vector(i) + segment_length-1); + end + + ampMat = zeros(Tr,(segment_length / 2)); + freq = zeros(Tr,(segment_length / 2)); + + Tm(find(Tm < 0)) = 0; + [Thr_sort Thr_sortInd] = sort(Tm'); + Yseg_sort = Yseg(Thr_sortInd,:);% sorted Y according to X (throttle or motor output) + + for i = 1 : Tr + clear tmp + if ~isempty(find(Thr_sort > i - wnd & Thr_sort <= i + wnd)) + ind = find(Thr_sort > i - wnd & Thr_sort <= i + wnd); + for j = 1 : length(ind) + try + clear Ytmp Ytmp2 Y N Np Fs + Ytmp = Yseg_sort(ind(j),:)'; + N = length(Ytmp); + Fs = (F * 1000) * (1 : (N / 2)) / N; + Ytmp2 = Ytmp .* hann(N);% hann taper on non padded signal + + if psd + Y = fft(Ytmp2); + psdx = abs(Y).^2 / (F * 1000 * N); % (1/(F*N)) * abs(Y).^2 is exactly same as abs(Y).^2 / (F*N), to + psdx(2 : end - 1) = 2 * psdx(2 : end-1); + psdx = psdx(1 : N / 2); + % scale to dB + Y = 10 * log10(psdx) + 40; + else + Y = abs(fft(Ytmp2,N)) / N; + Y = Y(1 : (N / 2)); + end + + tmp(j,:) = Y; + warning off + + catch + end + end + a = nanmean(tmp,1); + ampMat(i,:) = a; + freq(i,:) = Fs; + end + end +% clear tmp +% tmp = ampMat; +% clear ampMat +% if F>2, ampMat=tmp(:,1:floor(size(tmp,2)*(1/F)*2)); end +% if F<=2, ampMat=tmp; end +end + diff --git a/src/core/PStimeFreqCalc.m b/src/core/PStimeFreqCalc.m index c3c97c3..abd2899 100644 --- a/src/core/PStimeFreqCalc.m +++ b/src/core/PStimeFreqCalc.m @@ -1,44 +1,44 @@ -function [Tm freq specMat] = PStimeFreqCalc(Y, F, smoothFactor, subsampleFactor) -%% [Tm, freq, specMat] = PStimeFreqCalc(Y, F, smoothFactor) -% computes fft as function of time and generates time x freq matrix, -% specMat, a time vector, Tm, and freq vector, Fs. -% Y is flight data (gyro, PIDerror, etc) -% F is sample frequency in kHz of the input flight data. - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - multiplier = 0.3; % results in 300ms segments (must be same in datatip for accurate reading) - segment_length = F * 1000 * multiplier; % 300ms segments - halfSegment = round(segment_length/2); - fileDurSec = length(Y) / (F*1000); - - stepsz = round(segment_length/subsampleFactor); - - smpls = (1 : stepsz : size(Y,1) - segment_length); - Tm = smpls / (F*1000); - - Yseg=[]; - j = 0; - for i = smpls - j = j + 1; - if i < segment_length - Yseg(j,:) = Y(i : i+(segment_length)-1); - else - Yseg(j,:) = Y(i-(halfSegment) : i+(halfSegment)-1); - end - end - - specMat=[];freq = []; - for i = 1 : size(Yseg,1) - [freq specMat(i,:)] = PSSpec2d(Yseg(i,:), F, 1); - specMat(i,:) = smooth(specMat(i,:), smoothFactor, 'lowess'); - end - specMat = flipud(specMat'); - -end - +function [Tm freq specMat] = PStimeFreqCalc(Y, F, smoothFactor, subsampleFactor) +%% [Tm, freq, specMat] = PStimeFreqCalc(Y, F, smoothFactor) +% computes fft as function of time and generates time x freq matrix, +% specMat, a time vector, Tm, and freq vector, Fs. +% Y is flight data (gyro, PIDerror, etc) +% F is sample frequency in kHz of the input flight data. + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + multiplier = 0.3; % results in 300ms segments (must be same in datatip for accurate reading) + segment_length = F * 1000 * multiplier; % 300ms segments + halfSegment = round(segment_length/2); + fileDurSec = length(Y) / (F*1000); + + stepsz = round(segment_length/subsampleFactor); + + smpls = (1 : stepsz : size(Y,1) - segment_length); + Tm = smpls / (F*1000); + + Yseg=[]; + j = 0; + for i = smpls + j = j + 1; + if i < segment_length + Yseg(j,:) = Y(i : i+(segment_length)-1); + else + Yseg(j,:) = Y(i-(halfSegment) : i+(halfSegment)-1); + end + end + + specMat=[];freq = []; + for i = 1 : size(Yseg,1) + [freq specMat(i,:)] = PSSpec2d(Yseg(i,:), F, 1); + specMat(i,:) = smooth(specMat(i,:), smoothFactor, 'lowess'); + end + specMat = flipud(specMat'); + +end + diff --git a/src/plot/PSdispSetupInfo.m b/src/plot/PSdispSetupInfo.m index 1aa8da5..7ed0884 100644 --- a/src/plot/PSdispSetupInfo.m +++ b/src/plot/PSdispSetupInfo.m @@ -1,61 +1,61 @@ -%% PSdispSetupInfo - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - if Nfiles < 2 - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); % Octave compatible (was: strings) - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); - setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); % Octave compatible (was: string()) - else - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); - setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); - - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo,1), 1); - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,1), str); - setupB=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,2)); - end - - BGCol = []; - try - for i = 1 : size(setupA,1) - if strcmp(setupA{i}, setupB{i}) - BGCol(i,:) = [1 1 1]; - else - BGCol(i,:) = [1 .7 .7]; - end - end - catch - BGCol=[1 1 1]; - end - u=[]; - u = (sum(BGCol,2)/3) < 1; - - if get(guiHandlesInfo.checkboxDIFF, 'Value') == 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); - set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) - set(t,'BackgroundColor', [1 .7 .7]) - if Nfiles > 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); - set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) - set(t,'BackgroundColor', [1 .7 .7]) - end - else - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); - set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) - set(t,'BackgroundColor', [BGCol]) - if Nfiles > 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); - set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) - set(t,'BackgroundColor', [BGCol]) - end - end -end - +%% PSdispSetupInfo + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + if Nfiles < 2 + str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); % Octave compatible (was: strings) + str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); + setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); % Octave compatible (was: string()) + else + str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); + str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); + setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); + + str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo,1), 1); + str2=strcat(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,1), str); + setupB=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,2)); + end + + BGCol = []; + try + for i = 1 : size(setupA,1) + if strcmp(setupA{i}, setupB{i}) + BGCol(i,:) = [1 1 1]; + else + BGCol(i,:) = [1 .7 .7]; + end + end + catch + BGCol=[1 1 1]; + end + u=[]; + u = (sum(BGCol,2)/3) < 1; + + if get(guiHandlesInfo.checkboxDIFF, 'Value') == 1 + t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); + set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) + set(t,'BackgroundColor', [1 .7 .7]) + if Nfiles > 1 + t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); + set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) + set(t,'BackgroundColor', [1 .7 .7]) + end + else + t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); + set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) + set(t,'BackgroundColor', [BGCol]) + if Nfiles > 1 + t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); + set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) + set(t,'BackgroundColor', [BGCol]) + end + end +end + \ No newline at end of file diff --git a/src/plot/PSplotDynNotchOverlay.m b/src/plot/PSplotDynNotchOverlay.m index ea338d7..e805a11 100644 --- a/src/plot/PSplotDynNotchOverlay.m +++ b/src/plot/PSplotDynNotchOverlay.m @@ -1,78 +1,78 @@ -function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) -%% PSplotDynNotchOverlay - overlay dynamic notch frequencies on spectrogram -% ax - axes handle -% notchMat - Nx3 matrix [notch1_Hz, notch2_Hz, notch3_Hz] (same length as xData) -% xData - Nx1 throttle (%) or time index for each sample -% imgHeight - number of pixel rows in image (size(img,1)) -% freqMax - max frequency in Hz (Nyquist) -% mode - 'throttle' or 'time' - -if isempty(notchMat) || imgHeight < 2 || freqMax <= 0 - return; -end - -hold(ax, 'on'); -colors = [0 1 1; 1 1 1; 1 0 1]; % cyan, white, magenta -numNotches = min(3, size(notchMat, 2)); -hz_per_pixel = freqMax / imgHeight; - -if strcmp(mode, 'throttle') - % Average notch freq per throttle bin (1-100, same as PSthrSpec) - for n = 1:numNotches - xPts = []; - yPts = []; - for bin = 1:100 - inBin = abs(xData - bin) <= 1; - if any(inBin) - vals = notchMat(inBin, n); - vals = vals(vals > 0 & vals < freqMax); - if ~isempty(vals) - avgHz = nanmean(vals); - y_px = imgHeight - round(avgHz / hz_per_pixel); - if y_px >= 1 && y_px <= imgHeight - xPts(end+1) = bin; - yPts(end+1) = y_px; - end - end - end - end - if ~isempty(xPts) - h = plot(ax, xPts, yPts, '.', 'MarkerSize', 4); - set(h, 'Color', colors(n,:), 'HitTest', 'off'); - end - end - -elseif strcmp(mode, 'time') - % xData = number of time windows in spectrogram - % notchMat has full-rate samples, need to resample to numWindows - numWindows = xData; - numSamples = size(notchMat, 1); - if numSamples == 0 || numWindows == 0, return; end - - samplesPerWindow = max(1, round(numSamples / numWindows)); - - for n = 1:numNotches - xPts = []; - yPts = []; - for w = 1:numWindows - lo = max(1, round((w-1) * samplesPerWindow) + 1); - hi = min(numSamples, round(w * samplesPerWindow)); - vals = notchMat(lo:hi, n); - vals = vals(vals > 0 & vals < freqMax); - if ~isempty(vals) - avgHz = nanmean(vals); - y_px = imgHeight - round(avgHz / hz_per_pixel); - if y_px >= 1 && y_px <= imgHeight - xPts(end+1) = w; - yPts(end+1) = y_px; - end - end - end - if ~isempty(xPts) - h = plot(ax, xPts, yPts, '.', 'MarkerSize', 3); - set(h, 'Color', colors(n,:), 'HitTest', 'off'); - end - end -end - -end +function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) +%% PSplotDynNotchOverlay - overlay dynamic notch frequencies on spectrogram +% ax - axes handle +% notchMat - Nx3 matrix [notch1_Hz, notch2_Hz, notch3_Hz] (same length as xData) +% xData - Nx1 throttle (%) or time index for each sample +% imgHeight - number of pixel rows in image (size(img,1)) +% freqMax - max frequency in Hz (Nyquist) +% mode - 'throttle' or 'time' + +if isempty(notchMat) || imgHeight < 2 || freqMax <= 0 + return; +end + +hold(ax, 'on'); +colors = [0 1 1; 1 1 1; 1 0 1]; % cyan, white, magenta +numNotches = min(3, size(notchMat, 2)); +hz_per_pixel = freqMax / imgHeight; + +if strcmp(mode, 'throttle') + % Average notch freq per throttle bin (1-100, same as PSthrSpec) + for n = 1:numNotches + xPts = []; + yPts = []; + for bin = 1:100 + inBin = abs(xData - bin) <= 1; + if any(inBin) + vals = notchMat(inBin, n); + vals = vals(vals > 0 & vals < freqMax); + if ~isempty(vals) + avgHz = nanmean(vals); + y_px = imgHeight - round(avgHz / hz_per_pixel); + if y_px >= 1 && y_px <= imgHeight + xPts(end+1) = bin; + yPts(end+1) = y_px; + end + end + end + end + if ~isempty(xPts) + h = plot(ax, xPts, yPts, '.', 'MarkerSize', 4); + set(h, 'Color', colors(n,:), 'HitTest', 'off'); + end + end + +elseif strcmp(mode, 'time') + % xData = number of time windows in spectrogram + % notchMat has full-rate samples, need to resample to numWindows + numWindows = xData; + numSamples = size(notchMat, 1); + if numSamples == 0 || numWindows == 0, return; end + + samplesPerWindow = max(1, round(numSamples / numWindows)); + + for n = 1:numNotches + xPts = []; + yPts = []; + for w = 1:numWindows + lo = max(1, round((w-1) * samplesPerWindow) + 1); + hi = min(numSamples, round(w * samplesPerWindow)); + vals = notchMat(lo:hi, n); + vals = vals(vals > 0 & vals < freqMax); + if ~isempty(vals) + avgHz = nanmean(vals); + y_px = imgHeight - round(avgHz / hz_per_pixel); + if y_px >= 1 && y_px <= imgHeight + xPts(end+1) = w; + yPts(end+1) = y_px; + end + end + end + if ~isempty(xPts) + h = plot(ax, xPts, yPts, '.', 'MarkerSize', 3); + set(h, 'Color', colors(n,:), 'HitTest', 'off'); + end + end +end + +end diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index c0f7333..a45f11d 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -1,295 +1,295 @@ -%% PSplotLogViewer - script to plot main line graphs - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - - set(PSfig, 'pointer', 'watch') - - global logviewerYscale - logviewerYscale = str2num(get(guiHandles.maxY_input, 'String')); - - figure(PSfig); - - maxY=str2num(get(guiHandles.maxY_input, 'String')); - - alpha_red=.8; - alpha_blue=.8; - - % scale fonts according to size of window and/or screen - PSfig_pos = get(PSfig, 'Position'); - screensz_tmp = get(0,'ScreenSize'); if PSfig_pos(3) > 10, PSfig_pos(3:4) = PSfig_pos(3:4) ./ screensz_tmp(3:4); end - prop_max_screen=(max([PSfig_pos(3) PSfig_pos(4)])); - fontsz=(screensz_multiplier*prop_max_screen); - - f = fields(guiHandles); - for i = 1 : size(f,1) - try set(guiHandles.(f{i}), 'FontSize', fontsz); catch, end - end - set(controlpanel, 'FontSize', fontsz); - - lineSmoothFactors = [1 10 20 40 80]; - - if plotall_flag>=0 - allVal = get(guiHandles.checkbox15, 'Value'); - set(guiHandles.checkbox0, 'Value', allVal); - set(guiHandles.checkbox1, 'Value', allVal); - set(guiHandles.checkbox2, 'Value', allVal); - set(guiHandles.checkbox3, 'Value', allVal); - set(guiHandles.checkbox4, 'Value', allVal); - set(guiHandles.checkbox5, 'Value', allVal); - set(guiHandles.checkbox6, 'Value', allVal); - set(guiHandles.checkbox7, 'Value', allVal); - set(guiHandles.checkbox8, 'Value', allVal); - set(guiHandles.checkbox9, 'Value', allVal); - set(guiHandles.checkbox10, 'Value', allVal); - set(guiHandles.checkbox11, 'Value', allVal); - set(guiHandles.checkbox12, 'Value', allVal); - set(guiHandles.checkbox13, 'Value', allVal); - set(guiHandles.checkbox14, 'Value', allVal); - end - plotall_flag=-1; - - expand_sz=[0.05 0.06 0.815 0.835]; - - - %% where you want full range of data - fileIdx = get(guiHandles.FileNum, 'Value'); - - % Update Debug checkbox label for RC_INTERPOLATION mode (version-aware) - tmpRCidx = RC_INTERPOLATION; % global default - if exist('debugIdx','var') && numel(debugIdx) >= fileIdx - tmpRCidx = debugIdx{fileIdx}.RC_INTERPOLATION; - end - if exist('debugmode','var') && numel(debugmode) >= fileIdx && debugmode(fileIdx) == tmpRCidx - set(guiHandles.checkbox0, 'String', 'SP (raw)'); - else - set(guiHandles.checkbox0, 'String', 'Debug'); - end - - % if start or end > length of file, or start > end - if (epoch1_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch2_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch1_A(fileIdx) > epoch2_A(fileIdx)) - epoch1_A(fileIdx) = 2; - epoch2_A(fileIdx) = floor(tta{fileIdx}(end) / us2sec) - 1; - end - - y=[epoch1_A(fileIdx)*us2sec epoch2_A(fileIdx)*us2sec];%%% used for fill in unused data range - t1=(tta{fileIdx}(find(tta{fileIdx}>y(1),1))) / us2sec; - t2=(tta{fileIdx}(find(tta{fileIdx}>y(2),1))) / us2sec; - - tIND{fileIdx} = (tta{fileIdx} > (t1*us2sec)) & (tta{fileIdx} < (t2*us2sec)); - -% jRangeSlider = com.jidesoft.swing.RangeSlider(0,200,10,190); % min,max,low,high -% jRangeSlider = javacomponent(jRangeSlider,[50, 80, 500, 80], gcf); -% set(jRangeSlider, 'MajorTickSpacing',50, 'PaintTicks',true, 'PaintLabels',true, 'Background',java.awt.Color.white) -% jRangeSlider.LowValue = 20;, jRangeSlider.HighValue = 180; - - guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',[0.0826 0.905 0.787 0.02],... - 'min',0,'max',1, 'callback','PSslider1Actions;'); - - - - %% log viewer line plots - %%%%%%%% PLOT %%%%%%% - axLabel={'Roll';'Pitch';'Yaw'}; - lineStyleLV = {'-'; '-'; '-'}; - lineStyle2LV = {'-'; '--'; ':'}; - lineStyle2LVnames = {'solid' ; 'dashed' ; 'dotted'}; - axesOptionsLV = find([get(guiHandles.plotR, 'Value') get(guiHandles.plotP, 'Value') get(guiHandles.plotY, 'Value')]); - - try delete(hexpand1); catch, end - try delete(hexpand2); catch, end - try delete(hexpand3); catch, end - expandON = 0; - - ylabelname=''; - for i = 1 : size(axesOptionsLV,2) - if i == size(axesOptionsLV,2) - ylabelname = [ylabelname axLabel{axesOptionsLV(i)} '-' lineStyle2LVnames{i} ' (deg/s) ']; - else - ylabelname = [ylabelname axLabel{axesOptionsLV(i)} '-' lineStyle2LVnames{i} ' | ']; - end - end - - PSfig; - - try zoomOn = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn = 0; end - if ~zoomOn && ~expandON % - delete(subplot('position' ,fullszPlot)); - delete(subplot('position',posInfo.linepos1)); - delete(subplot('position',posInfo.linepos2)); - delete(subplot('position',posInfo.linepos3)); - delete(subplot('position',posInfo.linepos4)); - end - - for i = 1 : 19 - try - eval(['delete([hch' int2str(i) '])']) - catch - end - end - - - try % datacursormode not available in Octave - dcm_obj = datacursormode(PSfig); - set(dcm_obj,'UpdateFcn',@PSdatatip); - end - - cntLV = 0; - lnstyle = lineStyleLV; - - if exist('fnameMaster','var') && ~isempty(fnameMaster) - for ii = axesOptionsLV - if get(guiHandles.RPYcomboLV, 'Value'), expandON = 0; end - %%%%%%% - if ~get(guiHandles.RPYcomboLV, 'Value') && ~expandON - eval(['LVpanel' int2str(ii) '=subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) - LVpanel5 = subplot('position',posInfo.linepos4); - end - if ~get(guiHandles.RPYcomboLV, 'Value') && expandON - try - eval(['subplot(hexpand' int2str(ii) ',' '''position''' ',expand_sz);']) - warning off - catch - end - end - - if eval(['~isempty(hexpand' int2str(ii) ') && ishandle(hexpand' int2str(ii) ') || ~expandON']) - - cntLV = cntLV + 1; - if get(guiHandles.RPYcomboLV, 'Value') - LVpanel4 = subplot('position' ,fullszPlot) - lnstyle = lineStyle2LV; - end - if ~get(guiHandles.RPYcomboLV, 'Value') && expandON == 0 - eval(['LVpanel' int2str(ii) '= subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) - lnstyle = lineStyleLV; - end - - xmax=max(tta{fileIdx}/us2sec); - - - h=plot([0 xmax],[-maxY -maxY],'k'); - set(h,'linewidth',.2) - hold on - - set(gca,'ytick',[ -(maxY/2) 0 maxY/2 ],'yticklabel',{num2str(-(maxY/2)) '0' num2str((maxY/2)) ''},'YColor',[.2 .2 .2],'fontweight','bold') - set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) - - sFactor = lineSmoothFactors(get(guiHandles.lineSmooth, 'Value')); - fileIdx = get(guiHandles.FileNum, 'Value'); - lwVal = get(guiHandles.linewidth, 'Value')/2; - - if get(guiHandles.checkbox0, 'Value'), hch1=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['debug_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox1, 'Value'), hch2=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['gyroADC_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox2, 'Value'), hch3=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisP_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox3, 'Value'), hch4=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisI_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox4, 'Value') && ii<3, hch5=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisDpf_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox5, 'Value') && ii<3, hch6=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisD_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox6, 'Value'), hch7=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisF_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox7, 'Value'), hch8=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['setpoint_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox8, 'Value'), hch9=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['pidsum_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox9, 'Value'), hch10=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['piderr_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - - - h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - h=fill([t2,xmax,xmax,t2],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - - try zoomOn2 = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn2 = 0; end - if zoomOn2 - v = axis; - axis(v) - else - axis([0 xmax -maxY maxY]) - end - - box off - if get(guiHandles.RPYcomboLV, 'Value') - y=ylabel([ylabelname],'fontweight','bold','rotation', 90); - else - y=ylabel([axLabel{ii} ' (deg/s)'],'fontweight','bold','rotation', 90); - end - - - set(y,'Units','normalized', 'position', [-.035 .5 1],'color',[.2 .2 .2]); - y=xlabel('Time (s)','fontweight','bold'); - set(y,'color',[.2 .2 .2]); - set(gca,'fontsize',fontsz,'XMinorGrid','on') - grid on - - % Percent variables - LVpanel5 = subplot('position',posInfo.linepos4); - if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_0_, sFactor, 'loess'));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_1_, sFactor, 'loess'));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_2_, sFactor, 'loess'));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox13, 'Value'), try hch14=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_3_, sFactor, 'loess'));hold on;set(hch14,'color', [linec.col13],'LineWidth',lwVal), catch, end, end - % motor sigs 4-7 for x8 configuration - if get(guiHandles.checkbox10, 'Value'), try hch15=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_4_, sFactor, 'loess'));hold on;set(hch15,'color', [linec.col10],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox11, 'Value'), try hch16=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_5_, sFactor, 'loess'));hold on;set(hch16,'color', [linec.col11],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox12, 'Value'), try hch17=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_6_, sFactor, 'loess'));hold on;set(hch17,'color', [linec.col12],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox13, 'Value'), try hch18=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_7_, sFactor, 'loess'));hold on;set(hch18,'color', [linec.col13],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - - if get(guiHandles.checkbox14, 'Value'), hch19=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.setpoint_3_/10, sFactor, 'loess'));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end - - axis([0 xmax 0 100]) - - h=fill([0,t1,t1,0],[0, 0, 100, 100],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - h=fill([t2,xmax,xmax,t2],[0, 0, 100, 100],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - - y=xlabel('Time (s)','fontweight','bold'); - set(y,'color',[.2 .2 .2]); - y=ylabel({'Throttle | Motor (%)'},'fontweight','bold'); - set(gca,'fontsize',fontsz,'XMinorGrid','on','ylim',[0 100],'ytick',[0 20 40 60 80 100],'fontweight','bold') - set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) - grid on - - - - - end - - try - if ii==1 && ~expandON - set(LVpanel1,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos1]); - end - if ii==2 && ~expandON - set(LVpanel2,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos2]); - end - if ii==3 && ~expandON - set(LVpanel3,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos3]); - end - if ~expandON - set(LVpanel5,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); - end - catch - end - end - end - - % i/o keyboard trim: 'i' sets in-point, 'o' sets out-point - set(PSfig, 'KeyPressFcn', [ ... - 'if exist(''filenameA'',''var'') && ~isempty(filenameA), ' ... - 'kk=get(gcbo,''CurrentCharacter''); fIdx=get(guiHandles.FileNum,''Value''); ' ... - 'if kk==''i'', try, [xt,~]=ginput(1); epoch1_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... - 'elseif kk==''o'', try, [xt,~]=ginput(1); epoch2_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... - 'end, end']); - - % Set up click-to-show-value datatips on all axes - PSdatatipSetup(PSfig); - - set(PSfig, 'pointer', 'arrow') -else - warndlg('Please select file(s)'); -end - - +%% PSplotLogViewer - script to plot main line graphs + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + + set(PSfig, 'pointer', 'watch') + + global logviewerYscale + logviewerYscale = str2num(get(guiHandles.maxY_input, 'String')); + + figure(PSfig); + + maxY=str2num(get(guiHandles.maxY_input, 'String')); + + alpha_red=.8; + alpha_blue=.8; + + % scale fonts according to size of window and/or screen + PSfig_pos = get(PSfig, 'Position'); + screensz_tmp = get(0,'ScreenSize'); if PSfig_pos(3) > 10, PSfig_pos(3:4) = PSfig_pos(3:4) ./ screensz_tmp(3:4); end + prop_max_screen=(max([PSfig_pos(3) PSfig_pos(4)])); + fontsz=(screensz_multiplier*prop_max_screen); + + f = fields(guiHandles); + for i = 1 : size(f,1) + try set(guiHandles.(f{i}), 'FontSize', fontsz); catch, end + end + set(controlpanel, 'FontSize', fontsz); + + lineSmoothFactors = [1 10 20 40 80]; + + if plotall_flag>=0 + allVal = get(guiHandles.checkbox15, 'Value'); + set(guiHandles.checkbox0, 'Value', allVal); + set(guiHandles.checkbox1, 'Value', allVal); + set(guiHandles.checkbox2, 'Value', allVal); + set(guiHandles.checkbox3, 'Value', allVal); + set(guiHandles.checkbox4, 'Value', allVal); + set(guiHandles.checkbox5, 'Value', allVal); + set(guiHandles.checkbox6, 'Value', allVal); + set(guiHandles.checkbox7, 'Value', allVal); + set(guiHandles.checkbox8, 'Value', allVal); + set(guiHandles.checkbox9, 'Value', allVal); + set(guiHandles.checkbox10, 'Value', allVal); + set(guiHandles.checkbox11, 'Value', allVal); + set(guiHandles.checkbox12, 'Value', allVal); + set(guiHandles.checkbox13, 'Value', allVal); + set(guiHandles.checkbox14, 'Value', allVal); + end + plotall_flag=-1; + + expand_sz=[0.05 0.06 0.815 0.835]; + + + %% where you want full range of data + fileIdx = get(guiHandles.FileNum, 'Value'); + + % Update Debug checkbox label for RC_INTERPOLATION mode (version-aware) + tmpRCidx = RC_INTERPOLATION; % global default + if exist('debugIdx','var') && numel(debugIdx) >= fileIdx + tmpRCidx = debugIdx{fileIdx}.RC_INTERPOLATION; + end + if exist('debugmode','var') && numel(debugmode) >= fileIdx && debugmode(fileIdx) == tmpRCidx + set(guiHandles.checkbox0, 'String', 'SP (raw)'); + else + set(guiHandles.checkbox0, 'String', 'Debug'); + end + + % if start or end > length of file, or start > end + if (epoch1_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch2_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch1_A(fileIdx) > epoch2_A(fileIdx)) + epoch1_A(fileIdx) = 2; + epoch2_A(fileIdx) = floor(tta{fileIdx}(end) / us2sec) - 1; + end + + y=[epoch1_A(fileIdx)*us2sec epoch2_A(fileIdx)*us2sec];%%% used for fill in unused data range + t1=(tta{fileIdx}(find(tta{fileIdx}>y(1),1))) / us2sec; + t2=(tta{fileIdx}(find(tta{fileIdx}>y(2),1))) / us2sec; + + tIND{fileIdx} = (tta{fileIdx} > (t1*us2sec)) & (tta{fileIdx} < (t2*us2sec)); + +% jRangeSlider = com.jidesoft.swing.RangeSlider(0,200,10,190); % min,max,low,high +% jRangeSlider = javacomponent(jRangeSlider,[50, 80, 500, 80], gcf); +% set(jRangeSlider, 'MajorTickSpacing',50, 'PaintTicks',true, 'PaintLabels',true, 'Background',java.awt.Color.white) +% jRangeSlider.LowValue = 20;, jRangeSlider.HighValue = 180; + + guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',[0.0826 0.905 0.787 0.02],... + 'min',0,'max',1, 'callback','PSslider1Actions;'); + + + + %% log viewer line plots + %%%%%%%% PLOT %%%%%%% + axLabel={'Roll';'Pitch';'Yaw'}; + lineStyleLV = {'-'; '-'; '-'}; + lineStyle2LV = {'-'; '--'; ':'}; + lineStyle2LVnames = {'solid' ; 'dashed' ; 'dotted'}; + axesOptionsLV = find([get(guiHandles.plotR, 'Value') get(guiHandles.plotP, 'Value') get(guiHandles.plotY, 'Value')]); + + try delete(hexpand1); catch, end + try delete(hexpand2); catch, end + try delete(hexpand3); catch, end + expandON = 0; + + ylabelname=''; + for i = 1 : size(axesOptionsLV,2) + if i == size(axesOptionsLV,2) + ylabelname = [ylabelname axLabel{axesOptionsLV(i)} '-' lineStyle2LVnames{i} ' (deg/s) ']; + else + ylabelname = [ylabelname axLabel{axesOptionsLV(i)} '-' lineStyle2LVnames{i} ' | ']; + end + end + + PSfig; + + try zoomOn = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn = 0; end + if ~zoomOn && ~expandON % + delete(subplot('position' ,fullszPlot)); + delete(subplot('position',posInfo.linepos1)); + delete(subplot('position',posInfo.linepos2)); + delete(subplot('position',posInfo.linepos3)); + delete(subplot('position',posInfo.linepos4)); + end + + for i = 1 : 19 + try + eval(['delete([hch' int2str(i) '])']) + catch + end + end + + + try % datacursormode not available in Octave + dcm_obj = datacursormode(PSfig); + set(dcm_obj,'UpdateFcn',@PSdatatip); + end + + cntLV = 0; + lnstyle = lineStyleLV; + + if exist('fnameMaster','var') && ~isempty(fnameMaster) + for ii = axesOptionsLV + if get(guiHandles.RPYcomboLV, 'Value'), expandON = 0; end + %%%%%%% + if ~get(guiHandles.RPYcomboLV, 'Value') && ~expandON + eval(['LVpanel' int2str(ii) '=subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) + LVpanel5 = subplot('position',posInfo.linepos4); + end + if ~get(guiHandles.RPYcomboLV, 'Value') && expandON + try + eval(['subplot(hexpand' int2str(ii) ',' '''position''' ',expand_sz);']) + warning off + catch + end + end + + if eval(['~isempty(hexpand' int2str(ii) ') && ishandle(hexpand' int2str(ii) ') || ~expandON']) + + cntLV = cntLV + 1; + if get(guiHandles.RPYcomboLV, 'Value') + LVpanel4 = subplot('position' ,fullszPlot) + lnstyle = lineStyle2LV; + end + if ~get(guiHandles.RPYcomboLV, 'Value') && expandON == 0 + eval(['LVpanel' int2str(ii) '= subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) + lnstyle = lineStyleLV; + end + + xmax=max(tta{fileIdx}/us2sec); + + + h=plot([0 xmax],[-maxY -maxY],'k'); + set(h,'linewidth',.2) + hold on + + set(gca,'ytick',[ -(maxY/2) 0 maxY/2 ],'yticklabel',{num2str(-(maxY/2)) '0' num2str((maxY/2)) ''},'YColor',[.2 .2 .2],'fontweight','bold') + set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) + + sFactor = lineSmoothFactors(get(guiHandles.lineSmooth, 'Value')); + fileIdx = get(guiHandles.FileNum, 'Value'); + lwVal = get(guiHandles.linewidth, 'Value')/2; + + if get(guiHandles.checkbox0, 'Value'), hch1=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['debug_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox1, 'Value'), hch2=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['gyroADC_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox2, 'Value'), hch3=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisP_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox3, 'Value'), hch4=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisI_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox4, 'Value') && ii<3, hch5=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisDpf_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox5, 'Value') && ii<3, hch6=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisD_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox6, 'Value'), hch7=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisF_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox7, 'Value'), hch8=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['setpoint_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox8, 'Value'), hch9=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['pidsum_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox9, 'Value'), hch10=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['piderr_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + + + h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); + set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); + h=fill([t2,xmax,xmax,t2],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); + set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); + + try zoomOn2 = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn2 = 0; end + if zoomOn2 + v = axis; + axis(v) + else + axis([0 xmax -maxY maxY]) + end + + box off + if get(guiHandles.RPYcomboLV, 'Value') + y=ylabel([ylabelname],'fontweight','bold','rotation', 90); + else + y=ylabel([axLabel{ii} ' (deg/s)'],'fontweight','bold','rotation', 90); + end + + + set(y,'Units','normalized', 'position', [-.035 .5 1],'color',[.2 .2 .2]); + y=xlabel('Time (s)','fontweight','bold'); + set(y,'color',[.2 .2 .2]); + set(gca,'fontsize',fontsz,'XMinorGrid','on') + grid on + + % Percent variables + LVpanel5 = subplot('position',posInfo.linepos4); + if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_0_, sFactor, 'loess'));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_1_, sFactor, 'loess'));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_2_, sFactor, 'loess'));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox13, 'Value'), try hch14=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_3_, sFactor, 'loess'));hold on;set(hch14,'color', [linec.col13],'LineWidth',lwVal), catch, end, end + % motor sigs 4-7 for x8 configuration + if get(guiHandles.checkbox10, 'Value'), try hch15=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_4_, sFactor, 'loess'));hold on;set(hch15,'color', [linec.col10],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox11, 'Value'), try hch16=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_5_, sFactor, 'loess'));hold on;set(hch16,'color', [linec.col11],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox12, 'Value'), try hch17=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_6_, sFactor, 'loess'));hold on;set(hch17,'color', [linec.col12],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox13, 'Value'), try hch18=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_7_, sFactor, 'loess'));hold on;set(hch18,'color', [linec.col13],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + + if get(guiHandles.checkbox14, 'Value'), hch19=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.setpoint_3_/10, sFactor, 'loess'));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end + + axis([0 xmax 0 100]) + + h=fill([0,t1,t1,0],[0, 0, 100, 100],[.8 .8 .8]); + set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); + h=fill([t2,xmax,xmax,t2],[0, 0, 100, 100],[.8 .8 .8]); + set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); + + y=xlabel('Time (s)','fontweight','bold'); + set(y,'color',[.2 .2 .2]); + y=ylabel({'Throttle | Motor (%)'},'fontweight','bold'); + set(gca,'fontsize',fontsz,'XMinorGrid','on','ylim',[0 100],'ytick',[0 20 40 60 80 100],'fontweight','bold') + set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) + grid on + + + + + end + + try + if ii==1 && ~expandON + set(LVpanel1,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos1]); + end + if ii==2 && ~expandON + set(LVpanel2,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos2]); + end + if ii==3 && ~expandON + set(LVpanel3,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos3]); + end + if ~expandON + set(LVpanel5,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); + end + catch + end + end + end + + % i/o keyboard trim: 'i' sets in-point, 'o' sets out-point + set(PSfig, 'KeyPressFcn', [ ... + 'if exist(''filenameA'',''var'') && ~isempty(filenameA), ' ... + 'kk=get(gcbo,''CurrentCharacter''); fIdx=get(guiHandles.FileNum,''Value''); ' ... + 'if kk==''i'', try, [xt,~]=ginput(1); epoch1_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... + 'elseif kk==''o'', try, [xt,~]=ginput(1); epoch2_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... + 'end, end']); + + % Set up click-to-show-value datatips on all axes + PSdatatipSetup(PSfig); + + set(PSfig, 'pointer', 'arrow') +else + warndlg('Please select file(s)'); +end + + diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index 65c6fc6..00f02f6 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -1,194 +1,194 @@ -%% PSplotPIDerror - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -try - -set(PSerrfig, 'pointer', 'watch') - -if ~isempty(filenameA) || ~isempty(filenameB) - %% update fonts - - PSerrfig_pos = get(PSerrfig, 'Position'); - screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end - prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); - fontsz3=round(screensz_multiplier*prop_max_screen); - - set(guiHandlesPIDerr.refresh, 'FontSize', fontsz3); - set(guiHandlesPIDerr.maxSticktext, 'FontSize', fontsz3); - set(guiHandlesPIDerr.maxStick, 'FontSize', fontsz3); - set(guiHandlesPIDerr.saveFig3, 'FontSize', fontsz3); - - - %% PID error distributions - ylab2={'roll';'pitch';'yaw'}; - figure(PSerrfig); - for p=1:3 - delete(subplot('position',posInfo.PIDerrAnalysis(p,:))) - h1=subplot('position',posInfo.PIDerrAnalysis(p,:)); cla - hold on - - if ~isempty(filenameA) - RCRateALL_Thresh_A=abs(DATtmpA.RCRate(1,:)) < maxDegsec & abs(DATtmpA.RCRate(2,:)) < maxDegsec & abs(DATtmpA.RCRate(3,:)) < maxDegsec & abs(DATtmpA.PIDerr(1,:)) < maxDegsec & abs(DATtmpA.PIDerr(2,:)) < maxDegsec & abs(DATtmpA.PIDerr(3,:)) < maxDegsec; - [yA xA]=hist(DATtmpA.PIDerr(p,RCRateALL_Thresh_A),-1000:1:1000); % wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +try + +set(PSerrfig, 'pointer', 'watch') + +if ~isempty(filenameA) || ~isempty(filenameB) + %% update fonts + + PSerrfig_pos = get(PSerrfig, 'Position'); + screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end + prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); + fontsz3=round(screensz_multiplier*prop_max_screen); + + set(guiHandlesPIDerr.refresh, 'FontSize', fontsz3); + set(guiHandlesPIDerr.maxSticktext, 'FontSize', fontsz3); + set(guiHandlesPIDerr.maxStick, 'FontSize', fontsz3); + set(guiHandlesPIDerr.saveFig3, 'FontSize', fontsz3); + + + %% PID error distributions + ylab2={'roll';'pitch';'yaw'}; + figure(PSerrfig); + for p=1:3 + delete(subplot('position',posInfo.PIDerrAnalysis(p,:))) + h1=subplot('position',posInfo.PIDerrAnalysis(p,:)); cla + hold on + + if ~isempty(filenameA) + RCRateALL_Thresh_A=abs(DATtmpA.RCRate(1,:)) < maxDegsec & abs(DATtmpA.RCRate(2,:)) < maxDegsec & abs(DATtmpA.RCRate(3,:)) < maxDegsec & abs(DATtmpA.PIDerr(1,:)) < maxDegsec & abs(DATtmpA.PIDerr(2,:)) < maxDegsec & abs(DATtmpA.PIDerr(3,:)) < maxDegsec; + [yA xA]=hist(DATtmpA.PIDerr(p,RCRateALL_Thresh_A),-1000:1:1000); % 0 & vals < freqMax); - if ~isempty(vals) - avgHz = nanmean(vals); - y_px = imgHeight - round(avgHz / hz_per_pixel); - if y_px >= 1 && y_px <= imgHeight - xPts(end+1) = bin; - yPts(end+1) = y_px; - end - end - end - end - if ~isempty(xPts) - lstyle = lineStyles{min(harm, numel(lineStyles))}; - h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); - set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); - end - end - end - -elseif strcmp(mode, 'time') - numWindows = xData; - numSamples = size(rpmMat, 1); - if numSamples == 0 || numWindows == 0, return; end - samplesPerWindow = max(1, round(numSamples / numWindows)); - - for m = 1:nMotors - for harm = 1:nHarmonics - xPts = []; - yPts = []; - for w = 1:numWindows - lo = max(1, round((w-1) * samplesPerWindow) + 1); - hi = min(numSamples, round(w * samplesPerWindow)); - vals = rpmMat(lo:hi, m) * harm; - vals = vals(vals > 0 & vals < freqMax); - if ~isempty(vals) - avgHz = nanmean(vals); - y_px = imgHeight - round(avgHz / hz_per_pixel); - if y_px >= 1 && y_px <= imgHeight - xPts(end+1) = w; - yPts(end+1) = y_px; - end - end - end - if ~isempty(xPts) - lstyle = lineStyles{min(harm, numel(lineStyles))}; - h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); - set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); - end - end - end -end - -end +function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonics) +%% PSplotRPMOverlay - overlay RPM filter motor frequencies on spectrogram +% ax - axes handle +% rpmMat - Nx4 matrix [motor1_Hz, motor2_Hz, motor3_Hz, motor4_Hz] +% xData - throttle vector (Nx1) or number of time windows (scalar) +% imgHeight - number of pixel rows in image +% freqMax - max frequency in Hz +% mode - 'throttle' or 'time' +% nHarmonics - number of harmonics to draw (default 3) + +if nargin < 7, nHarmonics = 3; end +if isempty(rpmMat) || imgHeight < 2 || freqMax <= 0 + return; +end + +hold(ax, 'on'); +motorCol = [0 .9 0; .9 .9 0; .9 0 0; .3 .5 1]; % green yellow red blue +lineStyles = {'-'; '--'; ':'}; +hz_per_pixel = freqMax / imgHeight; +nMotors = min(4, size(rpmMat, 2)); + +if strcmp(mode, 'throttle') + for m = 1:nMotors + for harm = 1:nHarmonics + xPts = []; + yPts = []; + for bin = 1:100 + inBin = abs(xData - bin) <= 1; + if any(inBin) + vals = rpmMat(inBin, m) * harm; + vals = vals(vals > 0 & vals < freqMax); + if ~isempty(vals) + avgHz = nanmean(vals); + y_px = imgHeight - round(avgHz / hz_per_pixel); + if y_px >= 1 && y_px <= imgHeight + xPts(end+1) = bin; + yPts(end+1) = y_px; + end + end + end + end + if ~isempty(xPts) + lstyle = lineStyles{min(harm, numel(lineStyles))}; + h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); + set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); + end + end + end + +elseif strcmp(mode, 'time') + numWindows = xData; + numSamples = size(rpmMat, 1); + if numSamples == 0 || numWindows == 0, return; end + samplesPerWindow = max(1, round(numSamples / numWindows)); + + for m = 1:nMotors + for harm = 1:nHarmonics + xPts = []; + yPts = []; + for w = 1:numWindows + lo = max(1, round((w-1) * samplesPerWindow) + 1); + hi = min(numSamples, round(w * samplesPerWindow)); + vals = rpmMat(lo:hi, m) * harm; + vals = vals(vals > 0 & vals < freqMax); + if ~isempty(vals) + avgHz = nanmean(vals); + y_px = imgHeight - round(avgHz / hz_per_pixel); + if y_px >= 1 && y_px <= imgHeight + xPts(end+1) = w; + yPts(end+1) = y_px; + end + end + end + if ~isempty(xPts) + lstyle = lineStyles{min(harm, numel(lineStyles))}; + h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); + set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); + end + end + end +end + +end diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 824b91a..d275733 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -1,365 +1,365 @@ -%% PSplotSpec - script that computes and plots spectrograms - - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - -PSspecfig_pos = get(PSspecfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSspecfig_pos(3) > 10, PSspecfig_pos(3:4) = PSspecfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSspecfig_pos(3) PSspecfig_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); -%% update fonts - -f = fields(guiHandlesSpec); -for i = 1 : size(f,1) - try - h = guiHandlesSpec.(f{i}); - if iscell(h) - for ci = 1:numel(h), set(h{ci}, 'FontSize', fontsz); end - else - set(h, 'FontSize', fontsz); - end - catch - end -end - -guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); - -%% - -s1={'';'gyroADC';'debug';'piderr';'setpoint';'axisP';'axisD';'axisDpf';'pidsum'}; - -datSelectionString=[s1]; - -clear vars -for i=1:4 - vars(i)=get(guiHandlesSpec.SpecSelect{i}, 'Value'); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%% compute fft %%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -if get(guiHandlesSpec.SpecSelect{1}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{2}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{3}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{4}, 'Value')>1 - set(PSspecfig, 'pointer', 'watch') - if updateSpec==0 - clear s dat ampmat amp2d freq a RC smat amp2d freq2d Throt - p=0; - hw = waitbar(0,['please wait... ' ]); - - tmpPSDVal = get(guiHandlesSpec.checkboxPSD, 'Value'); - for k=1:length(vars) - tmpFileSelK = get(guiHandlesSpec.FileSelect{k}, 'Value'); - s=char(datSelectionString(vars(k))); - for a=1:3, - if ( ( ~isempty(strfind(s,'axisD'))) && a==3) || isempty(s) - p=p+1; - smat{p}=[];%string - ampmat{p}=[];%spec matrix - freq{p}=[];% freq matrix - amp2d{p}=[];%spec 2d - freq2d{p}=[];% freq2d - else - p=p+1; - try - eval(['dat{k}(a,:) = T{tmpFileSelK}.' char(datSelectionString(vars(k))) '_' int2str(a-1) '_(tIND{tmpFileSelK});';]) - Throt=T{tmpFileSelK}.setpoint_3_(tIND{tmpFileSelK}) / 10;% throttle - lograte = A_lograte(tmpFileSelK);%in kHz - waitbar(min(1, p/12), hw, ['processing spectrogram... ' int2str(p) ]); - smat{p}=s; - [freq{p} ampmat{p}]=PSthrSpec(Throt, dat{k}(a,:), lograte, tmpPSDVal); % compute matrices - [freq2d{p} amp2d{p}]=PSSpec2d(dat{k}(a,:),lograte, tmpPSDVal); %compute 2d amp spec at same time - catch ME - warning('PSplotSpec compute p=%d: %s', p, ME.message); - smat{p}=[]; ampmat{p}=[]; freq{p}=[]; amp2d{p}=[]; freq2d{p}=[]; - end - end - end - end - close(hw) - end -else - hwarn=warndlg({'Dropdowns set to ''NONE''.'; 'Please select a preset or specific variables to analyze.'}); - pause(3); - try - close(hwarn); - catch - end -end - -if get(guiHandlesSpec.checkbox2d, 'Value')==0 && ~isempty(ampmat) - figure(PSspecfig); - %%%%% plot spec mattrices - c1=[1 1 1 2 2 2 3 3 3 4 4 4]; - c2=[1 2 3 1 2 3 1 2 3 1 2 3]; - baselineY = [0 -40]; - ftr = fspecial('gaussian',[get(guiHandlesSpec.smoothFactor_select, 'Value')*5 get(guiHandlesSpec.smoothFactor_select, 'Value')],4); - for p=1:size(ampmat,2) - try delete(subplot('position',posInfo.SpecPos(p,:))); catch, end - if ~isempty(ampmat{p}) - try - delete(subplot('position',posInfo.SpecPos(p,:))); - h1=subplot('position',posInfo.SpecPos(p,:)); cla - img = flipud((filter2(ftr, ampmat{p} ))') + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); - imagesc(img); - - lograte=A_lograte(get(guiHandlesSpec.FileSelect{c1(p)}, 'Value')); - - axLabel={'roll';'pitch';'yaw'}; - - if get(guiHandlesSpec.Sub100HzCheck{c1(p)}, 'Value')==1 - hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim1/3.33) size(ampmat{p},2)-round(Flim1/3.33)],'y--');set(h,'linewidth',2) - hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim2/3.33) size(ampmat{p},2)-round(Flim2/3.33)],'y--');set(h,'linewidth',2) - % sub100Hz scaling - xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; - yticks=[(size(ampmat{p},2)-30):6:size(ampmat{p},2)]; - set(h1,'PlotBoxAspectRatioMode','auto','ylim',[size(ampmat{p},2)-30 size(ampmat{p},2)]) - set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',{'100';'80';'60';'40';'20';'0'},'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); - a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); - a2 = a(:,(round(Flim1/3.33))+1:(round(Flim2/3.33))); - meanspec=nanmean(a2(:)); - peakspec=max(max(a(:,(round(Flim1/3.33))+1:(round(Flim2/3.33))))); - if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 - h=text(64,(size(ampmat{p},2)-30)+3,['mean=' num2str(meanspec,3)]); - set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); - h=text(64,(size(ampmat{p},2)-30)+1,['peak=' num2str(peakspec,3)]); - set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); - else - h=text(64,(size(ampmat{p},2)-30)+3,['mean=' num2str(meanspec,3)]); - set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); - h=text(64,(size(ampmat{p},2)-30)+1,['peak=' num2str(peakspec,3)]); - set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); - end - h=text(xticks(1)+1,(size(ampmat{p},2)-30)+1,axLabel{c2(p)}); - set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') - else % full scaling - xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; - yticks=[1:(size(ampmat{p},2))/10:size(ampmat{p},2) size(ampmat{p},2)]; - maxHz = max(round(yticks * 3.333)); - ytlbl = {num2str(maxHz), '', num2str(round(maxHz*4/5)), '', num2str(round(maxHz*3/5)), '', num2str(round(maxHz*2/5)), '', num2str(round(maxHz*1/5)), '', '0'}; - set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',ytlbl,'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); - set(h1,'PlotBoxAspectRatioMode','auto','ylim',[1 size(ampmat{p},2)]) - a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); - a2 = a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)); - meanspec=nanmean(a2(:)); - peakspec=max(max(a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)))); - if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 - h=text(64,size(ampmat{p},2)*.04,['mean=' num2str(meanspec,3)]); - set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); - h=text(64,size(ampmat{p},2)*.13,['peak=' num2str(peakspec,3)]); - set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); - else - h=text(64,size(ampmat{p},2)*.04,['mean=' num2str(meanspec,3)]); - set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); - h=text(64,size(ampmat{p},2)*.13,['peak=' num2str(peakspec,3)]); - set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); - end - h=text(xticks(1)+1,size(ampmat{p},2)*.04,axLabel{c2(p)}); - set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') - end - - - grid on - ax = gca; - set(ax, 'GridColor', [1 1 1]); - if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 - set(ax, 'GridColor', [0 0 0]); % black on white background - set(h,'Color',[0 0 0],'fontsize',fontsz,'fontweight','bold') - end - ylabel('Frequency (Hz)','fontweight','bold') - xlabel('% Throttle','fontweight','bold') - - %% Dynamic notch overlay for FFT_FREQ mode - if exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') - tmpFileK = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); - tmpFFTk = FFT_FREQ; - if numel(debugIdx) >= tmpFileK - tmpFFTk = debugIdx{tmpFileK}.FFT_FREQ; - end - if debugmode(tmpFileK) == tmpFFTk && numel(notchData) >= tmpFileK && ~isempty(notchData{tmpFileK}) - % Only overlay on the axis matching gyro_debug_axis - tmpGdaK = 0; - if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileK - tmpGdaK = gyro_debug_axis(tmpFileK); - end - if (c2(p) - 1) == tmpGdaK - maxHzOverlay = (A_lograte(tmpFileK) / 2) * 1000; - PSplotDynNotchOverlay(gca, notchData{tmpFileK}, T{tmpFileK}.setpoint_3_(tIND{tmpFileK}) / 10, size(img, 1), maxHzOverlay, 'throttle'); - end - end - end - - %% RPM filter overlay (motor frequencies + harmonics) - if exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') - tmpFileK2 = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); - tmpRPMk = 46; - if numel(debugIdx) >= tmpFileK2 - tmpRPMk = debugIdx{tmpFileK2}.RPM_FILTER; - end - if debugmode(tmpFileK2) == tmpRPMk && numel(rpmFilterData) >= tmpFileK2 && ~isempty(rpmFilterData{tmpFileK2}) - maxHzOverlay2 = (A_lograte(tmpFileK2) / 2) * 1000; - PSplotRPMOverlay(gca, rpmFilterData{tmpFileK2}, T{tmpFileK2}.setpoint_3_(tIND{tmpFileK2}) / 10, size(img, 1), maxHzOverlay2, 'throttle'); - end - end - - %% Estimated RPM overlay from spectrum peak detection - if get(guiHandlesSpec.checkboxEstRPM, 'Value') && ~isempty(ampmat{p}) && ~isempty(freq{p}) - % find a throttle row that has freq data (not all zeros) - freqAx = []; - for rr = 1:size(freq{p}, 1) - if any(freq{p}(rr,:) > 0), freqAx = freq{p}(rr,:); break; end - end - if ~isempty(freqAx) - [estFund, estHarm] = PSestimateRPM(freqAx, ampmat{p}, 3); - % convert Hz to image pixel coords (img is flipped: row 1 = max Hz) - maxHzEst = (A_lograte(get(guiHandlesSpec.FileSelect{c1(p)}, 'Value')) / 2) * 1000; - hz_per_px = maxHzEst / size(img, 1); - hold on; - estCol = [0 .9 .2; .9 .7 0; .9 .2 0]; % green, orange, red for 1st/2nd/3rd - estStyle = {'-'; '--'; ':'}; - for nh = 1:3 - xPts = []; yPts = []; - for tb = 1:100 - if ~isnan(estHarm(tb, nh)) && estHarm(tb, nh) > 0 && estHarm(tb, nh) < maxHzEst - y_px = size(img, 1) - round(estHarm(tb, nh) / hz_per_px); - if y_px >= 1 && y_px <= size(img, 1) - xPts(end+1) = tb; - yPts(end+1) = y_px; - end - end - end - if ~isempty(xPts) - h = plot(xPts, yPts, estStyle{nh}, 'LineWidth', 1.5); - set(h, 'Color', estCol(nh,:), 'HitTest', 'off'); - end - end - end % ~isempty(freqAx) - end - - catch ME - warning('PSplotSpec render p=%d: %s', p, ME.message); - end - end - end - - % color bar2 at the top - try - delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) - catch - end - if vars(1)>1 % 1=none - subplot('position',posInfo.SpecPos(1,:)); - hCbar1= colorbar('NorthOutside'); - set(hCbar1,'Position', [posInfo.hCbar1pos]); - end - if vars(2)>1 % 1=none - subplot('position',posInfo.SpecPos(4,:)); - hCbar2= colorbar('NorthOutside'); - set(hCbar2,'Position', [posInfo.hCbar2pos]) - end - if vars(3)>1 % 1=none - subplot('position',posInfo.SpecPos(7,:)); - hCbar3= colorbar('NorthOutside'); - set(hCbar3,'Position', [posInfo.hCbar3pos]) - end - if vars(4)>1 % 1=none - subplot('position',posInfo.SpecPos(10,:)); - hCbar4= colorbar('NorthOutside'); - set(hCbar4,'Position', [posInfo.hCbar4pos]) - end - - % color maps - use set() to avoid stale colorbar listener errors in colormap() - try - tmpCmapVal = get(guiHandlesSpec.ColormapSelect, 'Value'); - if tmpCmapVal <= 7 - tmpCmapStr = get(guiHandlesSpec.ColormapSelect, 'String'); - cm = feval(char(tmpCmapStr(tmpCmapVal)), 64); - elseif tmpCmapVal == 8 - cm = linearREDcmap; - else - cm = linearGREYcmap; - end - set(PSspecfig, 'Colormap', cm); - catch, end - -end - -if get(guiHandlesSpec.checkbox2d, 'Value')==1 && ~isempty(amp2d) - figure(PSspecfig); - try - delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) - catch - end - baselineYlines = [0 -50]; - c1=[1 1 1 2 2 2 3 3 3 4 4 4]; - c2=[1 2 3 1 2 3 1 2 3 1 2 3]; - %%%%% plot 2d amp spec - for p=1:size(amp2d,2) - axLabel={'roll';'pitch';'yaw'}; - - delete(subplot('position',posInfo.SpecPos(p,:))); - if ~isempty(amp2d{p}) - h2=subplot('position',posInfo.SpecPos(p,:)); cla - h=plot(freq2d{p}, smooth(amp2d{p}, log10(size(amp2d{p},1)) * (get(guiHandlesSpec.smoothFactor_select, 'Value')^2), 'lowess'));hold on - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2) - set(h2,'fontsize',fontsz,'fontweight','bold') - if get(guiHandlesSpec.specPresets, 'Value') <= 3 - set(h,'Color',[SpecLineCols(c1(p),:,1)]) - end - if get(guiHandlesSpec.specPresets, 'Value') > 4 && get(guiHandlesSpec.specPresets, 'Value') <= 6 - set(h,'Color',[SpecLineCols(c1(p),:,2)]) - end - if get(guiHandlesSpec.specPresets, 'Value') > 6 - set(h,'Color',[SpecLineCols(c1(p),:,3)]) - end - - if get(guiHandlesSpec.Sub100HzCheck{c1(p)}, 'Value')==1 - set(h2,'xtick',[0 20 40 60 80 100], 'yminortick','on') - axis([0 100 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) - h=plot([round(Flim1) round(Flim1)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); - set(h,'linewidth',1) - h=plot([round(Flim2) round(Flim2)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); - set(h,'linewidth',1) - else - set(h2,'xtick',[0 : ((A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 / 5) : (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000],'yminortick','on') - axis([0 (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) - end - - xlabel('Frequency (Hz)') - if get(guiHandlesSpec.checkboxPSD, 'Value') - ylabel(['PSD (dB)']) - else - ylabel(['Amplitude']) - end - - - h=text(2,climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))*.95,axLabel{c2(p)}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold') - - grid on - end - end -end -% Set up click-to-show-value datatips + double-click expand on all axes -PSdatatipSetup(PSspecfig); - -set(PSspecfig, 'pointer', 'arrow') -updateSpec=0; - -end - +%% PSplotSpec - script that computes and plots spectrograms + + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + +PSspecfig_pos = get(PSspecfig, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PSspecfig_pos(3) > 10, PSspecfig_pos(3:4) = PSspecfig_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PSspecfig_pos(3) PSspecfig_pos(4)])); +fontsz=(screensz_multiplier*prop_max_screen); +%% update fonts + +f = fields(guiHandlesSpec); +for i = 1 : size(f,1) + try + h = guiHandlesSpec.(f{i}); + if iscell(h) + for ci = 1:numel(h), set(h{ci}, 'FontSize', fontsz); end + else + set(h, 'FontSize', fontsz); + end + catch + end +end + +guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); + +%% + +s1={'';'gyroADC';'debug';'piderr';'setpoint';'axisP';'axisD';'axisDpf';'pidsum'}; + +datSelectionString=[s1]; + +clear vars +for i=1:4 + vars(i)=get(guiHandlesSpec.SpecSelect{i}, 'Value'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% compute fft %%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if get(guiHandlesSpec.SpecSelect{1}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{2}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{3}, 'Value')>1 || get(guiHandlesSpec.SpecSelect{4}, 'Value')>1 + set(PSspecfig, 'pointer', 'watch') + if updateSpec==0 + clear s dat ampmat amp2d freq a RC smat amp2d freq2d Throt + p=0; + hw = waitbar(0,['please wait... ' ]); + + tmpPSDVal = get(guiHandlesSpec.checkboxPSD, 'Value'); + for k=1:length(vars) + tmpFileSelK = get(guiHandlesSpec.FileSelect{k}, 'Value'); + s=char(datSelectionString(vars(k))); + for a=1:3, + if ( ( ~isempty(strfind(s,'axisD'))) && a==3) || isempty(s) + p=p+1; + smat{p}=[];%string + ampmat{p}=[];%spec matrix + freq{p}=[];% freq matrix + amp2d{p}=[];%spec 2d + freq2d{p}=[];% freq2d + else + p=p+1; + try + eval(['dat{k}(a,:) = T{tmpFileSelK}.' char(datSelectionString(vars(k))) '_' int2str(a-1) '_(tIND{tmpFileSelK});';]) + Throt=T{tmpFileSelK}.setpoint_3_(tIND{tmpFileSelK}) / 10;% throttle + lograte = A_lograte(tmpFileSelK);%in kHz + waitbar(min(1, p/12), hw, ['processing spectrogram... ' int2str(p) ]); + smat{p}=s; + [freq{p} ampmat{p}]=PSthrSpec(Throt, dat{k}(a,:), lograte, tmpPSDVal); % compute matrices + [freq2d{p} amp2d{p}]=PSSpec2d(dat{k}(a,:),lograte, tmpPSDVal); %compute 2d amp spec at same time + catch ME + warning('PSplotSpec compute p=%d: %s', p, ME.message); + smat{p}=[]; ampmat{p}=[]; freq{p}=[]; amp2d{p}=[]; freq2d{p}=[]; + end + end + end + end + close(hw) + end +else + hwarn=warndlg({'Dropdowns set to ''NONE''.'; 'Please select a preset or specific variables to analyze.'}); + pause(3); + try + close(hwarn); + catch + end +end + +if get(guiHandlesSpec.checkbox2d, 'Value')==0 && ~isempty(ampmat) + figure(PSspecfig); + %%%%% plot spec mattrices + c1=[1 1 1 2 2 2 3 3 3 4 4 4]; + c2=[1 2 3 1 2 3 1 2 3 1 2 3]; + baselineY = [0 -40]; + ftr = fspecial('gaussian',[get(guiHandlesSpec.smoothFactor_select, 'Value')*5 get(guiHandlesSpec.smoothFactor_select, 'Value')],4); + for p=1:size(ampmat,2) + try delete(subplot('position',posInfo.SpecPos(p,:))); catch, end + if ~isempty(ampmat{p}) + try + delete(subplot('position',posInfo.SpecPos(p,:))); + h1=subplot('position',posInfo.SpecPos(p,:)); cla + img = flipud((filter2(ftr, ampmat{p} ))') + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); + imagesc(img); + + lograte=A_lograte(get(guiHandlesSpec.FileSelect{c1(p)}, 'Value')); + + axLabel={'roll';'pitch';'yaw'}; + + if get(guiHandlesSpec.Sub100HzCheck{c1(p)}, 'Value')==1 + hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim1/3.33) size(ampmat{p},2)-round(Flim1/3.33)],'y--');set(h,'linewidth',2) + hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim2/3.33) size(ampmat{p},2)-round(Flim2/3.33)],'y--');set(h,'linewidth',2) + % sub100Hz scaling + xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; + yticks=[(size(ampmat{p},2)-30):6:size(ampmat{p},2)]; + set(h1,'PlotBoxAspectRatioMode','auto','ylim',[size(ampmat{p},2)-30 size(ampmat{p},2)]) + set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',{'100';'80';'60';'40';'20';'0'},'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); + a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); + a2 = a(:,(round(Flim1/3.33))+1:(round(Flim2/3.33))); + meanspec=nanmean(a2(:)); + peakspec=max(max(a(:,(round(Flim1/3.33))+1:(round(Flim2/3.33))))); + if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 + h=text(64,(size(ampmat{p},2)-30)+3,['mean=' num2str(meanspec,3)]); + set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); + h=text(64,(size(ampmat{p},2)-30)+1,['peak=' num2str(peakspec,3)]); + set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); + else + h=text(64,(size(ampmat{p},2)-30)+3,['mean=' num2str(meanspec,3)]); + set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); + h=text(64,(size(ampmat{p},2)-30)+1,['peak=' num2str(peakspec,3)]); + set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); + end + h=text(xticks(1)+1,(size(ampmat{p},2)-30)+1,axLabel{c2(p)}); + set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + else % full scaling + xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; + yticks=[1:(size(ampmat{p},2))/10:size(ampmat{p},2) size(ampmat{p},2)]; + maxHz = max(round(yticks * 3.333)); + ytlbl = {num2str(maxHz), '', num2str(round(maxHz*4/5)), '', num2str(round(maxHz*3/5)), '', num2str(round(maxHz*2/5)), '', num2str(round(maxHz*1/5)), '', '0'}; + set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',ytlbl,'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); + set(h1,'PlotBoxAspectRatioMode','auto','ylim',[1 size(ampmat{p},2)]) + a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); + a2 = a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)); + meanspec=nanmean(a2(:)); + peakspec=max(max(a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)))); + if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 + h=text(64,size(ampmat{p},2)*.04,['mean=' num2str(meanspec,3)]); + set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); + h=text(64,size(ampmat{p},2)*.13,['peak=' num2str(peakspec,3)]); + set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); + else + h=text(64,size(ampmat{p},2)*.04,['mean=' num2str(meanspec,3)]); + set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); + h=text(64,size(ampmat{p},2)*.13,['peak=' num2str(peakspec,3)]); + set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); + end + h=text(xticks(1)+1,size(ampmat{p},2)*.04,axLabel{c2(p)}); + set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + end + + + grid on + ax = gca; + set(ax, 'GridColor', [1 1 1]); + if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 + set(ax, 'GridColor', [0 0 0]); % black on white background + set(h,'Color',[0 0 0],'fontsize',fontsz,'fontweight','bold') + end + ylabel('Frequency (Hz)','fontweight','bold') + xlabel('% Throttle','fontweight','bold') + + %% Dynamic notch overlay for FFT_FREQ mode + if exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') + tmpFileK = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); + tmpFFTk = FFT_FREQ; + if numel(debugIdx) >= tmpFileK + tmpFFTk = debugIdx{tmpFileK}.FFT_FREQ; + end + if debugmode(tmpFileK) == tmpFFTk && numel(notchData) >= tmpFileK && ~isempty(notchData{tmpFileK}) + % Only overlay on the axis matching gyro_debug_axis + tmpGdaK = 0; + if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileK + tmpGdaK = gyro_debug_axis(tmpFileK); + end + if (c2(p) - 1) == tmpGdaK + maxHzOverlay = (A_lograte(tmpFileK) / 2) * 1000; + PSplotDynNotchOverlay(gca, notchData{tmpFileK}, T{tmpFileK}.setpoint_3_(tIND{tmpFileK}) / 10, size(img, 1), maxHzOverlay, 'throttle'); + end + end + end + + %% RPM filter overlay (motor frequencies + harmonics) + if exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') + tmpFileK2 = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); + tmpRPMk = 46; + if numel(debugIdx) >= tmpFileK2 + tmpRPMk = debugIdx{tmpFileK2}.RPM_FILTER; + end + if debugmode(tmpFileK2) == tmpRPMk && numel(rpmFilterData) >= tmpFileK2 && ~isempty(rpmFilterData{tmpFileK2}) + maxHzOverlay2 = (A_lograte(tmpFileK2) / 2) * 1000; + PSplotRPMOverlay(gca, rpmFilterData{tmpFileK2}, T{tmpFileK2}.setpoint_3_(tIND{tmpFileK2}) / 10, size(img, 1), maxHzOverlay2, 'throttle'); + end + end + + %% Estimated RPM overlay from spectrum peak detection + if get(guiHandlesSpec.checkboxEstRPM, 'Value') && ~isempty(ampmat{p}) && ~isempty(freq{p}) + % find a throttle row that has freq data (not all zeros) + freqAx = []; + for rr = 1:size(freq{p}, 1) + if any(freq{p}(rr,:) > 0), freqAx = freq{p}(rr,:); break; end + end + if ~isempty(freqAx) + [estFund, estHarm] = PSestimateRPM(freqAx, ampmat{p}, 3); + % convert Hz to image pixel coords (img is flipped: row 1 = max Hz) + maxHzEst = (A_lograte(get(guiHandlesSpec.FileSelect{c1(p)}, 'Value')) / 2) * 1000; + hz_per_px = maxHzEst / size(img, 1); + hold on; + estCol = [0 .9 .2; .9 .7 0; .9 .2 0]; % green, orange, red for 1st/2nd/3rd + estStyle = {'-'; '--'; ':'}; + for nh = 1:3 + xPts = []; yPts = []; + for tb = 1:100 + if ~isnan(estHarm(tb, nh)) && estHarm(tb, nh) > 0 && estHarm(tb, nh) < maxHzEst + y_px = size(img, 1) - round(estHarm(tb, nh) / hz_per_px); + if y_px >= 1 && y_px <= size(img, 1) + xPts(end+1) = tb; + yPts(end+1) = y_px; + end + end + end + if ~isempty(xPts) + h = plot(xPts, yPts, estStyle{nh}, 'LineWidth', 1.5); + set(h, 'Color', estCol(nh,:), 'HitTest', 'off'); + end + end + end % ~isempty(freqAx) + end + + catch ME + warning('PSplotSpec render p=%d: %s', p, ME.message); + end + end + end + + % color bar2 at the top + try + delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) + catch + end + if vars(1)>1 % 1=none + subplot('position',posInfo.SpecPos(1,:)); + hCbar1= colorbar('NorthOutside'); + set(hCbar1,'Position', [posInfo.hCbar1pos]); + end + if vars(2)>1 % 1=none + subplot('position',posInfo.SpecPos(4,:)); + hCbar2= colorbar('NorthOutside'); + set(hCbar2,'Position', [posInfo.hCbar2pos]) + end + if vars(3)>1 % 1=none + subplot('position',posInfo.SpecPos(7,:)); + hCbar3= colorbar('NorthOutside'); + set(hCbar3,'Position', [posInfo.hCbar3pos]) + end + if vars(4)>1 % 1=none + subplot('position',posInfo.SpecPos(10,:)); + hCbar4= colorbar('NorthOutside'); + set(hCbar4,'Position', [posInfo.hCbar4pos]) + end + + % color maps - use set() to avoid stale colorbar listener errors in colormap() + try + tmpCmapVal = get(guiHandlesSpec.ColormapSelect, 'Value'); + if tmpCmapVal <= 7 + tmpCmapStr = get(guiHandlesSpec.ColormapSelect, 'String'); + cm = feval(char(tmpCmapStr(tmpCmapVal)), 64); + elseif tmpCmapVal == 8 + cm = linearREDcmap; + else + cm = linearGREYcmap; + end + set(PSspecfig, 'Colormap', cm); + catch, end + +end + +if get(guiHandlesSpec.checkbox2d, 'Value')==1 && ~isempty(amp2d) + figure(PSspecfig); + try + delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) + catch + end + baselineYlines = [0 -50]; + c1=[1 1 1 2 2 2 3 3 3 4 4 4]; + c2=[1 2 3 1 2 3 1 2 3 1 2 3]; + %%%%% plot 2d amp spec + for p=1:size(amp2d,2) + axLabel={'roll';'pitch';'yaw'}; + + delete(subplot('position',posInfo.SpecPos(p,:))); + if ~isempty(amp2d{p}) + h2=subplot('position',posInfo.SpecPos(p,:)); cla + h=plot(freq2d{p}, smooth(amp2d{p}, log10(size(amp2d{p},1)) * (get(guiHandlesSpec.smoothFactor_select, 'Value')^2), 'lowess'));hold on + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2) + set(h2,'fontsize',fontsz,'fontweight','bold') + if get(guiHandlesSpec.specPresets, 'Value') <= 3 + set(h,'Color',[SpecLineCols(c1(p),:,1)]) + end + if get(guiHandlesSpec.specPresets, 'Value') > 4 && get(guiHandlesSpec.specPresets, 'Value') <= 6 + set(h,'Color',[SpecLineCols(c1(p),:,2)]) + end + if get(guiHandlesSpec.specPresets, 'Value') > 6 + set(h,'Color',[SpecLineCols(c1(p),:,3)]) + end + + if get(guiHandlesSpec.Sub100HzCheck{c1(p)}, 'Value')==1 + set(h2,'xtick',[0 20 40 60 80 100], 'yminortick','on') + axis([0 100 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) + h=plot([round(Flim1) round(Flim1)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); + set(h,'linewidth',1) + h=plot([round(Flim2) round(Flim2)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); + set(h,'linewidth',1) + else + set(h2,'xtick',[0 : ((A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 / 5) : (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000],'yminortick','on') + axis([0 (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) + end + + xlabel('Frequency (Hz)') + if get(guiHandlesSpec.checkboxPSD, 'Value') + ylabel(['PSD (dB)']) + else + ylabel(['Amplitude']) + end + + + h=text(2,climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))*.95,axLabel{c2(p)}); + set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold') + + grid on + end + end +end +% Set up click-to-show-value datatips + double-click expand on all axes +PSdatatipSetup(PSspecfig); + +set(PSspecfig, 'pointer', 'arrow') +updateSpec=0; + +end + diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index 9f430c4..4308194 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -1,1151 +1,1151 @@ -%% PSplotStats - script to plot flight statistics - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if ~isempty(filenameA) || ~isempty(filenameB) - set(PSstatsfig, 'pointer', 'watch') - pause(.05) - %% update fonts - -PSstatsfig_pos = get(PSstatsfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); -fontsz5=round(screensz_multiplier*prop_max_screen); - -set(guiHandlesStats.saveFig5, 'FontSize', fontsz5); -set(guiHandlesStats.refresh, 'FontSize', fontsz5); -set(guiHandlesStats.degsecStick, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats, 'FontSize', fontsz5); - -set(guiHandlesStats.crossAxesStats_text, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_input, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_text2, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_input2, 'FontSize', fontsz5); - -%% Histograms - -if get(guiHandlesStats.crossAxesStats, 'Value')==1 - if ~isempty(filenameA) - if ~updateStats - - rcRates=dataA.rates(1,:); - rcExpo=dataA.rates(2,:); - Srates=dataA.rates(3,:); - const=200; - if FirmwareCode_A==INAV, const=1000; end - - RateCurveRoll_A=PSrc2deg([0:5:500],dataA.rates(1,1), dataA.rates(2,1), dataA.rates(3,1), const); - RateCurvePitch_A=PSrc2deg([0:5:500],dataA.rates(1,2), dataA.rates(2,2), dataA.rates(3,2), const); - RateCurveYaw_A=PSrc2deg([0:5:500],dataA.rates(1,3), dataA.rates(2,3), dataA.rates(3,3), const); - - Yscale=round((max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])) / 50) * 50; - - if get(guiHandlesStats.degsecStick, 'Value')==1, - RateCurveRoll_A=(diff(RateCurveRoll_A)); - RateCurvePitch_A=(diff(RateCurvePitch_A)); - RateCurveYaw_A=(diff(RateCurveYaw_A)); - - Yscale=round(max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])); - end - - Rpercent_A=PSPercent(DATtmpA.RCcommand(1,:)); - Ppercent_A=PSPercent(DATtmpA.RCcommand(2,:)); - Ypercent_A=PSPercent(DATtmpA.RCcommand(3,:)); - Tpercent_A=DATtmpA.RCRate(4,:); % already computed for throttle - end - - hhist=subplot('position',posInfo.statsPos(1,:)); - cla - h=histogram(Rpercent_A,'Normalization','probability','BinWidth',1); - y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold') - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) - - hold on - - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_A)-1],RateCurveRoll_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_A(20) RateCurveRoll_A(40) RateCurveRoll_A(60) RateCurveRoll_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(1,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveRoll_A(20),[int2str(RateCurveRoll_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveRoll_A(40),[int2str(RateCurveRoll_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveRoll_A(60),[int2str(RateCurveRoll_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveRoll_A(80),[int2str(RateCurveRoll_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(2,:)); - cla - h=histogram(Ppercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) - y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold') - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_A)-1],RateCurvePitch_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_A(20) RateCurvePitch_A(40) RateCurvePitch_A(60) RateCurvePitch_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(2,:)]); - axis([1 100 0 .1]) - - text(21, RateCurvePitch_A(20),[int2str(RateCurvePitch_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurvePitch_A(40),[int2str(RateCurvePitch_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurvePitch_A(60),[int2str(RateCurvePitch_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurvePitch_A(80),[int2str(RateCurvePitch_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(3,:)); - cla - h=histogram(Ypercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) - y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold') - - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_A)-1],RateCurveYaw_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_A(20) RateCurveYaw_A(40) RateCurveYaw_A(60) RateCurveYaw_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(3,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveYaw_A(20),[int2str(RateCurveYaw_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveYaw_A(40),[int2str(RateCurveYaw_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveYaw_A(60),[int2str(RateCurveYaw_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveYaw_A(80),[int2str(RateCurveYaw_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(4,:)); - cla - h=histogram(Tpercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7); - grid on - y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight', 'color',[colorA],'fontweight','bold') - set(hhist,'ycolor',[colorA],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(4,:)]); - axis([1 100 0 .1]) - end - - if ~isempty(filenameB) - if ~updateStats - rcRates=dataB.rates(1,:); - rcExpo=dataB.rates(2,:); - Srates=dataB.rates(3,:); - const=200; - if FirmwareCode_B==INAV, const=1000; end - - RateCurveRoll_B=PSrc2deg([0:5:500],dataB.rates(1,1), dataB.rates(2,1), dataB.rates(3,1), const); - RateCurvePitch_B=PSrc2deg([0:5:500],dataB.rates(1,2), dataB.rates(2,2), dataB.rates(3,2), const); - RateCurveYaw_B=PSrc2deg([0:5:500],dataB.rates(1,3), dataB.rates(2,3), dataB.rates(3,3), const); - - Yscale=round((max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])) / 50) * 50; - - if get(guiHandlesStats.degsecStick, 'Value')==1, - RateCurveRoll_B=(diff(RateCurveRoll_B)); - RateCurvePitch_B=(diff(RateCurvePitch_B)); - RateCurveYaw_B=(diff(RateCurveYaw_B)); - - Yscale=round(max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])); - end - - Rpercent_B=PSPercent(DATtmpB.RCcommand(1,:)); - Ppercent_B=PSPercent(DATtmpB.RCcommand(2,:)); - Ypercent_B=PSPercent(DATtmpB.RCcommand(3,:)); - Tpercent_B=DATtmpB.RCRate(4,:); % already computed for throttle - end - - hhist=subplot('position',posInfo.statsPos(5,:)); - cla - h=histogram(Rpercent_B,'Normalization','probability','BinWidth',1); - y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold') - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - - hold on - - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_B)-1],RateCurveRoll_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_B(20) RateCurveRoll_B(40) RateCurveRoll_B(60) RateCurveRoll_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(5,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveRoll_B(20),[int2str(RateCurveRoll_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveRoll_B(40),[int2str(RateCurveRoll_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveRoll_B(60),[int2str(RateCurveRoll_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveRoll_B(80),[int2str(RateCurveRoll_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); - - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(6,:)); - cla - h=histogram(Ppercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold'); - - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_B)-1],RateCurvePitch_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_B(20) RateCurvePitch_B(40) RateCurvePitch_B(60) RateCurvePitch_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(6,:)]); - axis([1 100 0 .1]) - - text(21, RateCurvePitch_B(20),[int2str(RateCurvePitch_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurvePitch_B(40),[int2str(RateCurvePitch_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurvePitch_B(60),[int2str(RateCurvePitch_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurvePitch_B(80),[int2str(RateCurvePitch_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - - text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(7,:)); - cla - h=histogram(Ypercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight','fontweight','bold') - - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_B)-1],RateCurveYaw_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_B(20) RateCurveYaw_B(40) RateCurveYaw_B(60) RateCurveYaw_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(7,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveYaw_B(20),[int2str(RateCurveYaw_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveYaw_B(40),[int2str(RateCurveYaw_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveYaw_B(60),[int2str(RateCurveYaw_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveYaw_B(80),[int2str(RateCurveYaw_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(8,:)); - cla - h=histogram(Tpercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - grid on - y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); - ylabel('% of flight', 'color',[colorB],'fontweight','bold') - set(hhist,'ycolor',[colorB],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(8,:)]); - axis([1 100 0 .1]) - end -end - -%% means/standard deviations - -if get(guiHandlesStats.crossAxesStats, 'Value')==2 - - cols=[0.06 0.3 0.54 0.78]; - rows=[0.76 0.53 0.3 0.08]; - k=0; - for c=1:length(cols) - for r=1:length(rows) - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.18 0.16]; - end - end - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - - N=length(DATtmpA.GyroFilt(1,:)); - - % gyro - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'FaceColor',[colorA]);%[ColorSet(11,:)]) - s1=errorbar([2],mean(abs(DATtmpA.GyroFilt(2,:))), std(abs(DATtmpA.GyroFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.GyroFilt(2,:)))); - set(s2,'FaceColor',[colorA]);%,[ColorSet(12,:)]) - s1=errorbar([3],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.GyroFilt(1,:)))); - set(s3,'FaceColor',[colorA]);%,[ColorSet(13,:)]) - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Gyro| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.GyroFilt),2))+max((std(abs(DATtmpA.GyroFilt)')'))); - axis([.5 3.5 0 ymax]) - box off - - % RCRate - h1=subplot('position',posInfo.statsPos2(5,:)); cla - s1=errorbar([1],mean(abs(Rpercent_A)), std(abs(Rpercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(Rpercent_A)));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(Ppercent_A)), std(abs(Ppercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(Ppercent_A))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(Ypercent_A)), std(abs(Ypercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(Ypercent_A))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(abs(Tpercent_A)), std(abs(Tpercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(Tpercent_A)); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('% RPYT [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - axis([0.5 4.5 0 100]) - box off - - % pterm - h1=subplot('position',posInfo.statsPos2(2,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Pterm(1,:))), std(abs(DATtmpA.Pterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Pterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Pterm(2,:))), std(abs(DATtmpA.Pterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Pterm(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.Pterm(3,:))), std(abs(DATtmpA.Pterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Pterm(3,:)))); - set(s3,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Pterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Pterm),2))+max((std(abs(DATtmpA.Pterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % fterm - h1=subplot('position',posInfo.statsPos2(6,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Fterm(1,:))), std(abs(DATtmpA.Fterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Fterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Fterm(2,:))), std(abs(DATtmpA.Fterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Fterm(2,:)))); - set(s2,'FaceColor',[colorA]);% % - s1=errorbar([3],mean(abs(DATtmpA.Fterm(3,:))), std(abs(DATtmpA.Fterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Fterm(3,:)))); - set(s3,'FaceColor',[colorA]); - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Fterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Fterm),2))+max((std(abs(DATtmpA.Fterm)')'))); - if ymax<1, ymax=10, end - axis([.5 3.5 0 ymax]) - box off - - % Iterm - h1=subplot('position',posInfo.statsPos2(3,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Iterm(1,:))), std(abs(DATtmpA.Iterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Iterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Iterm(2,:))), std(abs(DATtmpA.Iterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Iterm(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.Iterm(3,:))), std(abs(DATtmpA.Iterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Iterm(3,:)))); - set(s3,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Iterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Iterm),2))+max((std(abs(DATtmpA.Iterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % dterm - h1=subplot('position',posInfo.statsPos2(7,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.DtermFilt(1,:))), std(abs(DATtmpA.DtermFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.DtermFilt(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.DtermFilt(2,:))), std(abs(DATtmpA.DtermFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.DtermFilt(2,:)))); - set(s2,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Dterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.DtermFilt),2))+max((std(abs(DATtmpA.DtermFilt)')'))); - axis([.5 2.5 0 ymax]) - box off - - h1=subplot('position',posInfo.statsPos2(4,:)); cla - s1=errorbar([1],mean(DATtmpA.Motor12(1,:)), std(DATtmpA.Motor12(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(DATtmpA.Motor12(1,:)));hold on - set(s1,'FaceColor',[colorA]); - s1=errorbar([2],mean(DATtmpA.Motor12(2,:)), std(DATtmpA.Motor12(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(DATtmpA.Motor12(2,:))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(DATtmpA.Motor34(1,:)), std(DATtmpA.Motor34(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(DATtmpA.Motor34(1,:))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(DATtmpA.Motor34(2,:)), std(DATtmpA.Motor34(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(DATtmpA.Motor34(2,:))); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('Motors [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - axis([0.5 4.5 0 100]) - box off - - - h1=subplot('position',posInfo.statsPos2(8,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.debug12(1,:))), std(abs(DATtmpA.debug12(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.debug12(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.debug12(2,:))), std(abs(DATtmpA.debug12(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.debug12(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.debug34(1,:))), std(abs(DATtmpA.debug34(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.debug34(1,:)))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(abs(DATtmpA.debug34(2,:))), std(abs(DATtmpA.debug34(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(abs(DATtmpA.debug34(2,:)))); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Debug| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max([max(mean(abs(DATtmpA.debug12),2)) max(mean(abs(DATtmpA.debug34),2))]) + max([max((std(abs(DATtmpA.debug12)')')) max((std(abs(DATtmpA.debug34)')'))])); - axis([.5 4.5 0 ymax]) - box off - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - - N=length(DATtmpB.GyroFilt(1,:)); - - % gyro - h1=subplot('position',posInfo.statsPos2(9,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'FaceColor',[colorB]);%[ColorSet(11,:)]) - s1=errorbar([2],mean(abs(DATtmpB.GyroFilt(2,:))), std(abs(DATtmpB.GyroFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.GyroFilt(2,:)))); - set(s2,'FaceColor',[colorB]);%,[ColorSet(12,:)]) - s1=errorbar([3],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.GyroFilt(1,:)))); - set(s3,'FaceColor',[colorB]);%,[ColorSet(13,:)]) - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Gyro| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.GyroFilt),2))+max((std(abs(DATtmpB.GyroFilt)')'))); - axis([.5 3.5 0 ymax]) - box off - - % RCRate - h1=subplot('position',posInfo.statsPos2(13,:)); cla - s1=errorbar([1],mean(abs(Rpercent_B)), std(abs(Rpercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(Rpercent_B)));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(Ppercent_B)), std(abs(Ppercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(Ppercent_B))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(Ypercent_B)), std(abs(Ypercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(Ypercent_B))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(abs(Tpercent_B)), std(abs(Tpercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(Tpercent_B)); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('% RPYT [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - axis([0.5 4.5 0 100]) - box off - - % pterm - h1=subplot('position',posInfo.statsPos2(10,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Pterm(1,:))), std(abs(DATtmpB.Pterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Pterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Pterm(2,:))), std(abs(DATtmpB.Pterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Pterm(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.Pterm(3,:))), std(abs(DATtmpB.Pterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Pterm(3,:)))); - set(s3,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Pterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Pterm),2))+max((std(abs(DATtmpB.Pterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % fterm - h1=subplot('position',posInfo.statsPos2(14,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Fterm(1,:))), std(abs(DATtmpB.Fterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Fterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Fterm(2,:))), std(abs(DATtmpB.Fterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Fterm(2,:)))); - set(s2,'FaceColor',[colorB]);% % - s1=errorbar([3],mean(abs(DATtmpB.Fterm(3,:))), std(abs(DATtmpB.Fterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Fterm(3,:)))); - set(s3,'FaceColor',[colorB]); - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Fterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Fterm),2))+max((std(abs(DATtmpB.Fterm)')'))); - if ymax<1, ymax=10, end - axis([.5 3.5 0 ymax]) - box off - - % Iterm - h1=subplot('position',posInfo.statsPos2(11,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Iterm(1,:))), std(abs(DATtmpB.Iterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Iterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Iterm(2,:))), std(abs(DATtmpB.Iterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Iterm(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.Iterm(3,:))), std(abs(DATtmpB.Iterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Iterm(3,:)))); - set(s3,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Iterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Iterm),2))+max((std(abs(DATtmpB.Iterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % dterm - h1=subplot('position',posInfo.statsPos2(15,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.DtermFilt(1,:))), std(abs(DATtmpB.DtermFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.DtermFilt(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.DtermFilt(2,:))), std(abs(DATtmpB.DtermFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.DtermFilt(2,:)))); - set(s2,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Dterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.DtermFilt),2))+max((std(abs(DATtmpB.DtermFilt)')'))); - axis([.5 2.5 0 ymax]) - box off - - h1=subplot('position',posInfo.statsPos2(12,:)); cla - s1=errorbar([1],mean(DATtmpB.Motor12(1,:)), std(DATtmpB.Motor12(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(DATtmpB.Motor12(1,:)));hold on - set(s1,'FaceColor',[colorB]); - s1=errorbar([2],mean(DATtmpB.Motor12(2,:)), std(DATtmpB.Motor12(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(DATtmpB.Motor12(2,:))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(DATtmpB.Motor34(1,:)), std(DATtmpB.Motor34(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(DATtmpB.Motor34(1,:))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(DATtmpB.Motor34(2,:)), std(DATtmpB.Motor34(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(DATtmpB.Motor34(2,:))); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('Motors [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - axis([0.5 4.5 0 100]) - box off - - - h1=subplot('position',posInfo.statsPos2(16,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.debug12(1,:))), std(abs(DATtmpB.debug12(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.debug12(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.debug12(2,:))), std(abs(DATtmpB.debug12(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.debug12(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.debug34(1,:))), std(abs(DATtmpB.debug34(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.debug34(1,:)))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(abs(DATtmpB.debug34(2,:))), std(abs(DATtmpB.debug34(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) - s4=bar([4],mean(abs(DATtmpB.debug34(2,:)))); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Debug| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max([max(mean(abs(DATtmpB.debug12),2)) max(mean(abs(DATtmpB.debug34),2))]) + max([max((std(abs(DATtmpB.debug12)')')) max((std(abs(DATtmpB.debug34)')'))])); - axis([.5 4.5 0 ymax]) - box off - end -end - - -%% Mode 1 topography -if get(guiHandlesStats.crossAxesStats, 'Value')==3 - - cols=[0.06 0.52]; - rows=[0.55 0.08]; - k=0; - for c=1:2 - for r=1:2 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; - end - end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Ypercent_A;Ypercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_B;Ypercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - -end - -%% Mode 2 topography -if get(guiHandlesStats.crossAxesStats, 'Value')==4 - - cols=[0.06 0.52]; - rows=[0.55 0.08]; - k=0; - for c=1:2 - for r=1:2 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; - end - end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Rpercent_A;Rpercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - end - -end -%% each against throttle - -if get(guiHandlesStats.crossAxesStats, 'Value')==5 - - cols=[0.06 0.54]; - rows=[0.69 0.385 0.08]; - k=0; - for c=1:2 - for r=1:3 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.39 0.24]; - end - end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Ppercent_A;Ppercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h5=subplot('position',posInfo.statsPos2(5,:)); cla - s5=surface([Ppercent_B;Ppercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h5,'fontsize',fontsz5); - xlabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s5, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h6=subplot('position',posInfo.statsPos2(6,:)); cla - s6=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h6,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s6, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end -end - - - - -updateStats=0; -set(PSstatsfig, 'pointer', 'arrow') -end - - -function newmap = b2r(cmin_input,cmax_input) -%BLUEWHITERED Blue, white, and red color map. -% this matlab file is designed to draw anomaly figures. the color of -% the colorbar is from blue to white and then to red, corresponding to -% the anomaly values from negative to zero to positive, respectively. -% The color white always correspondes to value zero. -% -% You should input two values like caxis in matlab, that is the min and -% the max value of color values designed. e.g. colormap(b2r(-3,5)) -% -% the brightness of blue and red will change according to your setting, -% so that the brightness of the color corresponded to the color of his -% opposite number -% e.g. colormap(b2r(-3,6)) is from light blue to deep red -% e.g. colormap(b2r(-3,3)) is from deep blue to deep red -% -% I'd advise you to use colorbar first to make sure the caxis' cmax and cmin. -% Besides, there is also another similar colorbar named 'darkb2r', in which the -% color is darker. -% -% by Cunjie Zhang, 2011-3-14 -% find bugs ====> email : daisy19880411@126.com -% updated: Robert Beckman help to fix the bug when start point is zero, 2015-04-08 -% -% Examples: -% ------------------------------ -% figure -% peaks; -% colormap(b2r(-6,8)), colorbar, title('b2r') -% - - -%% check the input -if nargin ~= 2 ; - disp('input error'); - disp('input two variables, the range of caxis , for example : colormap(b2r(-3,3))'); -end - -if cmin_input >= cmax_input - disp('input error'); - disp('the color range must be from a smaller one to a larger one'); -end - -%% control the figure caxis -lims = get(gca, 'CLim'); % get figure caxis formation -caxis([cmin_input cmax_input]); - -%% color configuration : from blue to to white then to red - -red_top = [1 0 0]; -white_middle= [1 1 1]; -blue_bottom = [0 0 1]; - -%% color interpolation - -color_num = 251; -color_input = [blue_bottom; white_middle; red_top]; -oldsteps = linspace(-1, 1, size(color_input,1)); -newsteps = linspace(-1, 1, color_num); - -%% Category Discussion according to the cmin and cmax input - -% the color data will be remaped to color range from -max(abs(cmin_input),cmax_input) -% to max(abs(cmin_input),cmax_input) , and then squeeze the color data -% in order to make sure the blue and red color selected corresponded -% to their math values - -% for example : -% if b2r(-3,6) ,the color range is from light blue to deep red , so that -% the light blue valued at -3 correspondes to light red valued at 3 - - -%% Category Discussion according to the cmin and cmax input -% first : from negative to positive -% then : from positive to positive -% last : from negative to negative - -for j=1:3 - newmap_all(:,j) = min(max(transpose(interp1(oldsteps, color_input(:,j), newsteps)), 0), 1); -end - -if (cmin_input < 0) && (cmax_input > 0) ; - - - if abs(cmin_input) < cmax_input - - % |--------|---------|--------------------| - % -cmax cmin 0 cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - - elseif abs(cmin_input) >= cmax_input - - % |------------------|------|--------------| - % cmin 0 cmax -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); - end - - -elseif cmin_input >= 0 - - if lims(1) < 0 - disp('caution:') - disp('there are still values smaller than 0, but cmin is larger than 0.') - disp('some area will be in red color while it should be in blue color') - end - - % |-----------------|-------|-------------| - % -cmax 0 cmin cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - -elseif cmax_input <= 0 - - if lims(2) > 0 - disp('caution:') - disp('there are still values larger than 0, but cmax is smaller than 0.') - disp('some area will be in blue color while it should be in red color') - end - - % |------------|------|--------------------| - % cmin cmax 0 -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); -end - - - -end - - +%% PSplotStats - script to plot flight statistics + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if ~isempty(filenameA) || ~isempty(filenameB) + set(PSstatsfig, 'pointer', 'watch') + pause(.05) + %% update fonts + +PSstatsfig_pos = get(PSstatsfig, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); +fontsz5=round(screensz_multiplier*prop_max_screen); + +set(guiHandlesStats.saveFig5, 'FontSize', fontsz5); +set(guiHandlesStats.refresh, 'FontSize', fontsz5); +set(guiHandlesStats.degsecStick, 'FontSize', fontsz5); +set(guiHandlesStats.crossAxesStats, 'FontSize', fontsz5); + +set(guiHandlesStats.crossAxesStats_text, 'FontSize', fontsz5); +set(guiHandlesStats.crossAxesStats_input, 'FontSize', fontsz5); +set(guiHandlesStats.crossAxesStats_text2, 'FontSize', fontsz5); +set(guiHandlesStats.crossAxesStats_input2, 'FontSize', fontsz5); + +%% Histograms + +if get(guiHandlesStats.crossAxesStats, 'Value')==1 + if ~isempty(filenameA) + if ~updateStats + + rcRates=dataA.rates(1,:); + rcExpo=dataA.rates(2,:); + Srates=dataA.rates(3,:); + const=200; + if FirmwareCode_A==INAV, const=1000; end + + RateCurveRoll_A=PSrc2deg([0:5:500],dataA.rates(1,1), dataA.rates(2,1), dataA.rates(3,1), const); + RateCurvePitch_A=PSrc2deg([0:5:500],dataA.rates(1,2), dataA.rates(2,2), dataA.rates(3,2), const); + RateCurveYaw_A=PSrc2deg([0:5:500],dataA.rates(1,3), dataA.rates(2,3), dataA.rates(3,3), const); + + Yscale=round((max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])) / 50) * 50; + + if get(guiHandlesStats.degsecStick, 'Value')==1, + RateCurveRoll_A=(diff(RateCurveRoll_A)); + RateCurvePitch_A=(diff(RateCurvePitch_A)); + RateCurveYaw_A=(diff(RateCurveYaw_A)); + + Yscale=round(max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])); + end + + Rpercent_A=PSPercent(DATtmpA.RCcommand(1,:)); + Ppercent_A=PSPercent(DATtmpA.RCcommand(2,:)); + Ypercent_A=PSPercent(DATtmpA.RCcommand(3,:)); + Tpercent_A=DATtmpA.RCRate(4,:); % already computed for throttle + end + + hhist=subplot('position',posInfo.statsPos(1,:)); + cla + h=histogram(Rpercent_A,'Normalization','probability','BinWidth',1); + y=xlabel('% roll','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold') + set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) + + hold on + + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_A)-1],RateCurveRoll_A(1,2:end)); + set(ax(1),'Ycolor',[colorA]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_A(20) RateCurveRoll_A(40) RateCurveRoll_A(60) RateCurveRoll_A(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(1,:)]); + axis([1 100 0 .1]) + + text(21, RateCurveRoll_A(20),[int2str(RateCurveRoll_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurveRoll_A(40),[int2str(RateCurveRoll_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurveRoll_A(60),[int2str(RateCurveRoll_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurveRoll_A(80),[int2str(RateCurveRoll_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(2,:)); + cla + h=histogram(Ppercent_A,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) + y=xlabel('% pitch','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold') + hold on + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_A)-1],RateCurvePitch_A(1,2:end)); + set(ax(1),'Ycolor',[colorA]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_A(20) RateCurvePitch_A(40) RateCurvePitch_A(60) RateCurvePitch_A(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(2,:)]); + axis([1 100 0 .1]) + + text(21, RateCurvePitch_A(20),[int2str(RateCurvePitch_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurvePitch_A(40),[int2str(RateCurvePitch_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurvePitch_A(60),[int2str(RateCurvePitch_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurvePitch_A(80),[int2str(RateCurvePitch_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(3,:)); + cla + h=histogram(Ypercent_A,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) + y=xlabel('% yaw','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold') + + hold on + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_A)-1],RateCurveYaw_A(1,2:end)); + set(ax(1),'Ycolor',[colorA]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_A(20) RateCurveYaw_A(40) RateCurveYaw_A(60) RateCurveYaw_A(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(3,:)]); + axis([1 100 0 .1]) + + text(21, RateCurveYaw_A(20),[int2str(RateCurveYaw_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurveYaw_A(40),[int2str(RateCurveYaw_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurveYaw_A(60),[int2str(RateCurveYaw_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurveYaw_A(80),[int2str(RateCurveYaw_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(4,:)); + cla + h=histogram(Tpercent_A,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7); + grid on + y=xlabel('% throttle','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight', 'color',[colorA],'fontweight','bold') + set(hhist,'ycolor',[colorA],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(4,:)]); + axis([1 100 0 .1]) + end + + if ~isempty(filenameB) + if ~updateStats + rcRates=dataB.rates(1,:); + rcExpo=dataB.rates(2,:); + Srates=dataB.rates(3,:); + const=200; + if FirmwareCode_B==INAV, const=1000; end + + RateCurveRoll_B=PSrc2deg([0:5:500],dataB.rates(1,1), dataB.rates(2,1), dataB.rates(3,1), const); + RateCurvePitch_B=PSrc2deg([0:5:500],dataB.rates(1,2), dataB.rates(2,2), dataB.rates(3,2), const); + RateCurveYaw_B=PSrc2deg([0:5:500],dataB.rates(1,3), dataB.rates(2,3), dataB.rates(3,3), const); + + Yscale=round((max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])) / 50) * 50; + + if get(guiHandlesStats.degsecStick, 'Value')==1, + RateCurveRoll_B=(diff(RateCurveRoll_B)); + RateCurvePitch_B=(diff(RateCurvePitch_B)); + RateCurveYaw_B=(diff(RateCurveYaw_B)); + + Yscale=round(max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])); + end + + Rpercent_B=PSPercent(DATtmpB.RCcommand(1,:)); + Ppercent_B=PSPercent(DATtmpB.RCcommand(2,:)); + Ypercent_B=PSPercent(DATtmpB.RCcommand(3,:)); + Tpercent_B=DATtmpB.RCRate(4,:); % already computed for throttle + end + + hhist=subplot('position',posInfo.statsPos(5,:)); + cla + h=histogram(Rpercent_B,'Normalization','probability','BinWidth',1); + y=xlabel('% roll','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold') + set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) + + hold on + + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_B)-1],RateCurveRoll_B(1,2:end)); + set(ax(1),'Ycolor',[colorB]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_B(20) RateCurveRoll_B(40) RateCurveRoll_B(60) RateCurveRoll_B(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(5,:)]); + axis([1 100 0 .1]) + + text(21, RateCurveRoll_B(20),[int2str(RateCurveRoll_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurveRoll_B(40),[int2str(RateCurveRoll_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurveRoll_B(60),[int2str(RateCurveRoll_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurveRoll_B(80),[int2str(RateCurveRoll_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); + + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(6,:)); + cla + h=histogram(Ppercent_B,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) + y=xlabel('% pitch','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold'); + + hold on + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_B)-1],RateCurvePitch_B(1,2:end)); + set(ax(1),'Ycolor',[colorB]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_B(20) RateCurvePitch_B(40) RateCurvePitch_B(60) RateCurvePitch_B(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(6,:)]); + axis([1 100 0 .1]) + + text(21, RateCurvePitch_B(20),[int2str(RateCurvePitch_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurvePitch_B(40),[int2str(RateCurvePitch_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurvePitch_B(60),[int2str(RateCurvePitch_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurvePitch_B(80),[int2str(RateCurvePitch_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + + text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(7,:)); + cla + h=histogram(Ypercent_B,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) + y=xlabel('% yaw','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight','fontweight','bold') + + hold on + [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_B)-1],RateCurveYaw_B(1,2:end)); + set(ax(1),'Ycolor',[colorB]) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(get(ax(2), 'YLabel'), 'String', 'deg/s'); + if get(guiHandlesStats.degsecStick, 'Value')==1, + set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + end + set(h2,'color',[.5 .5 .5],'LineWidth',1.5) + hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_B(20) RateCurveYaw_B(40) RateCurveYaw_B(60) RateCurveYaw_B(80)],'ko','Parent', ax(2)); + set(h,'markerfacecolor','k') + + set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(7,:)]); + axis([1 100 0 .1]) + + text(21, RateCurveYaw_B(20),[int2str(RateCurveYaw_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(41, RateCurveYaw_B(40),[int2str(RateCurveYaw_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(61, RateCurveYaw_B(60),[int2str(RateCurveYaw_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(81, RateCurveYaw_B(80),[int2str(RateCurveYaw_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); + text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); + text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); + axis([1 100 0 .1]) + grid on + + hhist=subplot('position',posInfo.statsPos(8,:)); + cla + h=histogram(Tpercent_B,'Normalization','probability','BinWidth',1); + set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) + grid on + y=xlabel('% throttle','fontweight','bold'); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + ylabel('% of flight', 'color',[colorB],'fontweight','bold') + set(hhist,'ycolor',[colorB],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(8,:)]); + axis([1 100 0 .1]) + end +end + +%% means/standard deviations + +if get(guiHandlesStats.crossAxesStats, 'Value')==2 + + cols=[0.06 0.3 0.54 0.78]; + rows=[0.76 0.53 0.3 0.08]; + k=0; + for c=1:length(cols) + for r=1:length(rows) + k=k+1; + posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.18 0.16]; + end + end + lineThickness=2; + + if ~isempty(filenameA) + + Rpercent_A=DATtmpA.RCcommand(1,:)/5; + Ppercent_A=DATtmpA.RCcommand(2,:)/5; + Ypercent_A=DATtmpA.RCcommand(3,:)/5; + Tpercent_A=DATtmpA.RCRate(4,:); + + N=length(DATtmpA.GyroFilt(1,:)); + + % gyro + h1=subplot('position',posInfo.statsPos2(1,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.GyroFilt(1,:))));hold on + set(s1,'FaceColor',[colorA]);%[ColorSet(11,:)]) + s1=errorbar([2],mean(abs(DATtmpA.GyroFilt(2,:))), std(abs(DATtmpA.GyroFilt(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.GyroFilt(2,:)))); + set(s2,'FaceColor',[colorA]);%,[ColorSet(12,:)]) + s1=errorbar([3],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpA.GyroFilt(1,:)))); + set(s3,'FaceColor',[colorA]);%,[ColorSet(13,:)]) + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Gyro| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max(mean(abs(DATtmpA.GyroFilt),2))+max((std(abs(DATtmpA.GyroFilt)')'))); + axis([.5 3.5 0 ymax]) + box off + + % RCRate + h1=subplot('position',posInfo.statsPos2(5,:)); cla + s1=errorbar([1],mean(abs(Rpercent_A)), std(abs(Rpercent_A)));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(Rpercent_A)));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(Ppercent_A)), std(abs(Ppercent_A)));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(Ppercent_A))); + set(s2,'FaceColor',[colorA]);% + s1=errorbar([3],mean(abs(Ypercent_A)), std(abs(Ypercent_A)));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(Ypercent_A))); + set(s3,'FaceColor',[colorA]);% + s1=errorbar([4],mean(abs(Tpercent_A)), std(abs(Tpercent_A)));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(Tpercent_A)); + set(s4,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('% RPYT [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + axis([0.5 4.5 0 100]) + box off + + % pterm + h1=subplot('position',posInfo.statsPos2(2,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.Pterm(1,:))), std(abs(DATtmpA.Pterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.Pterm(1,:))));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(DATtmpA.Pterm(2,:))), std(abs(DATtmpA.Pterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.Pterm(2,:)))); + set(s2,'FaceColor',[colorA]);% + s1=errorbar([3],mean(abs(DATtmpA.Pterm(3,:))), std(abs(DATtmpA.Pterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpA.Pterm(3,:)))); + set(s3,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Pterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max(mean(abs(DATtmpA.Pterm),2))+max((std(abs(DATtmpA.Pterm)')'))); + axis([.5 3.5 0 ymax]) + box off + + % fterm + h1=subplot('position',posInfo.statsPos2(6,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.Fterm(1,:))), std(abs(DATtmpA.Fterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.Fterm(1,:))));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(DATtmpA.Fterm(2,:))), std(abs(DATtmpA.Fterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.Fterm(2,:)))); + set(s2,'FaceColor',[colorA]);% % + s1=errorbar([3],mean(abs(DATtmpA.Fterm(3,:))), std(abs(DATtmpA.Fterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpA.Fterm(3,:)))); + set(s3,'FaceColor',[colorA]); + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Fterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max(mean(abs(DATtmpA.Fterm),2))+max((std(abs(DATtmpA.Fterm)')'))); + if ymax<1, ymax=10, end + axis([.5 3.5 0 ymax]) + box off + + % Iterm + h1=subplot('position',posInfo.statsPos2(3,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.Iterm(1,:))), std(abs(DATtmpA.Iterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.Iterm(1,:))));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(DATtmpA.Iterm(2,:))), std(abs(DATtmpA.Iterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.Iterm(2,:)))); + set(s2,'FaceColor',[colorA]);% + s1=errorbar([3],mean(abs(DATtmpA.Iterm(3,:))), std(abs(DATtmpA.Iterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpA.Iterm(3,:)))); + set(s3,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Iterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max(mean(abs(DATtmpA.Iterm),2))+max((std(abs(DATtmpA.Iterm)')'))); + axis([.5 3.5 0 ymax]) + box off + + % dterm + h1=subplot('position',posInfo.statsPos2(7,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.DtermFilt(1,:))), std(abs(DATtmpA.DtermFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.DtermFilt(1,:))));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(DATtmpA.DtermFilt(2,:))), std(abs(DATtmpA.DtermFilt(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.DtermFilt(2,:)))); + set(s2,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Dterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max(mean(abs(DATtmpA.DtermFilt),2))+max((std(abs(DATtmpA.DtermFilt)')'))); + axis([.5 2.5 0 ymax]) + box off + + h1=subplot('position',posInfo.statsPos2(4,:)); cla + s1=errorbar([1],mean(DATtmpA.Motor12(1,:)), std(DATtmpA.Motor12(1,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(DATtmpA.Motor12(1,:)));hold on + set(s1,'FaceColor',[colorA]); + s1=errorbar([2],mean(DATtmpA.Motor12(2,:)), std(DATtmpA.Motor12(2,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(DATtmpA.Motor12(2,:))); + set(s2,'FaceColor',[colorA]);% + s1=errorbar([3],mean(DATtmpA.Motor34(1,:)), std(DATtmpA.Motor34(1,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(DATtmpA.Motor34(1,:))); + set(s3,'FaceColor',[colorA]);% + s1=errorbar([4],mean(DATtmpA.Motor34(2,:)), std(DATtmpA.Motor34(2,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(DATtmpA.Motor34(2,:))); + set(s4,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('Motors [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + axis([0.5 4.5 0 100]) + box off + + + h1=subplot('position',posInfo.statsPos2(8,:)); cla + s1=errorbar([1],mean(abs(DATtmpA.debug12(1,:))), std(abs(DATtmpA.debug12(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpA.debug12(1,:))));hold on + set(s1,'FaceColor',[colorA]);% + s1=errorbar([2],mean(abs(DATtmpA.debug12(2,:))), std(abs(DATtmpA.debug12(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpA.debug12(2,:)))); + set(s2,'FaceColor',[colorA]);% + s1=errorbar([3],mean(abs(DATtmpA.debug34(1,:))), std(abs(DATtmpA.debug34(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpA.debug34(1,:)))); + set(s3,'FaceColor',[colorA]);% + s1=errorbar([4],mean(abs(DATtmpA.debug34(2,:))), std(abs(DATtmpA.debug34(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(abs(DATtmpA.debug34(2,:)))); + set(s4,'FaceColor',[colorA]);% + set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Debug| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); + ymax=ceil(max([max(mean(abs(DATtmpA.debug12),2)) max(mean(abs(DATtmpA.debug34),2))]) + max([max((std(abs(DATtmpA.debug12)')')) max((std(abs(DATtmpA.debug34)')'))])); + axis([.5 4.5 0 ymax]) + box off + end + + + if ~isempty(filenameB) + + Rpercent_B=DATtmpB.RCcommand(1,:)/5; + Ppercent_B=DATtmpB.RCcommand(2,:)/5; + Ypercent_B=DATtmpB.RCcommand(3,:)/5; + Tpercent_B=DATtmpB.RCRate(4,:); + + N=length(DATtmpB.GyroFilt(1,:)); + + % gyro + h1=subplot('position',posInfo.statsPos2(9,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.GyroFilt(1,:))));hold on + set(s1,'FaceColor',[colorB]);%[ColorSet(11,:)]) + s1=errorbar([2],mean(abs(DATtmpB.GyroFilt(2,:))), std(abs(DATtmpB.GyroFilt(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.GyroFilt(2,:)))); + set(s2,'FaceColor',[colorB]);%,[ColorSet(12,:)]) + s1=errorbar([3],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpB.GyroFilt(1,:)))); + set(s3,'FaceColor',[colorB]);%,[ColorSet(13,:)]) + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Gyro| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max(mean(abs(DATtmpB.GyroFilt),2))+max((std(abs(DATtmpB.GyroFilt)')'))); + axis([.5 3.5 0 ymax]) + box off + + % RCRate + h1=subplot('position',posInfo.statsPos2(13,:)); cla + s1=errorbar([1],mean(abs(Rpercent_B)), std(abs(Rpercent_B)));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(Rpercent_B)));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(Ppercent_B)), std(abs(Ppercent_B)));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(Ppercent_B))); + set(s2,'FaceColor',[colorB]);% + s1=errorbar([3],mean(abs(Ypercent_B)), std(abs(Ypercent_B)));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(Ypercent_B))); + set(s3,'FaceColor',[colorB]);% + s1=errorbar([4],mean(abs(Tpercent_B)), std(abs(Tpercent_B)));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(Tpercent_B)); + set(s4,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('% RPYT [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + axis([0.5 4.5 0 100]) + box off + + % pterm + h1=subplot('position',posInfo.statsPos2(10,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.Pterm(1,:))), std(abs(DATtmpB.Pterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.Pterm(1,:))));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(DATtmpB.Pterm(2,:))), std(abs(DATtmpB.Pterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.Pterm(2,:)))); + set(s2,'FaceColor',[colorB]);% + s1=errorbar([3],mean(abs(DATtmpB.Pterm(3,:))), std(abs(DATtmpB.Pterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpB.Pterm(3,:)))); + set(s3,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Pterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max(mean(abs(DATtmpB.Pterm),2))+max((std(abs(DATtmpB.Pterm)')'))); + axis([.5 3.5 0 ymax]) + box off + + % fterm + h1=subplot('position',posInfo.statsPos2(14,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.Fterm(1,:))), std(abs(DATtmpB.Fterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.Fterm(1,:))));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(DATtmpB.Fterm(2,:))), std(abs(DATtmpB.Fterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.Fterm(2,:)))); + set(s2,'FaceColor',[colorB]);% % + s1=errorbar([3],mean(abs(DATtmpB.Fterm(3,:))), std(abs(DATtmpB.Fterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpB.Fterm(3,:)))); + set(s3,'FaceColor',[colorB]); + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Fterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max(mean(abs(DATtmpB.Fterm),2))+max((std(abs(DATtmpB.Fterm)')'))); + if ymax<1, ymax=10, end + axis([.5 3.5 0 ymax]) + box off + + % Iterm + h1=subplot('position',posInfo.statsPos2(11,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.Iterm(1,:))), std(abs(DATtmpB.Iterm(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.Iterm(1,:))));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(DATtmpB.Iterm(2,:))), std(abs(DATtmpB.Iterm(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.Iterm(2,:)))); + set(s2,'FaceColor',[colorB]);% + s1=errorbar([3],mean(abs(DATtmpB.Iterm(3,:))), std(abs(DATtmpB.Iterm(3,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpB.Iterm(3,:)))); + set(s3,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Iterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max(mean(abs(DATtmpB.Iterm),2))+max((std(abs(DATtmpB.Iterm)')'))); + axis([.5 3.5 0 ymax]) + box off + + % dterm + h1=subplot('position',posInfo.statsPos2(15,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.DtermFilt(1,:))), std(abs(DATtmpB.DtermFilt(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.DtermFilt(1,:))));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(DATtmpB.DtermFilt(2,:))), std(abs(DATtmpB.DtermFilt(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.DtermFilt(2,:)))); + set(s2,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Dterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max(mean(abs(DATtmpB.DtermFilt),2))+max((std(abs(DATtmpB.DtermFilt)')'))); + axis([.5 2.5 0 ymax]) + box off + + h1=subplot('position',posInfo.statsPos2(12,:)); cla + s1=errorbar([1],mean(DATtmpB.Motor12(1,:)), std(DATtmpB.Motor12(1,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(DATtmpB.Motor12(1,:)));hold on + set(s1,'FaceColor',[colorB]); + s1=errorbar([2],mean(DATtmpB.Motor12(2,:)), std(DATtmpB.Motor12(2,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(DATtmpB.Motor12(2,:))); + set(s2,'FaceColor',[colorB]);% + s1=errorbar([3],mean(DATtmpB.Motor34(1,:)), std(DATtmpB.Motor34(1,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(DATtmpB.Motor34(1,:))); + set(s3,'FaceColor',[colorB]);% + s1=errorbar([4],mean(DATtmpB.Motor34(2,:)), std(DATtmpB.Motor34(2,:)));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(DATtmpB.Motor34(2,:))); + set(s4,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('Motors [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + axis([0.5 4.5 0 100]) + box off + + + h1=subplot('position',posInfo.statsPos2(16,:)); cla + s1=errorbar([1],mean(abs(DATtmpB.debug12(1,:))), std(abs(DATtmpB.debug12(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s1=bar([1],mean(abs(DATtmpB.debug12(1,:))));hold on + set(s1,'FaceColor',[colorB]);% + s1=errorbar([2],mean(abs(DATtmpB.debug12(2,:))), std(abs(DATtmpB.debug12(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s2=bar([2],mean(abs(DATtmpB.debug12(2,:)))); + set(s2,'FaceColor',[colorB]);% + s1=errorbar([3],mean(abs(DATtmpB.debug34(1,:))), std(abs(DATtmpB.debug34(1,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s3=bar([3],mean(abs(DATtmpB.debug34(1,:)))); + set(s3,'FaceColor',[colorB]);% + s1=errorbar([4],mean(abs(DATtmpB.debug34(2,:))), std(abs(DATtmpB.debug34(2,:))));hold on + set(s1,'color','k','linewidth',lineThickness) + s4=bar([4],mean(abs(DATtmpB.debug34(2,:)))); + set(s4,'FaceColor',[colorB]);% + set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') + set(h1,'fontsize',fontsz5); + xlabel('|Debug| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); + ymax=ceil(max([max(mean(abs(DATtmpB.debug12),2)) max(mean(abs(DATtmpB.debug34),2))]) + max([max((std(abs(DATtmpB.debug12)')')) max((std(abs(DATtmpB.debug34)')'))])); + axis([.5 4.5 0 ymax]) + box off + end +end + + +%% Mode 1 topography +if get(guiHandlesStats.crossAxesStats, 'Value')==3 + + cols=[0.06 0.52]; + rows=[0.55 0.08]; + k=0; + for c=1:2 + for r=1:2 + k=k+1; + posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; + end + end + + + lineThickness=2; + + if ~isempty(filenameA) + + Rpercent_A=DATtmpA.RCcommand(1,:)/5; + Ppercent_A=DATtmpA.RCcommand(2,:)/5; + Ypercent_A=DATtmpA.RCcommand(3,:)/5; + Tpercent_A=DATtmpA.RCRate(4,:); + Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; + col=Tacceleration_A; + + cmap_crossaxisStat=b2r(-1, 1); + + h1=subplot('position',posInfo.statsPos2(1,:)); cla + s1=surface([Ypercent_A;Ypercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) + set(h1,'fontsize',fontsz5); + xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 -100 100]) + set(s1, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[-100 100],':k'); + plot([-100 100],[0 0],':k'); + + h2=subplot('position',posInfo.statsPos2(2,:)); cla + s2=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h2,'fontsize',fontsz5); + xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s2, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + end + + + if ~isempty(filenameB) + + Rpercent_B=DATtmpB.RCcommand(1,:)/5; + Ppercent_B=DATtmpB.RCcommand(2,:)/5; + Ypercent_B=DATtmpB.RCcommand(3,:)/5; + Tpercent_B=DATtmpB.RCRate(4,:); + Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; + col=Tacceleration_B; + + h3=subplot('position',posInfo.statsPos2(3,:)); cla + s3=surface([Ypercent_B;Ypercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) + set(h3,'fontsize',fontsz5); + xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 -100 100]) + set(s3, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[-100 100],':k'); + plot([-100 100],[0 0],':k'); + + h4=subplot('position',posInfo.statsPos2(4,:)); cla + s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h4,'fontsize',fontsz5); + xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s4, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + end + +end + +%% Mode 2 topography +if get(guiHandlesStats.crossAxesStats, 'Value')==4 + + cols=[0.06 0.52]; + rows=[0.55 0.08]; + k=0; + for c=1:2 + for r=1:2 + k=k+1; + posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; + end + end + + + lineThickness=2; + + if ~isempty(filenameA) + + Rpercent_A=DATtmpA.RCcommand(1,:)/5; + Ppercent_A=DATtmpA.RCcommand(2,:)/5; + Ypercent_A=DATtmpA.RCcommand(3,:)/5; + Tpercent_A=DATtmpA.RCRate(4,:); + Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; + col=Tacceleration_A; + + cmap_crossaxisStat=b2r(-1, 1); + + h1=subplot('position',posInfo.statsPos2(1,:)); cla + s1=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h1,'fontsize',fontsz5); + xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s1, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h2=subplot('position',posInfo.statsPos2(2,:)); cla + s2=surface([Rpercent_A;Rpercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) + set(h2,'fontsize',fontsz5); + xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 -100 100]) + set(s2, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[-100 100],':k'); + plot([-100 100],[0 0],':k'); + end + + + if ~isempty(filenameB) + + Rpercent_B=DATtmpB.RCcommand(1,:)/5; + Ppercent_B=DATtmpB.RCcommand(2,:)/5; + Ypercent_B=DATtmpB.RCcommand(3,:)/5; + Tpercent_B=DATtmpB.RCRate(4,:); + Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; + col=Tacceleration_B; + + h3=subplot('position',posInfo.statsPos2(3,:)); cla + s3=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h3,'fontsize',fontsz5); + xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s3, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h4=subplot('position',posInfo.statsPos2(4,:)); cla + s4=surface([Rpercent_B;Rpercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) + set(h4,'fontsize',fontsz5); + xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 -100 100]) + set(s4, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[-100 100],':k'); + plot([-100 100],[0 0],':k'); + end + +end +%% each against throttle + +if get(guiHandlesStats.crossAxesStats, 'Value')==5 + + cols=[0.06 0.54]; + rows=[0.69 0.385 0.08]; + k=0; + for c=1:2 + for r=1:3 + k=k+1; + posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.39 0.24]; + end + end + + + lineThickness=2; + + if ~isempty(filenameA) + + Rpercent_A=DATtmpA.RCcommand(1,:)/5; + Ppercent_A=DATtmpA.RCcommand(2,:)/5; + Ypercent_A=DATtmpA.RCcommand(3,:)/5; + Tpercent_A=DATtmpA.RCRate(4,:); + Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; + col=Tacceleration_A; + + cmap_crossaxisStat=b2r(-1, 1); + + h1=subplot('position',posInfo.statsPos2(1,:)); cla + s1=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h1,'fontsize',fontsz5); + xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s1, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h2=subplot('position',posInfo.statsPos2(2,:)); cla + s2=surface([Ppercent_A;Ppercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h2,'fontsize',fontsz5); + xlabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s2, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h3=subplot('position',posInfo.statsPos2(3,:)); cla + s3=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h3,'fontsize',fontsz5); + xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s3, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + end + + + if ~isempty(filenameB) + + Rpercent_B=DATtmpB.RCcommand(1,:)/5; + Ppercent_B=DATtmpB.RCcommand(2,:)/5; + Ypercent_B=DATtmpB.RCcommand(3,:)/5; + Tpercent_B=DATtmpB.RCRate(4,:); + Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; + col=Tacceleration_B; + + h4=subplot('position',posInfo.statsPos2(4,:)); cla + s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h4,'fontsize',fontsz5); + xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s4, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h5=subplot('position',posInfo.statsPos2(5,:)); cla + s5=surface([Ppercent_B;Ppercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h5,'fontsize',fontsz5); + xlabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s5, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + + h6=subplot('position',posInfo.statsPos2(6,:)); cla + s6=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); + colormap(cmap_crossaxisStat); + set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) + set(h6,'fontsize',fontsz5); + xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); + ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); + axis([-100 100 0 100]) + set(s6, 'EdgeAlpha',zTransparency); + hold on; plot([0 0],[0 100],':k'); + plot([-100 100],[50 50],':k'); + end +end + + + + +updateStats=0; +set(PSstatsfig, 'pointer', 'arrow') +end + + +function newmap = b2r(cmin_input,cmax_input) +%BLUEWHITERED Blue, white, and red color map. +% this matlab file is designed to draw anomaly figures. the color of +% the colorbar is from blue to white and then to red, corresponding to +% the anomaly values from negative to zero to positive, respectively. +% The color white always correspondes to value zero. +% +% You should input two values like caxis in matlab, that is the min and +% the max value of color values designed. e.g. colormap(b2r(-3,5)) +% +% the brightness of blue and red will change according to your setting, +% so that the brightness of the color corresponded to the color of his +% opposite number +% e.g. colormap(b2r(-3,6)) is from light blue to deep red +% e.g. colormap(b2r(-3,3)) is from deep blue to deep red +% +% I'd advise you to use colorbar first to make sure the caxis' cmax and cmin. +% Besides, there is also another similar colorbar named 'darkb2r', in which the +% color is darker. +% +% by Cunjie Zhang, 2011-3-14 +% find bugs ====> email : daisy19880411@126.com +% updated: Robert Beckman help to fix the bug when start point is zero, 2015-04-08 +% +% Examples: +% ------------------------------ +% figure +% peaks; +% colormap(b2r(-6,8)), colorbar, title('b2r') +% + + +%% check the input +if nargin ~= 2 ; + disp('input error'); + disp('input two variables, the range of caxis , for example : colormap(b2r(-3,3))'); +end + +if cmin_input >= cmax_input + disp('input error'); + disp('the color range must be from a smaller one to a larger one'); +end + +%% control the figure caxis +lims = get(gca, 'CLim'); % get figure caxis formation +caxis([cmin_input cmax_input]); + +%% color configuration : from blue to to white then to red + +red_top = [1 0 0]; +white_middle= [1 1 1]; +blue_bottom = [0 0 1]; + +%% color interpolation + +color_num = 251; +color_input = [blue_bottom; white_middle; red_top]; +oldsteps = linspace(-1, 1, size(color_input,1)); +newsteps = linspace(-1, 1, color_num); + +%% Category Discussion according to the cmin and cmax input + +% the color data will be remaped to color range from -max(abs(cmin_input),cmax_input) +% to max(abs(cmin_input),cmax_input) , and then squeeze the color data +% in order to make sure the blue and red color selected corresponded +% to their math values + +% for example : +% if b2r(-3,6) ,the color range is from light blue to deep red , so that +% the light blue valued at -3 correspondes to light red valued at 3 + + +%% Category Discussion according to the cmin and cmax input +% first : from negative to positive +% then : from positive to positive +% last : from negative to negative + +for j=1:3 + newmap_all(:,j) = min(max(transpose(interp1(oldsteps, color_input(:,j), newsteps)), 0), 1); +end + +if (cmin_input < 0) && (cmax_input > 0) ; + + + if abs(cmin_input) < cmax_input + + % |--------|---------|--------------------| + % -cmax cmin 0 cmax [cmin,cmax] + + start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); + newmap = squeeze(newmap_all(start_point:color_num,:)); + + elseif abs(cmin_input) >= cmax_input + + % |------------------|------|--------------| + % cmin 0 cmax -cmin [cmin,cmax] + + end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); + newmap = squeeze(newmap_all(1:end_point,:)); + end + + +elseif cmin_input >= 0 + + if lims(1) < 0 + disp('caution:') + disp('there are still values smaller than 0, but cmin is larger than 0.') + disp('some area will be in red color while it should be in blue color') + end + + % |-----------------|-------|-------------| + % -cmax 0 cmin cmax [cmin,cmax] + + start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); + newmap = squeeze(newmap_all(start_point:color_num,:)); + +elseif cmax_input <= 0 + + if lims(2) > 0 + disp('caution:') + disp('there are still values larger than 0, but cmax is smaller than 0.') + disp('some area will be in blue color while it should be in red color') + end + + % |------------|------|--------------------| + % cmin cmax 0 -cmin [cmin,cmax] + + end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); + newmap = squeeze(newmap_all(1:end_point,:)); +end + + + +end + + diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 51fa1ed..025ba91 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -1,226 +1,226 @@ -%% PStuningParams - scripts for plotting tune-related parameters - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -PStunefig=figure(4); - -PStunefig_pos = get(PStunefig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PStunefig_pos(3) > 10, PStunefig_pos(3:4) = PStunefig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PStunefig_pos(3) PStunefig_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); - -f = fields(guiHandlesTune); -for i = 1 : size(f,1) - try set(guiHandlesTune.(f{i}), 'FontSize', fontsz); catch, end -end - - -%% step resp computed directly from set point and gyro -ylab={'Roll';'Pitch';'Yaw'}; -ylab2={'roll';'pitch';'yaw'}; - -axesOptions = find([get(guiHandlesTune.plotR, 'Value') get(guiHandlesTune.plotP, 'Value') get(guiHandlesTune.plotY, 'Value')]); -lineStyle = {'-' ; '--' ; ':'}; -lnLabels = {'solid' ; 'dashed'; 'dotted'}; -p = {' P, I, D, Dm, F'; ' P, I, D, Dm, F'; ' P, I, D, cD'}; -pidlabels = p{get(guiHandles.Firmware, 'Value')}; - -%%%%%%%%%%%%% step resp %%%%%%%%%%%%% -figure(PStunefig) - -ymax = str2num(get(guiHandlesTune.maxYStepInput, 'String')); -ypos = [(ymax/3)*2.9 (ymax/3)*1.85 (ymax/3)*.8]; -hwarn=[]; -if ~get(guiHandlesTune.clearPlots, 'Value') - cnt = 0; - set(PStunefig, 'pointer', 'watch') - pause(.05); - - for f = get(guiHandlesTune.fileListWindowStep, 'Value') - fcntSR = fcntSR + 1; - if fcntSR <= 10 - cnt2 = 0; - for p = axesOptions - cnt = cnt + 1; - cnt2 = cnt2 + 1; - try - if ~updateStep - clear H G L - eval(['H = T{f}.setpoint_' int2str(p-1) '_(tIND{f});']) - eval(['G = T{f}.gyroADC_' int2str(p-1) '_(tIND{f});']) - [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value')); - % xcorrLag(p) = finddelay(H, G) * A_lograte(f); - end - catch - stepresp_A{p}=[]; - end - - if get(guiHandlesTune.RPYcombo, 'Value') == 0 - h1=subplot('position',posInfo.TparamsPos(p,:)); - hold on - - if size(stepresp_A{p},1)>1 - s = []; - s = stepresp_A{p}; - m=nanmean(s); - - h1=plot(tA,m); - set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5); - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; - peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% - peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); - - eval(['PID=' ylab2{p} 'PIDF{f};']) - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); - set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') - set(h,'fontsize',fontsz) - else - peakresp(p, fcntSR) = nan; - peaktime(p, fcntSR) = nan; - latencyHalfHeight(p, fcntSR) = nan; - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') insufficient data']); - set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') - end - - set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); - - box off - if cnt <= 3, h=ylabel([ylab{p} ' Response '], 'fontweight','bold'); end - - xlabel('Time (ms)', 'fontweight','bold'); - - if p==1, title('Step Response Functions');end - h=plot([0 500],[1 1],'k--'); - set(h,'linewidth',.5) - axis([0 500 0 ymax]) - grid on - - - h2=subplot('position',posInfo.TparamsPos(p+6,:)); - h=plot(fcntSR, peakresp(p, fcntSR),'sk'); - set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) - set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) - ylabel([ylab{p} ' Peak '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); - hold on - grid on - plot([0 10],[1 1],'--k') - - h3=subplot('position',posInfo.TparamsPos(p+9,:)); - h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); - set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) - - mn = min(latencyHalfHeight(p, :))-rem(min(latencyHalfHeight(p, :)),2); - mx = max(latencyHalfHeight(p, :))+rem(max(latencyHalfHeight(p, :)),2); - - ymaxLat = mx+4; - yminLat = mn-4; - try - set(gca,'fontsize',fontsz,'ylim',[yminLat ymaxLat],'ytick',[yminLat:2:ymaxLat], 'xtick',[1:fcntSR],'xlim',[0.5 fcntSR+0.5]) - catch - end - ylabel([ylab{p} ' Latency (ms) '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); - hold on - grid on - - - end - - - if get(guiHandlesTune.RPYcombo, 'Value') == 1 - h1=subplot('position',[0.0500 0.1 0.72 0.84]) - hold on - - if size(stepresp_A{p},1)>1 - s = []; - s = stepresp_A{p}; - m=nanmean(s); - - h1=plot(tA,m); - set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5, 'linestyle', lineStyle{cnt2}); - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; - peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% - peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); - - eval(['PID=' ylab2{p} 'PIDF{f};']) - if cnt <= 3 - if size(axesOptions,2) < 2 - h=text(505, ypos(p)+0.04, [ylab{p}]); - else - h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); - end - - set(h,'fontsize',fontsz,'fontweight','bold'); - h=text(505, ypos(p), [pidlabels]); - set(h,'fontsize',fontsz,'fontweight','bold'); - end - h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); - set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') - set(h,'fontsize',fontsz) - else - peakresp(p, fcntSR) = nan; - peaktime(p, fcntSR) = nan; - latencyHalfHeight(p, fcntSR) = nan; - if cnt <= 3 - if size(axesOptions,2) < 2 - h=text(505, ypos(p)+0.04, [ylab{p}]); - else - h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); - end - - set(h,'fontsize',fontsz,'fontweight','bold'); - h=text(505, ypos(p), [pidlabels]); - set(h,'fontsize',fontsz,'fontweight','bold'); - end - h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') insufficient data']); - set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') - end - - set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); - - box off - if cnt <= 3, h=ylabel(['Response '], 'fontweight','bold'); end - - xlabel('Time (ms)', 'fontweight','bold'); - - title('Step Response Functions'); - h=plot([0 500],[1 1],'k--'); - set(h,'linewidth',.5) - axis([0 500 0 ymax]) - grid on - end - end - - elseif fcntSR == 11 - warndlg('10 files maximum. Click reset.'); - end - end - set(PStunefig, 'pointer', 'arrow') - - updateStep=0; -else - for p = 1 : 3 - delete(subplot('position',posInfo.TparamsPos(p,:))) - delete(subplot('position',posInfo.TparamsPos(p+3,:))) - delete(subplot('position',posInfo.TparamsPos(p+6,:))) - delete(subplot('position',posInfo.TparamsPos(p+9,:))) - peaktime = []; - peakresp = []; - latencyHalfHeight = []; - latencyHalfHeight_std = []; - peakresp_std = []; - end -end - - - - - +%% PStuningParams - scripts for plotting tune-related parameters + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +PStunefig=figure(4); + +PStunefig_pos = get(PStunefig, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PStunefig_pos(3) > 10, PStunefig_pos(3:4) = PStunefig_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PStunefig_pos(3) PStunefig_pos(4)])); +fontsz=(screensz_multiplier*prop_max_screen); + +f = fields(guiHandlesTune); +for i = 1 : size(f,1) + try set(guiHandlesTune.(f{i}), 'FontSize', fontsz); catch, end +end + + +%% step resp computed directly from set point and gyro +ylab={'Roll';'Pitch';'Yaw'}; +ylab2={'roll';'pitch';'yaw'}; + +axesOptions = find([get(guiHandlesTune.plotR, 'Value') get(guiHandlesTune.plotP, 'Value') get(guiHandlesTune.plotY, 'Value')]); +lineStyle = {'-' ; '--' ; ':'}; +lnLabels = {'solid' ; 'dashed'; 'dotted'}; +p = {' P, I, D, Dm, F'; ' P, I, D, Dm, F'; ' P, I, D, cD'}; +pidlabels = p{get(guiHandles.Firmware, 'Value')}; + +%%%%%%%%%%%%% step resp %%%%%%%%%%%%% +figure(PStunefig) + +ymax = str2num(get(guiHandlesTune.maxYStepInput, 'String')); +ypos = [(ymax/3)*2.9 (ymax/3)*1.85 (ymax/3)*.8]; +hwarn=[]; +if ~get(guiHandlesTune.clearPlots, 'Value') + cnt = 0; + set(PStunefig, 'pointer', 'watch') + pause(.05); + + for f = get(guiHandlesTune.fileListWindowStep, 'Value') + fcntSR = fcntSR + 1; + if fcntSR <= 10 + cnt2 = 0; + for p = axesOptions + cnt = cnt + 1; + cnt2 = cnt2 + 1; + try + if ~updateStep + clear H G L + eval(['H = T{f}.setpoint_' int2str(p-1) '_(tIND{f});']) + eval(['G = T{f}.gyroADC_' int2str(p-1) '_(tIND{f});']) + [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value')); + % xcorrLag(p) = finddelay(H, G) * A_lograte(f); + end + catch + stepresp_A{p}=[]; + end + + if get(guiHandlesTune.RPYcombo, 'Value') == 0 + h1=subplot('position',posInfo.TparamsPos(p,:)); + hold on + + if size(stepresp_A{p},1)>1 + s = []; + s = stepresp_A{p}; + m=nanmean(s); + + h1=plot(tA,m); + set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5); + latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% + peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); + + eval(['PID=' ylab2{p} 'PIDF{f};']) + if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end + h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); + set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') + set(h,'fontsize',fontsz) + else + peakresp(p, fcntSR) = nan; + peaktime(p, fcntSR) = nan; + latencyHalfHeight(p, fcntSR) = nan; + if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end + h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') insufficient data']); + set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') + end + + set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); + + box off + if cnt <= 3, h=ylabel([ylab{p} ' Response '], 'fontweight','bold'); end + + xlabel('Time (ms)', 'fontweight','bold'); + + if p==1, title('Step Response Functions');end + h=plot([0 500],[1 1],'k--'); + set(h,'linewidth',.5) + axis([0 500 0 ymax]) + grid on + + + h2=subplot('position',posInfo.TparamsPos(p+6,:)); + h=plot(fcntSR, peakresp(p, fcntSR),'sk'); + set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) + set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) + ylabel([ylab{p} ' Peak '], 'fontweight','bold'); + xlabel('Test', 'fontweight','bold'); + hold on + grid on + plot([0 10],[1 1],'--k') + + h3=subplot('position',posInfo.TparamsPos(p+9,:)); + h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); + set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) + + mn = min(latencyHalfHeight(p, :))-rem(min(latencyHalfHeight(p, :)),2); + mx = max(latencyHalfHeight(p, :))+rem(max(latencyHalfHeight(p, :)),2); + + ymaxLat = mx+4; + yminLat = mn-4; + try + set(gca,'fontsize',fontsz,'ylim',[yminLat ymaxLat],'ytick',[yminLat:2:ymaxLat], 'xtick',[1:fcntSR],'xlim',[0.5 fcntSR+0.5]) + catch + end + ylabel([ylab{p} ' Latency (ms) '], 'fontweight','bold'); + xlabel('Test', 'fontweight','bold'); + hold on + grid on + + + end + + + if get(guiHandlesTune.RPYcombo, 'Value') == 1 + h1=subplot('position',[0.0500 0.1 0.72 0.84]) + hold on + + if size(stepresp_A{p},1)>1 + s = []; + s = stepresp_A{p}; + m=nanmean(s); + + h1=plot(tA,m); + set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5, 'linestyle', lineStyle{cnt2}); + latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% + peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); + + eval(['PID=' ylab2{p} 'PIDF{f};']) + if cnt <= 3 + if size(axesOptions,2) < 2 + h=text(505, ypos(p)+0.04, [ylab{p}]); + else + h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); + end + + set(h,'fontsize',fontsz,'fontweight','bold'); + h=text(505, ypos(p), [pidlabels]); + set(h,'fontsize',fontsz,'fontweight','bold'); + end + h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); + set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') + set(h,'fontsize',fontsz) + else + peakresp(p, fcntSR) = nan; + peaktime(p, fcntSR) = nan; + latencyHalfHeight(p, fcntSR) = nan; + if cnt <= 3 + if size(axesOptions,2) < 2 + h=text(505, ypos(p)+0.04, [ylab{p}]); + else + h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); + end + + set(h,'fontsize',fontsz,'fontweight','bold'); + h=text(505, ypos(p), [pidlabels]); + set(h,'fontsize',fontsz,'fontweight','bold'); + end + h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') insufficient data']); + set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') + end + + set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); + + box off + if cnt <= 3, h=ylabel(['Response '], 'fontweight','bold'); end + + xlabel('Time (ms)', 'fontweight','bold'); + + title('Step Response Functions'); + h=plot([0 500],[1 1],'k--'); + set(h,'linewidth',.5) + axis([0 500 0 ymax]) + grid on + end + end + + elseif fcntSR == 11 + warndlg('10 files maximum. Click reset.'); + end + end + set(PStunefig, 'pointer', 'arrow') + + updateStep=0; +else + for p = 1 : 3 + delete(subplot('position',posInfo.TparamsPos(p,:))) + delete(subplot('position',posInfo.TparamsPos(p+3,:))) + delete(subplot('position',posInfo.TparamsPos(p+6,:))) + delete(subplot('position',posInfo.TparamsPos(p+9,:))) + peaktime = []; + peakresp = []; + latencyHalfHeight = []; + latencyHalfHeight_std = []; + peakresp_std = []; + end +end + + + + + diff --git a/src/ui/PScolormap.m b/src/ui/PScolormap.m index aecc745..e2c162a 100644 --- a/src/ui/PScolormap.m +++ b/src/ui/PScolormap.m @@ -1,37 +1,37 @@ -%% PScolormap - creates a few novel, more linear, colormap options -% this is a good place to continue improving colormaps -% -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Get colormap data without setting any figure's colormap (avoids stale colorbar errors) -try - a=parula(64); -catch - a=viridis(64); -end -viridiscmap=[a(:,1) a(:,2) a(:,3)*.6]; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% 2) a couple more linear, gamma corrected, cmaps using dkl colorspace -% http://www.scholarpedia.org/article/Color_spaces - -% 64 steps from -1 to 1, DKL color range -i=.01:1/64:1;%half -i2=-1:1/31.9:1;%full - -% DKL 'red' white bg -for j=1:64, - linearREDcmap(j,:)=dkl2rgb([-i2(j) i(j) 0])/255; -end -% DKL 'grey' white bg -for j=1:64, - linearGREYcmap(j,:)=dkl2rgb([-i2(j) 0 0])/255; -end +%% PScolormap - creates a few novel, more linear, colormap options +% this is a good place to continue improving colormaps +% +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get colormap data without setting any figure's colormap (avoids stale colorbar errors) +try + a=parula(64); +catch + a=viridis(64); +end +viridiscmap=[a(:,1) a(:,2) a(:,3)*.6]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% 2) a couple more linear, gamma corrected, cmaps using dkl colorspace +% http://www.scholarpedia.org/article/Color_spaces + +% 64 steps from -1 to 1, DKL color range +i=.01:1/64:1;%half +i2=-1:1/31.9:1;%full + +% DKL 'red' white bg +for j=1:64, + linearREDcmap(j,:)=dkl2rgb([-i2(j) i(j) 0])/255; +end +% DKL 'grey' white bg +for j=1:64, + linearGREYcmap(j,:)=dkl2rgb([-i2(j) 0 0])/255; +end diff --git a/src/ui/PSdispSetupInfoUIcontrol.m b/src/ui/PSdispSetupInfoUIcontrol.m index 9adf766..4ec7cc7 100644 --- a/src/ui/PSdispSetupInfoUIcontrol.m +++ b/src/ui/PSdispSetupInfoUIcontrol.m @@ -1,49 +1,49 @@ -%% PSdispSetupInfo - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - -PSdisp=figure(5); -screensz = get(0,'ScreenSize'); -set(PSdisp, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSdisp, 'NumberTitle', 'on'); -set(PSdisp, 'Name', ['PIDscope (' PsVersion ') - Setup Info']); -set(PSdisp,'color',bgcolor) - -columnWidth=55*round(screensz_multiplier*prop_max_screen); - -TooltipString_FileNumDispA=['List of files available. Click to view setup info for each']; -posInfo.FileNumDispA=[.22 .95 .1 .04]; -posInfo.FileNumDispB=[.72 .95 .1 .04]; -posInfo.checkboxDIFF=[.04 .96 .1 .04]; - -guiHandlesInfo.FileNumDispA = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... - 'fontsize',fontsz, 'units','normalized','Position', [posInfo.FileNumDispA],'callback','@selection; PSdispSetupInfo;'); -set(guiHandlesInfo.FileNumDispA, 'Value', 1); -if Nfiles > 1 - guiHandlesInfo.FileNumDispB = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... - 'fontsize',fontsz, 'units','normalized','Position', [posInfo.FileNumDispB],'callback','@selection; PSdispSetupInfo;'); - set(guiHandlesInfo.FileNumDispB, 'Value', 2); -end - -guiHandlesInfo.checkboxDIFF =uicontrol(PSdisp,'Style','checkbox','String','Show Differences Only','fontsize',fontsz,'TooltipString', [''],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxDIFF],'callback', 'PSdispSetupInfo;'); - -else - warndlg('Please select file(s)'); -end - -% functions -function selection(src,event) - val = c.Value; - str = c.String; - str{val}; - % disp(['Selection: ' str{val}]); +%% PSdispSetupInfo + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + +PSdisp=figure(5); +screensz = get(0,'ScreenSize'); +set(PSdisp, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); +set(PSdisp, 'NumberTitle', 'on'); +set(PSdisp, 'Name', ['PIDscope (' PsVersion ') - Setup Info']); +set(PSdisp,'color',bgcolor) + +columnWidth=55*round(screensz_multiplier*prop_max_screen); + +TooltipString_FileNumDispA=['List of files available. Click to view setup info for each']; +posInfo.FileNumDispA=[.22 .95 .1 .04]; +posInfo.FileNumDispB=[.72 .95 .1 .04]; +posInfo.checkboxDIFF=[.04 .96 .1 .04]; + +guiHandlesInfo.FileNumDispA = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz, 'units','normalized','Position', [posInfo.FileNumDispA],'callback','@selection; PSdispSetupInfo;'); +set(guiHandlesInfo.FileNumDispA, 'Value', 1); +if Nfiles > 1 + guiHandlesInfo.FileNumDispB = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz, 'units','normalized','Position', [posInfo.FileNumDispB],'callback','@selection; PSdispSetupInfo;'); + set(guiHandlesInfo.FileNumDispB, 'Value', 2); +end + +guiHandlesInfo.checkboxDIFF =uicontrol(PSdisp,'Style','checkbox','String','Show Differences Only','fontsize',fontsz,'TooltipString', [''],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxDIFF],'callback', 'PSdispSetupInfo;'); + +else + warndlg('Please select file(s)'); +end + +% functions +function selection(src,event) + val = c.Value; + str = c.String; + str{val}; + % disp(['Selection: ' str{val}]); end \ No newline at end of file diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index 63046bf..b00285c 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -1,67 +1,67 @@ -%% PSerrUIcontrol - ui controls for PID error analyses - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - -if ~isempty(filenameA) || ~isempty(filenameB) - -PSerrfig=figure(3); -set(PSerrfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSerrfig, 'NumberTitle', 'off'); -set(PSerrfig, 'Name', ['PIDscope (' PsVersion ') - PID Error Tool']); -set(PSerrfig, 'InvertHardcopy', 'off'); -set(PSerrfig,'color',bgcolor) - -PSerrfig_pos = get(PSerrfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); -fontsz3=round(screensz_multiplier*prop_max_screen); -maxDegsec=100; -updateErr=0; - -TooltipString_degsec=['Sets the maximum rate used in the PID error analysis (distribution plots only).',... - newline , 'E.g., the default means only data in which set point was <= 100deg/s is used.',... - newline , 'This cutoff helps to reduce inclusion of data with inflated PID error as a result of snap maneuvers' ]; - -clear posInfo.PIDerrAnalysis -cols=[0.1 0.55]; -rows=[0.66 0.38 0.1]; -k=0; -for c=1:2 - for r=1:3 - k=k+1; - posInfo.PIDerrAnalysis(k,:)=[cols(c) rows(r) 0.39 0.24]; - end -end - -posInfo.refresh2=[.09 .94 .06 .04]; -posInfo.saveFig3=[.16 .94 .06 .04]; - -posInfo.maxSticktext=[.22 .966 .12 .03]; -posInfo.maxStick=[.24 .94 .06 .03]; - -errCrtlpanel = uipanel('Title','','FontSize',fontsz3,... - 'BackgroundColor',[.95 .95 .95],... - 'Position',[.085 .93 .23 .06]); - -guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh2],... - 'callback','updateErr=1;PSplotPIDerror;'); -set(guiHandlesPIDerr.refresh, 'BackgroundColor', [1 1 .2]); - -guiHandlesPIDerr.maxSticktext = uicontrol(PSerrfig,'style','text','string','max stick deg/s','fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxSticktext]); -guiHandlesPIDerr.maxStick = uicontrol(PSerrfig,'style','edit','string',[int2str(maxDegsec)],'fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','Position',[posInfo.maxStick],... - 'callback','@textinput_call; maxDegsec=str2num(get(guiHandlesPIDerr.maxStick, ''String'')); updateErr=1;PSplotPIDerror; '); - -guiHandlesPIDerr.saveFig3 = uicontrol(PSerrfig,'string','Save Fig','fontsize',fontsz3,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig3],... - 'callback','set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''normal'');'); -set(guiHandlesPIDerr.saveFig3, 'BackgroundColor', [.8 .8 .8]); - -else - errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); - pause(2); -end +%% PSerrUIcontrol - ui controls for PID error analyses + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + +if ~isempty(filenameA) || ~isempty(filenameB) + +PSerrfig=figure(3); +set(PSerrfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); +set(PSerrfig, 'NumberTitle', 'off'); +set(PSerrfig, 'Name', ['PIDscope (' PsVersion ') - PID Error Tool']); +set(PSerrfig, 'InvertHardcopy', 'off'); +set(PSerrfig,'color',bgcolor) + +PSerrfig_pos = get(PSerrfig, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); +fontsz3=round(screensz_multiplier*prop_max_screen); +maxDegsec=100; +updateErr=0; + +TooltipString_degsec=['Sets the maximum rate used in the PID error analysis (distribution plots only).',... + newline , 'E.g., the default means only data in which set point was <= 100deg/s is used.',... + newline , 'This cutoff helps to reduce inclusion of data with inflated PID error as a result of snap maneuvers' ]; + +clear posInfo.PIDerrAnalysis +cols=[0.1 0.55]; +rows=[0.66 0.38 0.1]; +k=0; +for c=1:2 + for r=1:3 + k=k+1; + posInfo.PIDerrAnalysis(k,:)=[cols(c) rows(r) 0.39 0.24]; + end +end + +posInfo.refresh2=[.09 .94 .06 .04]; +posInfo.saveFig3=[.16 .94 .06 .04]; + +posInfo.maxSticktext=[.22 .966 .12 .03]; +posInfo.maxStick=[.24 .94 .06 .03]; + +errCrtlpanel = uipanel('Title','','FontSize',fontsz3,... + 'BackgroundColor',[.95 .95 .95],... + 'Position',[.085 .93 .23 .06]); + +guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh2],... + 'callback','updateErr=1;PSplotPIDerror;'); +set(guiHandlesPIDerr.refresh, 'BackgroundColor', [1 1 .2]); + +guiHandlesPIDerr.maxSticktext = uicontrol(PSerrfig,'style','text','string','max stick deg/s','fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxSticktext]); +guiHandlesPIDerr.maxStick = uicontrol(PSerrfig,'style','edit','string',[int2str(maxDegsec)],'fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','Position',[posInfo.maxStick],... + 'callback','@textinput_call; maxDegsec=str2num(get(guiHandlesPIDerr.maxStick, ''String'')); updateErr=1;PSplotPIDerror; '); + +guiHandlesPIDerr.saveFig3 = uicontrol(PSerrfig,'string','Save Fig','fontsize',fontsz3,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig3],... + 'callback','set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''normal'');'); +set(guiHandlesPIDerr.saveFig3, 'BackgroundColor', [.8 .8 .8]); + +else + errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); + pause(2); +end diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 3571c96..b5c509b 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -1,294 +1,294 @@ -%% PSspecUIcontrol - ui controls for spectral analyses plots - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - -%%% tooltips -TooltipString_specRun=['Run current spectral configuration',... - newline, 'Warning: Set subsampling dropdown @ or < medium for faster processing.']; -TooltipString_presets=['Choose from a selection of PRESET configurations']; -TooltipString_cmap=['Choose from a selection of colormaps']; -TooltipString_smooth=['Choose amount of smoothing']; -TooltipString_2d=['Show 2 dimensional plots']; -TooltipString_user=['Choose the variable you wish to plot (consider PRESETs dropdown menu above for quick configurations)']; -TooltipString_sub100=['Zoom data to show sub 100Hz details',... - newline, 'Typically used to see propwash or mid-throttle vibration in e.g. Gyro/Pterm/PIDerror']; -TooltipString_phase=['Estimated phase delay based on cross-correlation technique.',... - newline, 'Note: estimate is most reliable with sufficient stick input so as to modulate the gyro and dterm.',... - newline, 'Also requires that betaflight debug_mode is set to ''GYRO_SCALED'' ']; -TooltipString_scale=['Colormap scaling. Note, the default is set such that an optimally filtered gyro ',... - newline, 'should show little to no activity with the exception of a sub 100Hz band across throttle.',... - newline, 'Dterm and motor outputs will typically be noisier, so sometimes scale adjustments ',... - newline, 'are useful to see details. Otherwise, scaling should be the same when making comparisons']; -TooltipString_controlFreqCutoff=['Hz = Freq cutoff bounds for sub100Hz mean/peak analysis window.',... - newline 'Changing this will move the yellow dashed lines representing this range (only in sub100Hz view).']; - - -%%% - -% define -smat=[];%string -ampmat=[];%spec matrix -amp2d=[];%spec 2d -freq=[];% freq - -% only need to call once to compute extra colormaps -try PScolormap; catch, end -SpecLineCols=[]; -SpecLineCols(:,:,1) = [colorA; colorA; colorA; colorA]; -SpecLineCols(:,:,2) = [colorA; colorA; colorB; colorB]; -SpecLineCols(:,:,3) = [colorA; colorB; colorC; colorD]; - - -clear posInfo.SpecPos -cols=[0.04 0.25 0.46 0.67]; -rows=[0.64 0.35 0.06]; -k=0; -for c=1:4 - for r=1:3 - k=k+1; - posInfo.SpecPos(k,:)=[cols(c) rows(r) 0.175 0.24]; - end -end - - -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; - -posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .026]; -posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .026]; -posInfo.saveFig1= [cpL+.006 .845 cpW/2-.006 .026]; -posInfo.saveSettings1= [cpL+cpW/2 .845 cpW/2-.006 .026]; - -posInfo.specPresets= [cpL+.003 .805 cpW-.006 .035]; -posInfo.ColormapSelect= [cpL+.003 .775 cpW-.006 .035]; -posInfo.smooth_select = [cpL+.003 .745 cpW-.006 .035]; -posInfo.controlFreqCutoff_text =[cpL+.003 .725 cpW-.006 .025]; -posInfo.controlFreq1Cutoff = [cpL+.005 .700 cpW/2-.005 .025]; -posInfo.controlFreq2Cutoff = [cpL+cpW/2 .700 cpW/2-.005 .025]; -posInfo.checkbox2d= [cpL+.005 .675 cpW/2-.005 .025]; -posInfo.checkboxPSD= [cpL+cpW/2 .675 cpW/2-.005 .025]; -posInfo.checkboxEstRPM= [cpL+.005 .650 cpW-.005 .025]; -posInfo.rpmLegend1= [cpL+.005 .632 cpW-.005 .018]; -posInfo.rpmLegend2= [cpL+.005 .616 cpW-.005 .018]; -posInfo.rpmLegend3= [cpL+.005 .600 cpW-.005 .018]; - -if exist('isOctave','var') && isOctave - % Octave Qt widgets need more vertical space - posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .030]; - posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .030]; - posInfo.saveFig1= [cpL+.006 .838 cpW/2-.006 .030]; - posInfo.saveSettings1= [cpL+cpW/2 .838 cpW/2-.006 .030]; - posInfo.specPresets= [cpL+.003 .795 cpW-.006 .035]; - posInfo.ColormapSelect= [cpL+.003 .758 cpW-.006 .035]; - posInfo.smooth_select= [cpL+.003 .721 cpW-.006 .035]; - posInfo.controlFreqCutoff_text= [cpL+.003 .700 cpW-.006 .024]; - posInfo.controlFreq1Cutoff= [cpL+.005 .675 cpW/2-.005 .024]; - posInfo.controlFreq2Cutoff= [cpL+cpW/2 .675 cpW/2-.005 .024]; - posInfo.checkbox2d= [cpL+.005 .650 cpW/2-.005 .025]; - posInfo.checkboxPSD= [cpL+cpW/2 .650 cpW/2-.005 .025]; - posInfo.checkboxEstRPM= [cpL+.005 .625 cpW-.005 .025]; - posInfo.rpmLegend1= [cpL+.005 .605 cpW-.005 .020]; - posInfo.rpmLegend2= [cpL+.005 .585 cpW-.005 .020]; - posInfo.rpmLegend3= [cpL+.005 .565 cpW-.005 .020]; -end - -posInfo.AphasedelayText1=[.06 .984 .14 .02]; -posInfo.AphasedelayText2=[.27 .984 .14 .02]; -posInfo.AphasedelayText3=[.48 .984 .14 .02]; -posInfo.AphasedelayText4=[.69 .984 .14 .02]; - -posInfo.hCbar1pos=[0.04 0.89 0.175 0.02]; -posInfo.hCbar2pos=[0.25 0.89 0.175 0.02]; -posInfo.hCbar3pos=[0.46 0.89 0.175 0.02]; -posInfo.hCbar4pos=[0.67 0.89 0.175 0.02]; - -ddh = 0.01; % dropdown height -if exist('isOctave','var') && isOctave, ddh = 0.025; end -posInfo.hDropdn1pos=[0.08 0.97 0.095 ddh]; -posInfo.hDropdn2pos=[0.29 0.97 0.095 ddh]; -posInfo.hDropdn3pos=[0.50 0.97 0.095 ddh]; -posInfo.hDropdn4pos=[0.71 0.97 0.095 ddh]; - -posInfo.fDropdn1pos=[0.08 0.942 0.095 ddh]; -posInfo.fDropdn2pos=[0.29 0.942 0.095 ddh]; -posInfo.fDropdn3pos=[0.50 0.942 0.095 ddh]; -posInfo.fDropdn4pos=[0.71 0.942 0.095 ddh]; - -posInfo.Sub100HzCheck1=[0.175 0.942 .06 .025]; -posInfo.Sub100HzCheck2=[.385 .942 .06 .025]; -posInfo.Sub100HzCheck3=[.595 .942 .06 .025]; -posInfo.Sub100HzCheck4=[.805 .942 .06 .025]; - -posInfo.climMax_text = [.01 .913 .025 .024]; -posInfo.climMax_input = [.01 .888 .025 .024]; -posInfo.climMax_text2 = [.22 .913 .025 .024]; -posInfo.climMax_input2 = [.22 .888 .025 .024]; -posInfo.climMax_text3 = [.43 .913 .025 .024]; -posInfo.climMax_input3 = [.43 .888 .025 .024]; -posInfo.climMax_text4 = [.64 .913 .025 .024]; -posInfo.climMax_input4 = [.64 .888 .025 .024]; -climScale=[0.5 0.5 0.5 0.5; 10 10 10 10]; -Flim1=20; % 3.3333Hz steps -Flim2=60; - -PSspecfig=figure(2); -set(PSspecfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSspecfig, 'NumberTitle', 'off'); -set(PSspecfig, 'Name', ['PIDscope (' PsVersion ') - Frequency x Throttle Spectrogram']); -set(PSspecfig, 'InvertHardcopy', 'off'); -set(PSspecfig,'color',bgcolor); - - -try % datacursormode not available in Octave - dcm_obj2 = datacursormode(PSspecfig); - set(dcm_obj2,'UpdateFcn',@PSdatatip); -end - -specCrtlpanelPos = [cpL .59 cpW .33]; -if exist('isOctave','var') && isOctave - specCrtlpanelPos = [cpL .55 cpW .37]; -end -specCrtlpanel = uipanel('Title','Params','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... - 'Position',specCrtlpanelPos); - -%%% PRESET CONFIGURATIONS - -% guiHandles.FileNum = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... -% 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameASpec],'callback','PSplotSpec;'); - -guiHandlesSpec.specPresets = uicontrol(PSspecfig,'Style','popupmenu','string',{'Presets:'; '1. Gyro prefilt | Gyro | Dterm prefilt | Dterm' ; '2. Gyro prefilt | Gyro | Pterm | Dterm' ; '3. Gyro | Dterm | Set point | PID error' ; '4. A|A|B|B Gyro prefilt | Gyro' ; '5. A|A|B|B Dterm prefilt | Dterm' ; '6. A|B|C|D Gyro prefilt ' ;'7. A|B|C|D Gyro '; '8. A|B|C|D Dterm '; '9. A|B|C|D PID error'},... - 'fontsize',fontsz,'TooltipString', [TooltipString_presets], 'units','normalized','Position', [posInfo.specPresets],'callback',... - ['pv=get(guiHandlesSpec.specPresets,''Value''); ',... - 'if pv==1, set(guiHandlesSpec.SpecSelect{1},''Value'',1); set(guiHandlesSpec.SpecSelect{2},''Value'',1); set(guiHandlesSpec.SpecSelect{3},''Value'',1); set(guiHandlesSpec.SpecSelect{4},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==2, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==3, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',6); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==4, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',5); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',1); end;',... - 'if pv==5, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==6, set(guiHandlesSpec.SpecSelect{1},''Value'',8); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==7, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',3); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',3); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==8, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',2); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==9, set(guiHandlesSpec.SpecSelect{1},''Value'',7); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',7); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==10, set(guiHandlesSpec.SpecSelect{1},''Value'',4); set(guiHandlesSpec.SpecSelect{2},''Value'',4); set(guiHandlesSpec.SpecSelect{3},''Value'',4); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;']); - -guiHandlesSpec.computeSpec = uicontrol(PSspecfig,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec],... - 'callback','PSplotSpec;'); -set(guiHandlesSpec.computeSpec, 'ForegroundColor', colRun); - -guiHandlesSpec.resetSpec = uicontrol(PSspecfig,'string','Reset','fontsize',fontsz,'TooltipString', ['Reset Spectral Tool'],'units','normalized','Position',[posInfo.resetSpec],... - 'callback','delete(findobj(PSspecfig,''Type'',''axes'')); set(guiHandlesSpec.specPresets, ''Value'', 1); PSspecUIcontrol; set(PSspecfig, ''pointer'', ''arrow'');'); -set(guiHandlesSpec.resetSpec, 'ForegroundColor', cautionCol); - -guiHandlesSpec.checkbox2d =uicontrol(PSspecfig,'Style','checkbox','String','2D','fontsize',fontsz,'TooltipString', [TooltipString_2d],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkbox2d],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.checkboxPSD =uicontrol(PSspecfig,'Style','checkbox','String','PSD','fontsize',fontsz,'TooltipString', ['Power Spectral Density'],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec;'); -set(guiHandlesSpec.checkboxPSD, 'Value', 0); - -guiHandlesSpec.checkboxEstRPM =uicontrol(PSspecfig,'Style','checkbox','String','Est. RPM','fontsize',fontsz,'TooltipString', ['Estimate motor RPM from spectrum (2D heatmap only)'],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxEstRPM],'callback','updateSpec=1;PSplotSpec;'); -set(guiHandlesSpec.checkboxEstRPM, 'Value', 0); - -uicontrol(PSspecfig,'style','text','string','--- 1st (fund)','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[0 .7 .15],'FontWeight','bold','Position',[posInfo.rpmLegend1]); -uicontrol(PSspecfig,'style','text','string','-- 2nd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .7 0],'FontWeight','bold','Position',[posInfo.rpmLegend2]); -uicontrol(PSspecfig,'style','text','string','... 3rd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .2 0],'FontWeight','bold','Position',[posInfo.rpmLegend3]); - -guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); -guiHandlesSpec.controlFreq1Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim1))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq1Cutoff],... - 'callback','@textinput_call2; Flim1=round(str2num(get(guiHandlesSpec.controlFreq1Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); -guiHandlesSpec.controlFreq2Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim2))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq2Cutoff],... - 'callback','@textinput_call2; Flim2=round(str2num(get(guiHandlesSpec.controlFreq2Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.saveFig1 = uicontrol(PSspecfig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','ForegroundColor',[saveCol],'Position',[posInfo.saveFig1],... - 'callback','set(guiHandlesSpec.saveFig1, ''FontWeight'', ''bold'');PSsaveFig;set(guiHandlesSpec.saveFig1, ''FontWeight'', ''normal'');'); - -guiHandlesSpec.saveSettings1 = uicontrol(PSspecfig,'string','Save Settings','fontsize',fontsz, 'TooltipString',['Save current settings to PIDscope defaults' ], 'units','normalized','Position',[posInfo.saveSettings1],... - 'callback','set(guiHandlesSpec.saveSettings1, ''FontWeight'', ''bold'');PSsaveSettings; set(guiHandlesSpec.saveSettings1, ''FontWeight'', ''normal'');'); -set(guiHandlesSpec.saveSettings1, 'ForegroundColor', saveCol); - -guiHandlesSpec.Sub100HzCheck{1} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck1],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); -guiHandlesSpec.Sub100HzCheck{2} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck2],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); -guiHandlesSpec.Sub100HzCheck{3} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck3],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); -guiHandlesSpec.Sub100HzCheck{4} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck4],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); - -% create string list for SpecSelect -sA={'NONE','Gyro','Gyro prefilt','PID error','Set point','Pterm','Dterm','Dterm prefilt','PIDsum'}; - -guiHandlesSpec.SpecSelect{1} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn1pos]); -guiHandlesSpec.SpecSelect{2} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn2pos]); -guiHandlesSpec.SpecSelect{3} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn3pos]); -guiHandlesSpec.SpecSelect{4} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn4pos]); - -guiHandlesSpec.FileSelect{1} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn1pos]); -guiHandlesSpec.FileSelect{2} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn2pos]); -guiHandlesSpec.FileSelect{3} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn3pos]); -guiHandlesSpec.FileSelect{4} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn4pos]); - -guiHandlesSpec.smoothFactor_select = uicontrol(PSspecfig,'style','popupmenu','string',{'smoothing low' 'smoothing low-med' 'smoothing medium' 'smoothing med-high' 'smoothing high'},'fontsize',fontsz,'TooltipString', [TooltipString_smooth], 'units','normalized','Position',[posInfo.smooth_select],... - 'callback','@selection2;updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_text = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text]); -guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); - - guiHandlesSpec.climMax_text2 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text2]); -guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); - - guiHandlesSpec.climMax_text3 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text3]); -guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); - - guiHandlesSpec.climMax_text4 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text4]); -guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); - - guiHandlesSpec.ColormapSelect = uicontrol(PSspecfig,'Style','popupmenu','string',{'viridis','jet','hot','cool','gray','bone','copper','linear-RED','linear-GREY'},... - 'fontsize',fontsz,'TooltipString', [TooltipString_cmap], 'units','normalized','Position',[posInfo.ColormapSelect],'callback','@selection2;updateSpec=1; PSplotSpec;'); -set(guiHandlesSpec.ColormapSelect, 'Value', 3);% jet 2 hot 3 viridis 8 - -try set(guiHandlesSpec.SpecSelect{1}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column1')))), catch, set(guiHandlesSpec.SpecSelect{1}, 'Value', 3); end -try set(guiHandlesSpec.SpecSelect{2}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column2')))), catch, set(guiHandlesSpec.SpecSelect{2}, 'Value', 2); end -try set(guiHandlesSpec.SpecSelect{3}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column3')))), catch, set(guiHandlesSpec.SpecSelect{3}, 'Value', 8); end -try set(guiHandlesSpec.SpecSelect{4}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column4')))), catch, set(guiHandlesSpec.SpecSelect{4}, 'Value', 7); end -try set(guiHandlesSpec.specPresets, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Preset')))), catch, set(guiHandlesSpec.specPresets, 'Value', 1); end -try set(guiHandlesSpec.ColormapSelect, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Colormap')))), catch, set(guiHandlesSpec.ColormapSelect, 'Value', 3); end -try set(guiHandlesSpec.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Smoothing')))), catch, set(guiHandlesSpec.smoothFactor_select, 'Value', 3); end - - -else - warndlg('Please select file(s)'); -end - - -% functions -function selection2(src,event) - val = c.Value; - str = c.String; - str{val}; -end - -function getList2(hObj,event) -v=get(hObj,'value') -end - -function textinput_call2(src,eventdata) -str=get(src,'String'); - if isempty(str2num(str)) - set(src,'string','0'); - warndlg('Input must be numerical'); - end -end - - - - - +%% PSspecUIcontrol - ui controls for spectral analyses plots + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + +%%% tooltips +TooltipString_specRun=['Run current spectral configuration',... + newline, 'Warning: Set subsampling dropdown @ or < medium for faster processing.']; +TooltipString_presets=['Choose from a selection of PRESET configurations']; +TooltipString_cmap=['Choose from a selection of colormaps']; +TooltipString_smooth=['Choose amount of smoothing']; +TooltipString_2d=['Show 2 dimensional plots']; +TooltipString_user=['Choose the variable you wish to plot (consider PRESETs dropdown menu above for quick configurations)']; +TooltipString_sub100=['Zoom data to show sub 100Hz details',... + newline, 'Typically used to see propwash or mid-throttle vibration in e.g. Gyro/Pterm/PIDerror']; +TooltipString_phase=['Estimated phase delay based on cross-correlation technique.',... + newline, 'Note: estimate is most reliable with sufficient stick input so as to modulate the gyro and dterm.',... + newline, 'Also requires that betaflight debug_mode is set to ''GYRO_SCALED'' ']; +TooltipString_scale=['Colormap scaling. Note, the default is set such that an optimally filtered gyro ',... + newline, 'should show little to no activity with the exception of a sub 100Hz band across throttle.',... + newline, 'Dterm and motor outputs will typically be noisier, so sometimes scale adjustments ',... + newline, 'are useful to see details. Otherwise, scaling should be the same when making comparisons']; +TooltipString_controlFreqCutoff=['Hz = Freq cutoff bounds for sub100Hz mean/peak analysis window.',... + newline 'Changing this will move the yellow dashed lines representing this range (only in sub100Hz view).']; + + +%%% + +% define +smat=[];%string +ampmat=[];%spec matrix +amp2d=[];%spec 2d +freq=[];% freq + +% only need to call once to compute extra colormaps +try PScolormap; catch, end +SpecLineCols=[]; +SpecLineCols(:,:,1) = [colorA; colorA; colorA; colorA]; +SpecLineCols(:,:,2) = [colorA; colorA; colorB; colorB]; +SpecLineCols(:,:,3) = [colorA; colorB; colorC; colorD]; + + +clear posInfo.SpecPos +cols=[0.04 0.25 0.46 0.67]; +rows=[0.64 0.35 0.06]; +k=0; +for c=1:4 + for r=1:3 + k=k+1; + posInfo.SpecPos(k,:)=[cols(c) rows(r) 0.175 0.24]; + end +end + + +% Control panel layout (consistent with Log Viewer cpL/cpW) +cpL = .875; cpW = .12; + +posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .026]; +posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .026]; +posInfo.saveFig1= [cpL+.006 .845 cpW/2-.006 .026]; +posInfo.saveSettings1= [cpL+cpW/2 .845 cpW/2-.006 .026]; + +posInfo.specPresets= [cpL+.003 .805 cpW-.006 .035]; +posInfo.ColormapSelect= [cpL+.003 .775 cpW-.006 .035]; +posInfo.smooth_select = [cpL+.003 .745 cpW-.006 .035]; +posInfo.controlFreqCutoff_text =[cpL+.003 .725 cpW-.006 .025]; +posInfo.controlFreq1Cutoff = [cpL+.005 .700 cpW/2-.005 .025]; +posInfo.controlFreq2Cutoff = [cpL+cpW/2 .700 cpW/2-.005 .025]; +posInfo.checkbox2d= [cpL+.005 .675 cpW/2-.005 .025]; +posInfo.checkboxPSD= [cpL+cpW/2 .675 cpW/2-.005 .025]; +posInfo.checkboxEstRPM= [cpL+.005 .650 cpW-.005 .025]; +posInfo.rpmLegend1= [cpL+.005 .632 cpW-.005 .018]; +posInfo.rpmLegend2= [cpL+.005 .616 cpW-.005 .018]; +posInfo.rpmLegend3= [cpL+.005 .600 cpW-.005 .018]; + +if exist('isOctave','var') && isOctave + % Octave Qt widgets need more vertical space + posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .030]; + posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .030]; + posInfo.saveFig1= [cpL+.006 .838 cpW/2-.006 .030]; + posInfo.saveSettings1= [cpL+cpW/2 .838 cpW/2-.006 .030]; + posInfo.specPresets= [cpL+.003 .795 cpW-.006 .035]; + posInfo.ColormapSelect= [cpL+.003 .758 cpW-.006 .035]; + posInfo.smooth_select= [cpL+.003 .721 cpW-.006 .035]; + posInfo.controlFreqCutoff_text= [cpL+.003 .700 cpW-.006 .024]; + posInfo.controlFreq1Cutoff= [cpL+.005 .675 cpW/2-.005 .024]; + posInfo.controlFreq2Cutoff= [cpL+cpW/2 .675 cpW/2-.005 .024]; + posInfo.checkbox2d= [cpL+.005 .650 cpW/2-.005 .025]; + posInfo.checkboxPSD= [cpL+cpW/2 .650 cpW/2-.005 .025]; + posInfo.checkboxEstRPM= [cpL+.005 .625 cpW-.005 .025]; + posInfo.rpmLegend1= [cpL+.005 .605 cpW-.005 .020]; + posInfo.rpmLegend2= [cpL+.005 .585 cpW-.005 .020]; + posInfo.rpmLegend3= [cpL+.005 .565 cpW-.005 .020]; +end + +posInfo.AphasedelayText1=[.06 .984 .14 .02]; +posInfo.AphasedelayText2=[.27 .984 .14 .02]; +posInfo.AphasedelayText3=[.48 .984 .14 .02]; +posInfo.AphasedelayText4=[.69 .984 .14 .02]; + +posInfo.hCbar1pos=[0.04 0.89 0.175 0.02]; +posInfo.hCbar2pos=[0.25 0.89 0.175 0.02]; +posInfo.hCbar3pos=[0.46 0.89 0.175 0.02]; +posInfo.hCbar4pos=[0.67 0.89 0.175 0.02]; + +ddh = 0.01; % dropdown height +if exist('isOctave','var') && isOctave, ddh = 0.025; end +posInfo.hDropdn1pos=[0.08 0.97 0.095 ddh]; +posInfo.hDropdn2pos=[0.29 0.97 0.095 ddh]; +posInfo.hDropdn3pos=[0.50 0.97 0.095 ddh]; +posInfo.hDropdn4pos=[0.71 0.97 0.095 ddh]; + +posInfo.fDropdn1pos=[0.08 0.942 0.095 ddh]; +posInfo.fDropdn2pos=[0.29 0.942 0.095 ddh]; +posInfo.fDropdn3pos=[0.50 0.942 0.095 ddh]; +posInfo.fDropdn4pos=[0.71 0.942 0.095 ddh]; + +posInfo.Sub100HzCheck1=[0.175 0.942 .06 .025]; +posInfo.Sub100HzCheck2=[.385 .942 .06 .025]; +posInfo.Sub100HzCheck3=[.595 .942 .06 .025]; +posInfo.Sub100HzCheck4=[.805 .942 .06 .025]; + +posInfo.climMax_text = [.01 .913 .025 .024]; +posInfo.climMax_input = [.01 .888 .025 .024]; +posInfo.climMax_text2 = [.22 .913 .025 .024]; +posInfo.climMax_input2 = [.22 .888 .025 .024]; +posInfo.climMax_text3 = [.43 .913 .025 .024]; +posInfo.climMax_input3 = [.43 .888 .025 .024]; +posInfo.climMax_text4 = [.64 .913 .025 .024]; +posInfo.climMax_input4 = [.64 .888 .025 .024]; +climScale=[0.5 0.5 0.5 0.5; 10 10 10 10]; +Flim1=20; % 3.3333Hz steps +Flim2=60; + +PSspecfig=figure(2); +set(PSspecfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); +set(PSspecfig, 'NumberTitle', 'off'); +set(PSspecfig, 'Name', ['PIDscope (' PsVersion ') - Frequency x Throttle Spectrogram']); +set(PSspecfig, 'InvertHardcopy', 'off'); +set(PSspecfig,'color',bgcolor); + + +try % datacursormode not available in Octave + dcm_obj2 = datacursormode(PSspecfig); + set(dcm_obj2,'UpdateFcn',@PSdatatip); +end + +specCrtlpanelPos = [cpL .59 cpW .33]; +if exist('isOctave','var') && isOctave + specCrtlpanelPos = [cpL .55 cpW .37]; +end +specCrtlpanel = uipanel('Title','Params','FontSize',fontsz,... + 'BackgroundColor',[.95 .95 .95],... + 'Position',specCrtlpanelPos); + +%%% PRESET CONFIGURATIONS + +% guiHandles.FileNum = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... +% 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameASpec],'callback','PSplotSpec;'); + +guiHandlesSpec.specPresets = uicontrol(PSspecfig,'Style','popupmenu','string',{'Presets:'; '1. Gyro prefilt | Gyro | Dterm prefilt | Dterm' ; '2. Gyro prefilt | Gyro | Pterm | Dterm' ; '3. Gyro | Dterm | Set point | PID error' ; '4. A|A|B|B Gyro prefilt | Gyro' ; '5. A|A|B|B Dterm prefilt | Dterm' ; '6. A|B|C|D Gyro prefilt ' ;'7. A|B|C|D Gyro '; '8. A|B|C|D Dterm '; '9. A|B|C|D PID error'},... + 'fontsize',fontsz,'TooltipString', [TooltipString_presets], 'units','normalized','Position', [posInfo.specPresets],'callback',... + ['pv=get(guiHandlesSpec.specPresets,''Value''); ',... + 'if pv==1, set(guiHandlesSpec.SpecSelect{1},''Value'',1); set(guiHandlesSpec.SpecSelect{2},''Value'',1); set(guiHandlesSpec.SpecSelect{3},''Value'',1); set(guiHandlesSpec.SpecSelect{4},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==2, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==3, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',6); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==4, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',5); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',1); end;',... + 'if pv==5, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==6, set(guiHandlesSpec.SpecSelect{1},''Value'',8); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==7, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',3); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',3); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==8, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',2); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==9, set(guiHandlesSpec.SpecSelect{1},''Value'',7); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',7); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... + 'if pv==10, set(guiHandlesSpec.SpecSelect{1},''Value'',4); set(guiHandlesSpec.SpecSelect{2},''Value'',4); set(guiHandlesSpec.SpecSelect{3},''Value'',4); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;']); + +guiHandlesSpec.computeSpec = uicontrol(PSspecfig,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec],... + 'callback','PSplotSpec;'); +set(guiHandlesSpec.computeSpec, 'ForegroundColor', colRun); + +guiHandlesSpec.resetSpec = uicontrol(PSspecfig,'string','Reset','fontsize',fontsz,'TooltipString', ['Reset Spectral Tool'],'units','normalized','Position',[posInfo.resetSpec],... + 'callback','delete(findobj(PSspecfig,''Type'',''axes'')); set(guiHandlesSpec.specPresets, ''Value'', 1); PSspecUIcontrol; set(PSspecfig, ''pointer'', ''arrow'');'); +set(guiHandlesSpec.resetSpec, 'ForegroundColor', cautionCol); + +guiHandlesSpec.checkbox2d =uicontrol(PSspecfig,'Style','checkbox','String','2D','fontsize',fontsz,'TooltipString', [TooltipString_2d],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkbox2d],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.checkboxPSD =uicontrol(PSspecfig,'Style','checkbox','String','PSD','fontsize',fontsz,'TooltipString', ['Power Spectral Density'],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec;'); +set(guiHandlesSpec.checkboxPSD, 'Value', 0); + +guiHandlesSpec.checkboxEstRPM =uicontrol(PSspecfig,'Style','checkbox','String','Est. RPM','fontsize',fontsz,'TooltipString', ['Estimate motor RPM from spectrum (2D heatmap only)'],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxEstRPM],'callback','updateSpec=1;PSplotSpec;'); +set(guiHandlesSpec.checkboxEstRPM, 'Value', 0); + +uicontrol(PSspecfig,'style','text','string','--- 1st (fund)','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[0 .7 .15],'FontWeight','bold','Position',[posInfo.rpmLegend1]); +uicontrol(PSspecfig,'style','text','string','-- 2nd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .7 0],'FontWeight','bold','Position',[posInfo.rpmLegend2]); +uicontrol(PSspecfig,'style','text','string','... 3rd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .2 0],'FontWeight','bold','Position',[posInfo.rpmLegend3]); + +guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); +guiHandlesSpec.controlFreq1Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim1))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq1Cutoff],... + 'callback','@textinput_call2; Flim1=round(str2num(get(guiHandlesSpec.controlFreq1Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); +guiHandlesSpec.controlFreq2Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim2))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq2Cutoff],... + 'callback','@textinput_call2; Flim2=round(str2num(get(guiHandlesSpec.controlFreq2Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.saveFig1 = uicontrol(PSspecfig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','ForegroundColor',[saveCol],'Position',[posInfo.saveFig1],... + 'callback','set(guiHandlesSpec.saveFig1, ''FontWeight'', ''bold'');PSsaveFig;set(guiHandlesSpec.saveFig1, ''FontWeight'', ''normal'');'); + +guiHandlesSpec.saveSettings1 = uicontrol(PSspecfig,'string','Save Settings','fontsize',fontsz, 'TooltipString',['Save current settings to PIDscope defaults' ], 'units','normalized','Position',[posInfo.saveSettings1],... + 'callback','set(guiHandlesSpec.saveSettings1, ''FontWeight'', ''bold'');PSsaveSettings; set(guiHandlesSpec.saveSettings1, ''FontWeight'', ''normal'');'); +set(guiHandlesSpec.saveSettings1, 'ForegroundColor', saveCol); + +guiHandlesSpec.Sub100HzCheck{1} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck1],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); +guiHandlesSpec.Sub100HzCheck{2} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck2],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); +guiHandlesSpec.Sub100HzCheck{3} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck3],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); +guiHandlesSpec.Sub100HzCheck{4} =uicontrol(PSspecfig,'Style','checkbox','String','<100Hz','fontsize',fontsz,'TooltipString', [TooltipString_sub100], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Sub100HzCheck4],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), end;updateSpec=1;PSplotSpec;'); + +% create string list for SpecSelect +sA={'NONE','Gyro','Gyro prefilt','PID error','Set point','Pterm','Dterm','Dterm prefilt','PIDsum'}; + +guiHandlesSpec.SpecSelect{1} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn1pos]); +guiHandlesSpec.SpecSelect{2} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn2pos]); +guiHandlesSpec.SpecSelect{3} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn3pos]); +guiHandlesSpec.SpecSelect{4} = uicontrol(PSspecfig,'Style','popupmenu','string',sA, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.hDropdn4pos]); + +guiHandlesSpec.FileSelect{1} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn1pos]); +guiHandlesSpec.FileSelect{2} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn2pos]); +guiHandlesSpec.FileSelect{3} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn3pos]); +guiHandlesSpec.FileSelect{4} = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster], 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fDropdn4pos]); + +guiHandlesSpec.smoothFactor_select = uicontrol(PSspecfig,'style','popupmenu','string',{'smoothing low' 'smoothing low-med' 'smoothing medium' 'smoothing med-high' 'smoothing high'},'fontsize',fontsz,'TooltipString', [TooltipString_smooth], 'units','normalized','Position',[posInfo.smooth_select],... + 'callback','@selection2;updateSpec=1;PSplotSpec;'); + +guiHandlesSpec.climMax_text = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text]); +guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); + + guiHandlesSpec.climMax_text2 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text2]); +guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); + + guiHandlesSpec.climMax_text3 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text3]); +guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); + + guiHandlesSpec.climMax_text4 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text4]); +guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); + + guiHandlesSpec.ColormapSelect = uicontrol(PSspecfig,'Style','popupmenu','string',{'viridis','jet','hot','cool','gray','bone','copper','linear-RED','linear-GREY'},... + 'fontsize',fontsz,'TooltipString', [TooltipString_cmap], 'units','normalized','Position',[posInfo.ColormapSelect],'callback','@selection2;updateSpec=1; PSplotSpec;'); +set(guiHandlesSpec.ColormapSelect, 'Value', 3);% jet 2 hot 3 viridis 8 + +try set(guiHandlesSpec.SpecSelect{1}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column1')))), catch, set(guiHandlesSpec.SpecSelect{1}, 'Value', 3); end +try set(guiHandlesSpec.SpecSelect{2}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column2')))), catch, set(guiHandlesSpec.SpecSelect{2}, 'Value', 2); end +try set(guiHandlesSpec.SpecSelect{3}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column3')))), catch, set(guiHandlesSpec.SpecSelect{3}, 'Value', 8); end +try set(guiHandlesSpec.SpecSelect{4}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column4')))), catch, set(guiHandlesSpec.SpecSelect{4}, 'Value', 7); end +try set(guiHandlesSpec.specPresets, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Preset')))), catch, set(guiHandlesSpec.specPresets, 'Value', 1); end +try set(guiHandlesSpec.ColormapSelect, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Colormap')))), catch, set(guiHandlesSpec.ColormapSelect, 'Value', 3); end +try set(guiHandlesSpec.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Smoothing')))), catch, set(guiHandlesSpec.smoothFactor_select, 'Value', 3); end + + +else + warndlg('Please select file(s)'); +end + + +% functions +function selection2(src,event) + val = c.Value; + str = c.String; + str{val}; +end + +function getList2(hObj,event) +v=get(hObj,'value') +end + +function textinput_call2(src,eventdata) +str=get(src,'String'); + if isempty(str2num(str)) + set(src,'string','0'); + warndlg('Input must be numerical'); + end +end + + + + + diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index b1b209c..40614c4 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -1,123 +1,123 @@ -%% PSplotStats - UI control for flight statistics - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if ~isempty(filenameA) || ~isempty(filenameB) - -PSstatsfig=figure(6); -set(PSstatsfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSstatsfig, 'NumberTitle', 'off'); -set(PSstatsfig, 'Name', ['PIDscope (' PsVersion ') - Flight stats']); -set(PSstatsfig, 'InvertHardcopy', 'off'); -set(PSstatsfig,'color',bgcolor) - -TooltipString_degsecStick=['Plots rate curve (Histograms Figs) in terms of degs per sec per stick-travel units, or how fast one''s rates change across stick travel ']; -TooltipString_crossAxesStats=['Selects from several plotting options, from basic histograms of stick use per flight, to means',... - newline 'and various between-axes representations.',... - newline ' ',... - newline 'Histograms:',... - newline 'Basic descriptive stats of the flight behavior.',... - newline ' ',... - newline 'Mean +/-SD:',... - newline 'Bars represent the mean/average and lines represent the standard deviation (a measure of variability), ||=absolute value or unsigned average.',... - newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer.',... - newline ' ',... - newline 'Topographic Mode1/2 plots:',... - newline 'The plots show the topographic view (like looking down at your radio) of how the sticks were moved during the flight',... - newline 'Line color represents throttle acceleration in red and deceleration in blue, with higher values more saturated.',... - newline 'If throttle is neither accelerating nor decelerating the line color is white.',... - newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer, and refreshing the Flight stats tool.',... - newline ' ',... - newline 'Axes x Throttle plots:',... - newline 'Like the mode plots, line color represents throttle acceleration in red and deceleration in blue, with higher values more saturated.',... - newline 'If throttle is neither accelerating nor decelerating the line color is white.',... - newline 'The ''Mode and axes X throttle topography plots'' are useful for examining patterns in flight behavior,',... - newline 'For example, flight behaviour associated with optimal lap times in a race, or the qualities or asymmetries in one''s flying style ',... - newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer.']; -TooltipString_statScale=['For ''Mode and axes X throttle topography plots'':',... - newline 'Scale: Z-axis (line color) scale, from 1 to infinity, with higher numbers yielding a wider scale,',... - newline 'giving greater distinction between lines, where only the fastest movements (highest acceleration/deceleration) become visible.']; -TooltipString_statAlpha=['For ''Mode and axes X throttle topography plots'':',... - newline 'Alpha: line transparency, from 0 (fully transparent) to 1 (not transparent)']; -updateStats=0; - -zScale=1; -zTransparency=1; - -PSstatsfig_pos = get(PSstatsfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); -fontsz5=round(screensz_multiplier*prop_max_screen); - -clear posInfo.statsPos -cols=[0.06 0.54]; -rows=[0.75 0.52 0.29 0.06]; -k=0; -for c=1:2 - for r=1:4 - k=k+1; - posInfo.statsPos(k,:)=[cols(c) rows(r) 0.39 0.18]; - end -end - -posInfo.saveFig5=[.065 .945 .06 .04]; -posInfo.refresh3=[.135 .945 .06 .04]; -posInfo.degsecStick=[.20 .945 .09 .04]; -posInfo.crossAxesStats=[.29 .945 .08 .04]; - -posInfo.crossAxesStats_text = [.385 .965 .03 .03]; -posInfo.crossAxesStats_input = [.385 .945 .03 .03]; -posInfo.crossAxesStats_text2 = [.42 .965 .03 .03]; -posInfo.crossAxesStats_input2 = [.42 .945 .03 .03]; - -statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... - 'BackgroundColor',[.95 .95 .95],... - 'Position',[.06 .935 .40 .06]); - -guiHandlesStats.saveFig5 = uicontrol(PSstatsfig,'string','Save Fig','fontsize',fontsz5,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig5],... - 'callback','set(guiHandlesStats.saveFig5, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesStats.saveFig5, ''FontWeight'', ''normal'');'); -set(guiHandlesStats.saveFig5, 'BackgroundColor', [.8 .8 .8]); - -guiHandlesStats.refresh = uicontrol(PSstatsfig,'string','Refresh','fontsize',fontsz5,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh3],... - 'callback','updateStats=1;PSplotStats;'); -set(guiHandlesStats.refresh, 'BackgroundColor', [1 1 .2]); - -guiHandlesStats.degsecStick =uicontrol(PSstatsfig,'Style','checkbox','String','rate of change','fontsize',fontsz5,'TooltipString',[TooltipString_degsecStick],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.degsecStick],'callback','if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); -guiHandlesStats.crossAxesStats =uicontrol(PSstatsfig,'Style','popupmenu','String',{'Histograms'; 'Mean & Standard Deviation'; 'Mode 1 topography'; 'Mode 2 topography'; 'Axes X Throttle'},'fontsize',fontsz5,'TooltipString',[TooltipString_crossAxesStats],... - 'units','normalized','BackgroundColor',[1 1 1 ],'Position',[posInfo.crossAxesStats],'callback','@selection; if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); -%guiHandlesStats.crossAxesStats.Value=0; - -guiHandlesStats.crossAxesStats_text = uicontrol(PSstatsfig,'style','text','string','scale','fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text]); -guiHandlesStats.crossAxesStats_input = uicontrol(PSstatsfig,'style','edit','string',[num2str(zScale)],'fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','Position',[posInfo.crossAxesStats_input],... - 'callback','@textinput_call4; zScale=str2num(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); - -guiHandlesStats.crossAxesStats_text2 = uicontrol(PSstatsfig,'style','text','string','alpha','fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text2]); -guiHandlesStats.crossAxesStats_input2 = uicontrol(PSstatsfig,'style','edit','string',[num2str(zTransparency)],'fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','Position',[posInfo.crossAxesStats_input2],... - 'callback','@textinput_call4; zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); - -else - errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); - pause(2); -end - - -function textinput_call4(src,eventdata) -str=get(src,'String'); - if isempty(str2num(str)) - set(src,'string','0'); - warndlg('Input must be numerical'); - end -end - -function selection(src,event) - val = c.Value; - str = c.String; - str{val}; - % disp(['Selection: ' str{val}]); +%% PSplotStats - UI control for flight statistics + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if ~isempty(filenameA) || ~isempty(filenameB) + +PSstatsfig=figure(6); +set(PSstatsfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); +set(PSstatsfig, 'NumberTitle', 'off'); +set(PSstatsfig, 'Name', ['PIDscope (' PsVersion ') - Flight stats']); +set(PSstatsfig, 'InvertHardcopy', 'off'); +set(PSstatsfig,'color',bgcolor) + +TooltipString_degsecStick=['Plots rate curve (Histograms Figs) in terms of degs per sec per stick-travel units, or how fast one''s rates change across stick travel ']; +TooltipString_crossAxesStats=['Selects from several plotting options, from basic histograms of stick use per flight, to means',... + newline 'and various between-axes representations.',... + newline ' ',... + newline 'Histograms:',... + newline 'Basic descriptive stats of the flight behavior.',... + newline ' ',... + newline 'Mean +/-SD:',... + newline 'Bars represent the mean/average and lines represent the standard deviation (a measure of variability), ||=absolute value or unsigned average.',... + newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer.',... + newline ' ',... + newline 'Topographic Mode1/2 plots:',... + newline 'The plots show the topographic view (like looking down at your radio) of how the sticks were moved during the flight',... + newline 'Line color represents throttle acceleration in red and deceleration in blue, with higher values more saturated.',... + newline 'If throttle is neither accelerating nor decelerating the line color is white.',... + newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer, and refreshing the Flight stats tool.',... + newline ' ',... + newline 'Axes x Throttle plots:',... + newline 'Like the mode plots, line color represents throttle acceleration in red and deceleration in blue, with higher values more saturated.',... + newline 'If throttle is neither accelerating nor decelerating the line color is white.',... + newline 'The ''Mode and axes X throttle topography plots'' are useful for examining patterns in flight behavior,',... + newline 'For example, flight behaviour associated with optimal lap times in a race, or the qualities or asymmetries in one''s flying style ',... + newline 'Note: you can select out portions of the data to be examined if desired, by adjusting the selection window in the log viewer.']; +TooltipString_statScale=['For ''Mode and axes X throttle topography plots'':',... + newline 'Scale: Z-axis (line color) scale, from 1 to infinity, with higher numbers yielding a wider scale,',... + newline 'giving greater distinction between lines, where only the fastest movements (highest acceleration/deceleration) become visible.']; +TooltipString_statAlpha=['For ''Mode and axes X throttle topography plots'':',... + newline 'Alpha: line transparency, from 0 (fully transparent) to 1 (not transparent)']; +updateStats=0; + +zScale=1; +zTransparency=1; + +PSstatsfig_pos = get(PSstatsfig, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); +fontsz5=round(screensz_multiplier*prop_max_screen); + +clear posInfo.statsPos +cols=[0.06 0.54]; +rows=[0.75 0.52 0.29 0.06]; +k=0; +for c=1:2 + for r=1:4 + k=k+1; + posInfo.statsPos(k,:)=[cols(c) rows(r) 0.39 0.18]; + end +end + +posInfo.saveFig5=[.065 .945 .06 .04]; +posInfo.refresh3=[.135 .945 .06 .04]; +posInfo.degsecStick=[.20 .945 .09 .04]; +posInfo.crossAxesStats=[.29 .945 .08 .04]; + +posInfo.crossAxesStats_text = [.385 .965 .03 .03]; +posInfo.crossAxesStats_input = [.385 .945 .03 .03]; +posInfo.crossAxesStats_text2 = [.42 .965 .03 .03]; +posInfo.crossAxesStats_input2 = [.42 .945 .03 .03]; + +statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... + 'BackgroundColor',[.95 .95 .95],... + 'Position',[.06 .935 .40 .06]); + +guiHandlesStats.saveFig5 = uicontrol(PSstatsfig,'string','Save Fig','fontsize',fontsz5,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig5],... + 'callback','set(guiHandlesStats.saveFig5, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesStats.saveFig5, ''FontWeight'', ''normal'');'); +set(guiHandlesStats.saveFig5, 'BackgroundColor', [.8 .8 .8]); + +guiHandlesStats.refresh = uicontrol(PSstatsfig,'string','Refresh','fontsize',fontsz5,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh3],... + 'callback','updateStats=1;PSplotStats;'); +set(guiHandlesStats.refresh, 'BackgroundColor', [1 1 .2]); + +guiHandlesStats.degsecStick =uicontrol(PSstatsfig,'Style','checkbox','String','rate of change','fontsize',fontsz5,'TooltipString',[TooltipString_degsecStick],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.degsecStick],'callback','if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); +guiHandlesStats.crossAxesStats =uicontrol(PSstatsfig,'Style','popupmenu','String',{'Histograms'; 'Mean & Standard Deviation'; 'Mode 1 topography'; 'Mode 2 topography'; 'Axes X Throttle'},'fontsize',fontsz5,'TooltipString',[TooltipString_crossAxesStats],... + 'units','normalized','BackgroundColor',[1 1 1 ],'Position',[posInfo.crossAxesStats],'callback','@selection; if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); +%guiHandlesStats.crossAxesStats.Value=0; + +guiHandlesStats.crossAxesStats_text = uicontrol(PSstatsfig,'style','text','string','scale','fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text]); +guiHandlesStats.crossAxesStats_input = uicontrol(PSstatsfig,'style','edit','string',[num2str(zScale)],'fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','Position',[posInfo.crossAxesStats_input],... + 'callback','@textinput_call4; zScale=str2num(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); + +guiHandlesStats.crossAxesStats_text2 = uicontrol(PSstatsfig,'style','text','string','alpha','fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text2]); +guiHandlesStats.crossAxesStats_input2 = uicontrol(PSstatsfig,'style','edit','string',[num2str(zTransparency)],'fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','Position',[posInfo.crossAxesStats_input2],... + 'callback','@textinput_call4; zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); + +else + errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); + pause(2); +end + + +function textinput_call4(src,eventdata) +str=get(src,'String'); + if isempty(str2num(str)) + set(src,'string','0'); + warndlg('Input must be numerical'); + end +end + +function selection(src,event) + val = c.Value; + str = c.String; + str{val}; + % disp(['Selection: ' str{val}]); end \ No newline at end of file diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 4d0bafb..085c57e 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -1,146 +1,146 @@ -%% PStuneUIcontrol - ui controls for tuning-specific parameters - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if exist('fnameMaster','var') && ~isempty(fnameMaster) - -PStunefig=figure(4); -set(PStunefig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PStunefig, 'NumberTitle', 'on'); -set(PStunefig, 'Name', ['PIDscope (' PsVersion ') - Step Response Tool']); -set(PStunefig, 'InvertHardcopy', 'off'); -set(PStunefig,'color',bgcolor) - -updateStep=0; - -TooltipString_steprun=['Runs step response analysis.',... - newline, 'Warning: Set subsampling dropdown @ or < medium for faster processing.']; -TooltipString_minRate=['Input the minimum rate of rotation for calculating the step response (lower bound must be > 0 but lower than upper bound).',... - newline, 'Really low values may yield more noisy contributions to the data, whereas higher values limit the total data used.',... - newline, 'The default of 40deg/s should be sufficient in most cases, but if N is low, try setting this to lower']; -TooltipString_maxRate=['Input the maximum rate of rotation for for calculating the step response (upper bound must be greater than lower bound).',... - newline, 'This also marks the lower bound for step resp plots associated with the ''snap maneuvers'' selection.',... - newline, 'The default of 500deg/s is sufficient in most cases']; -TooltipString_FastStepResp=['Plots the step response associated with snap maneuvers, whose lower cutoff is defined by upper deg/s dropdown.',... - newline, 'Note: this requires that the log contains maneuvers > the selected upper deg/s, else the plot is left blank']; -TooltipString_fileListWindowStep=['List of files available. Click to select which files to run']; -TooltipString_clearPlot=['Clears lines from all subplots']; - -fcntSR = 0; - -clear posInfo.TparamsPos -cols=[0.05 0.45 0.58 0.73]; -rows=[0.69 0.395 0.1]; -k=0; -for c=1 : size(cols,2) - for r=1 : size(rows,2) - k=k+1; - if c == 1 - posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.4 0.245]; - else - posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.11 0.245]; - end - end -end - -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; -rh = .030; rs = .034; - -posInfo.fileListWindowStep= [cpL+.003 .660 cpW-.006 .24]; -posInfo.run4= [cpL+.006 .625 cpW/2-.006 rh]; -posInfo.clearPlots= [cpL+cpW/2 .625 cpW/2-.006 rh]; -posInfo.saveFig4= [cpL+.006 .591 cpW/2-.006 rh]; -posInfo.saveSettings4= [cpL+cpW/2 .591 cpW/2-.006 rh]; -posInfo.smooth_tuning= [cpL+.003 .557 cpW-.006 rh]; -posInfo.plotR= [cpL+.005 .523 .035 .025]; -posInfo.plotP= [cpL+.04 .523 .035 .025]; -posInfo.plotY= [cpL+.075 .523 .035 .025]; -posInfo.RPYcombo= [cpL+.005 .489 cpW-.01 .025]; -posInfo.Ycorrection= [cpL+.005 .455 cpW-.01 .025]; -posInfo.maxYStepInput= [cpL+.005 .421 cpW/3 .025]; -posInfo.maxYStepTxt= [cpL+cpW/3+.005 .421 cpW/2 .025]; - -guiHandlesTune.tuneCrtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... - 'Position',[cpL .41 cpW .51]); - -guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... - 'callback','PStuningParams;'); -set(guiHandlesTune.run4, 'ForegroundColor', colRun); - -guiHandlesTune.fileListWindowStep = uicontrol(PStunefig,'Style','listbox','string',[fnameMaster],'max',10,'min',1,... - 'fontsize',fontsz,'TooltipString', [TooltipString_fileListWindowStep],'units','normalized','Position', [posInfo.fileListWindowStep],'callback','@selection2;'); -set(guiHandlesTune.fileListWindowStep, 'Value', 1); - -guiHandlesTune.saveFig4 = uicontrol(PStunefig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig4],... - 'callback','set(guiHandlesTune.saveFig4, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesTune.saveFig4, ''FontWeight'', ''normal'');'); -set(guiHandlesTune.saveFig4, 'ForegroundColor', saveCol); - -guiHandlesTune.saveSettings = uicontrol(PStunefig,'string','Save Settings','fontsize',fontsz, 'TooltipString',['Save current settings to PIDscope defaults' ], 'units','normalized','Position',[posInfo.saveSettings4],... - 'callback','set(guiHandlesTune.saveSettings, ''FontWeight'', ''bold'');PSsaveSettings; set(guiHandlesTune.saveSettings, ''FontWeight'', ''normal'');'); -set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); - -guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); - -guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); -set(guiHandlesTune.plotP, 'Value', 1); - -guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); -set(guiHandlesTune.plotY, 'Value', 0); - -guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... - 'callback','set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); -set(guiHandlesTune.clearPlots, 'ForegroundColor', cautionCol); - -guiHandlesTune.Ycorrection =uicontrol(PStunefig,'Style','checkbox','String','Y correction','fontsize',fontsz,'TooltipString', ['Y axis offset correction '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Ycorrection],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); -set(guiHandlesTune.Ycorrection, 'Value', 0); - -guiHandlesTune.RPYcombo =uicontrol(PStunefig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'TooltipString', ['Plot RPY in same panel '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); -set(guiHandlesTune.RPYcombo, 'Value', 0); - -guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); -guiHandlesTune.maxYStepInput = uicontrol(PStunefig,'style','edit','string','1.75','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','Position',[posInfo.maxYStepInput],... - 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0;PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal'') ;PStuningParams; '); - -guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothing off' 'smoothing low' 'smoothing medium' 'smoothing high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... - 'callback','@selection2;'); -set(guiHandlesTune.smoothFactor_select, 'Value', 1); - -try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end -try set(guiHandlesTune.plotP, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotP')))), catch, set(guiHandlesTune.plotP, 'Value', 1), end -try set(guiHandlesTune.plotY, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotY')))), catch, set(guiHandlesTune.plotY, 'Value', 1), end -try set(guiHandlesTune.RPYcombo, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-SinglePanel')))), catch, set(guiHandlesTune.RPYcombo, 'Value', 0), end -try set(guiHandlesTune.maxYStepInput, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-Ymax'))))), catch, end - -else - warndlg('Please select file(s)'); -end - -% functions -function textinput_call3(src,eventdata) -str=get(src,'String'); - if isempty(str2num(str)) - set(src,'string','0'); - warndlg('Input must be numerical'); - end -end - -% functions -function selection2(src,event) - val = c.Value; - str = c.String; - str{val}; - % disp(['Selection: ' str{val}]); -end - +%% PStuneUIcontrol - ui controls for tuning-specific parameters + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if exist('fnameMaster','var') && ~isempty(fnameMaster) + +PStunefig=figure(4); +set(PStunefig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); +set(PStunefig, 'NumberTitle', 'on'); +set(PStunefig, 'Name', ['PIDscope (' PsVersion ') - Step Response Tool']); +set(PStunefig, 'InvertHardcopy', 'off'); +set(PStunefig,'color',bgcolor) + +updateStep=0; + +TooltipString_steprun=['Runs step response analysis.',... + newline, 'Warning: Set subsampling dropdown @ or < medium for faster processing.']; +TooltipString_minRate=['Input the minimum rate of rotation for calculating the step response (lower bound must be > 0 but lower than upper bound).',... + newline, 'Really low values may yield more noisy contributions to the data, whereas higher values limit the total data used.',... + newline, 'The default of 40deg/s should be sufficient in most cases, but if N is low, try setting this to lower']; +TooltipString_maxRate=['Input the maximum rate of rotation for for calculating the step response (upper bound must be greater than lower bound).',... + newline, 'This also marks the lower bound for step resp plots associated with the ''snap maneuvers'' selection.',... + newline, 'The default of 500deg/s is sufficient in most cases']; +TooltipString_FastStepResp=['Plots the step response associated with snap maneuvers, whose lower cutoff is defined by upper deg/s dropdown.',... + newline, 'Note: this requires that the log contains maneuvers > the selected upper deg/s, else the plot is left blank']; +TooltipString_fileListWindowStep=['List of files available. Click to select which files to run']; +TooltipString_clearPlot=['Clears lines from all subplots']; + +fcntSR = 0; + +clear posInfo.TparamsPos +cols=[0.05 0.45 0.58 0.73]; +rows=[0.69 0.395 0.1]; +k=0; +for c=1 : size(cols,2) + for r=1 : size(rows,2) + k=k+1; + if c == 1 + posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.4 0.245]; + else + posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.11 0.245]; + end + end +end + +% Control panel layout (consistent with Log Viewer cpL/cpW) +cpL = .875; cpW = .12; +rh = .030; rs = .034; + +posInfo.fileListWindowStep= [cpL+.003 .660 cpW-.006 .24]; +posInfo.run4= [cpL+.006 .625 cpW/2-.006 rh]; +posInfo.clearPlots= [cpL+cpW/2 .625 cpW/2-.006 rh]; +posInfo.saveFig4= [cpL+.006 .591 cpW/2-.006 rh]; +posInfo.saveSettings4= [cpL+cpW/2 .591 cpW/2-.006 rh]; +posInfo.smooth_tuning= [cpL+.003 .557 cpW-.006 rh]; +posInfo.plotR= [cpL+.005 .523 .035 .025]; +posInfo.plotP= [cpL+.04 .523 .035 .025]; +posInfo.plotY= [cpL+.075 .523 .035 .025]; +posInfo.RPYcombo= [cpL+.005 .489 cpW-.01 .025]; +posInfo.Ycorrection= [cpL+.005 .455 cpW-.01 .025]; +posInfo.maxYStepInput= [cpL+.005 .421 cpW/3 .025]; +posInfo.maxYStepTxt= [cpL+cpW/3+.005 .421 cpW/2 .025]; + +guiHandlesTune.tuneCrtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... + 'BackgroundColor',[.95 .95 .95],... + 'Position',[cpL .41 cpW .51]); + +guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... + 'callback','PStuningParams;'); +set(guiHandlesTune.run4, 'ForegroundColor', colRun); + +guiHandlesTune.fileListWindowStep = uicontrol(PStunefig,'Style','listbox','string',[fnameMaster],'max',10,'min',1,... + 'fontsize',fontsz,'TooltipString', [TooltipString_fileListWindowStep],'units','normalized','Position', [posInfo.fileListWindowStep],'callback','@selection2;'); +set(guiHandlesTune.fileListWindowStep, 'Value', 1); + +guiHandlesTune.saveFig4 = uicontrol(PStunefig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig4],... + 'callback','set(guiHandlesTune.saveFig4, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesTune.saveFig4, ''FontWeight'', ''normal'');'); +set(guiHandlesTune.saveFig4, 'ForegroundColor', saveCol); + +guiHandlesTune.saveSettings = uicontrol(PStunefig,'string','Save Settings','fontsize',fontsz, 'TooltipString',['Save current settings to PIDscope defaults' ], 'units','normalized','Position',[posInfo.saveSettings4],... + 'callback','set(guiHandlesTune.saveSettings, ''FontWeight'', ''bold'');PSsaveSettings; set(guiHandlesTune.saveSettings, ''FontWeight'', ''normal'');'); +set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); + +guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); + +guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.plotP, 'Value', 1); + +guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.plotY, 'Value', 0); + +guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... + 'callback','set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.clearPlots, 'ForegroundColor', cautionCol); + +guiHandlesTune.Ycorrection =uicontrol(PStunefig,'Style','checkbox','String','Y correction','fontsize',fontsz,'TooltipString', ['Y axis offset correction '],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Ycorrection],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); +set(guiHandlesTune.Ycorrection, 'Value', 0); + +guiHandlesTune.RPYcombo =uicontrol(PStunefig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'TooltipString', ['Plot RPY in same panel '],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); +set(guiHandlesTune.RPYcombo, 'Value', 0); + +guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); +guiHandlesTune.maxYStepInput = uicontrol(PStunefig,'style','edit','string','1.75','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','Position',[posInfo.maxYStepInput],... + 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0;PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal'') ;PStuningParams; '); + +guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothing off' 'smoothing low' 'smoothing medium' 'smoothing high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... + 'callback','@selection2;'); +set(guiHandlesTune.smoothFactor_select, 'Value', 1); + +try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end +try set(guiHandlesTune.plotP, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotP')))), catch, set(guiHandlesTune.plotP, 'Value', 1), end +try set(guiHandlesTune.plotY, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotY')))), catch, set(guiHandlesTune.plotY, 'Value', 1), end +try set(guiHandlesTune.RPYcombo, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-SinglePanel')))), catch, set(guiHandlesTune.RPYcombo, 'Value', 0), end +try set(guiHandlesTune.maxYStepInput, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-Ymax'))))), catch, end + +else + warndlg('Please select file(s)'); +end + +% functions +function textinput_call3(src,eventdata) +str=get(src,'String'); + if isempty(str2num(str)) + set(src,'string','0'); + warndlg('Input must be numerical'); + end +end + +% functions +function selection2(src,event) + val = c.Value; + str = c.String; + str{val}; + % disp(['Selection: ' str{val}]); +end + diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 82beb4e..c9470d1 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -1,130 +1,130 @@ -%% PSviewerUIcontrol - - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - - -posInfo.checkbox0=[.1 .965 .1 .025]; -posInfo.checkbox1=[.1 .94 .1 .025]; -posInfo.checkbox2=[.18 .965 .1 .025]; -posInfo.checkbox3=[.18 .94 .1 .025]; -posInfo.checkbox4=[.26 .965 .1 .025]; -posInfo.checkbox5=[.26 .94 .1 .025]; -posInfo.checkbox6=[.34 .965 .1 .025]; -posInfo.checkbox7=[.34 .94 .1 .025]; -posInfo.checkbox8=[.42 .965 .1 .025]; -posInfo.checkbox9=[.42 .94 .1 .025]; -posInfo.checkbox13=[.50 .965 .06 .025];%m4 -posInfo.checkbox12=[.50 .94 .06 .025];%m3 -posInfo.checkbox11=[.58 .965 .06 .025];%m2 -posInfo.checkbox10=[.58 .94 .06 .025]; %m1 -posInfo.checkbox14=[.66 .965 .06 .025]; -posInfo.checkbox15=[.66 .94 .06 .025]; - -posInfo.maxYtext = [.70 .965 .04 .025]; -posInfo.maxYinput = [.735 .965 .025 .025]; - -posInfo.nCols_text = [.70 .94 .04 .025]; -posInfo.nCols_input = [.735 .94 .025 .025]; - -posInfo.YTstick = [cpL+.005 vPos-0.39 .05 .085]; -posInfo.RPstick = [cpL+cpW/2 vPos-0.39 .05 .085]; - -posInfo.linepos1=[0.095 0.685 0.77 0.21]; -posInfo.linepos2=[0.095 0.47 0.77 0.21]; -posInfo.linepos3=[0.095 0.255 0.77 0.21]; - -posInfo.linepos4=[0.095 0.1 0.77 0.11];% - -fullszPlot = [0.095 0.255 0.77 0.63]; - - -checkpanel = uipanel('Title','','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... - 'Position',[.096 .932 .68 .065]); - -guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox0],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox1=uicontrol(PSfig,'Style','checkbox','String','Gyro','fontsize',fontsz,'ForegroundColor',[linec.col1],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox1],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox2=uicontrol(PSfig,'Style','checkbox','String','P-term','fontsize',fontsz,'ForegroundColor',[linec.col2],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox2],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox3=uicontrol(PSfig,'Style','checkbox','String','I-term','fontsize',fontsz,'ForegroundColor',[linec.col3],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox3],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox4=uicontrol(PSfig,'Style','checkbox','String','D-term (prefilt)','fontsize',fontsz,'ForegroundColor',[linec.col4],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox4],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); -guiHandles.checkbox5=uicontrol(PSfig,'Style','checkbox','String','D-term','fontsize',fontsz,'ForegroundColor',[linec.col5],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox5],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); -guiHandles.checkbox6=uicontrol(PSfig,'Style','checkbox','String','F-term','fontsize',fontsz,'ForegroundColor',[linec.col6],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox6],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox7=uicontrol(PSfig,'Style','checkbox','String','Set point','fontsize',fontsz,'ForegroundColor',[linec.col7],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox7],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox8=uicontrol(PSfig,'Style','checkbox','String','PID sum','fontsize',fontsz,'ForegroundColor',[linec.col8],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox8],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox9=uicontrol(PSfig,'Style','checkbox','String','PID error','fontsize',fontsz,'ForegroundColor',[linec.col9],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox9],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox10=uicontrol(PSfig,'Style','checkbox','String','Motor 1','fontsize',fontsz,'ForegroundColor',[linec.col10],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox10],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); -guiHandles.checkbox11=uicontrol(PSfig,'Style','checkbox','String','Motor 2','fontsize',fontsz,'ForegroundColor',[linec.col11],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox11],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox12=uicontrol(PSfig,'Style','checkbox','String','Motor 3','fontsize',fontsz,'ForegroundColor',[linec.col12],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox12],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox13=uicontrol(PSfig,'Style','checkbox','String','Motor 4','fontsize',fontsz,'ForegroundColor',[linec.col13],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox13],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox14=uicontrol(PSfig,'Style','checkbox','String','Throttle','fontsize',fontsz,'ForegroundColor',[linec.col14],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox14],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); - -set(guiHandles.checkbox1, 'Value', 1); -set(guiHandles.checkbox7, 'Value', 1); -set(guiHandles.checkbox10, 'Value', 1); -set(guiHandles.checkbox11, 'Value', 1); -set(guiHandles.checkbox12, 'Value', 1); -set(guiHandles.checkbox13, 'Value', 1); -set(guiHandles.checkbox14, 'Value', 1); - -guiHandles.checkbox15=uicontrol(PSfig,'Style','checkbox','String','All','fontsize',fontsz,'TooltipString', ['Plot or clear all lines '],'ForegroundColor',[linec.col15],'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); - -TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; -guiHandles.FileNum = uicontrol(PSfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... - 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameAText],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); - - -fileIdx = get(guiHandles.FileNum, 'Value'); -if exist('tta','var') && iscell(tta) && numel(tta) >= fileIdx - if numel(epoch1_A) < fileIdx || numel(epoch2_A) < fileIdx - epoch1_A(fileIdx)=tta{fileIdx}(1)/us2sec; - epoch2_A(fileIdx)=tta{fileIdx}(end)/us2sec; - end -end - -% set IND for data subset. Updated in logviewer. -for f = 1 : Nfiles - tIND{f} = tta{f} > (epoch1_A(f)*us2sec) & tta{f} < (epoch2_A(f)*us2sec); -end - -maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; -guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); -guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... - 'callback','PSplotLogViewer; '); - -guiHandles.nCols_text = uicontrol(PSfig,'style','text','string','N colors','fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.nCols_text]); -guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... - 'callback','if str2num(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2num(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2num(get(guiHandles.nCols_input, ''String''))); '); - -subplot('position',[posInfo.YTstick]); -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on -subplot('position',[posInfo.RPstick]) -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on - -try set(guiHandles.maxY_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ymax'))))), catch, end -try set(guiHandles.nCols_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ncolors'))))), catch, end - - +%% PSviewerUIcontrol + + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + + +posInfo.checkbox0=[.1 .965 .1 .025]; +posInfo.checkbox1=[.1 .94 .1 .025]; +posInfo.checkbox2=[.18 .965 .1 .025]; +posInfo.checkbox3=[.18 .94 .1 .025]; +posInfo.checkbox4=[.26 .965 .1 .025]; +posInfo.checkbox5=[.26 .94 .1 .025]; +posInfo.checkbox6=[.34 .965 .1 .025]; +posInfo.checkbox7=[.34 .94 .1 .025]; +posInfo.checkbox8=[.42 .965 .1 .025]; +posInfo.checkbox9=[.42 .94 .1 .025]; +posInfo.checkbox13=[.50 .965 .06 .025];%m4 +posInfo.checkbox12=[.50 .94 .06 .025];%m3 +posInfo.checkbox11=[.58 .965 .06 .025];%m2 +posInfo.checkbox10=[.58 .94 .06 .025]; %m1 +posInfo.checkbox14=[.66 .965 .06 .025]; +posInfo.checkbox15=[.66 .94 .06 .025]; + +posInfo.maxYtext = [.70 .965 .04 .025]; +posInfo.maxYinput = [.735 .965 .025 .025]; + +posInfo.nCols_text = [.70 .94 .04 .025]; +posInfo.nCols_input = [.735 .94 .025 .025]; + +posInfo.YTstick = [cpL+.005 vPos-0.39 .05 .085]; +posInfo.RPstick = [cpL+cpW/2 vPos-0.39 .05 .085]; + +posInfo.linepos1=[0.095 0.685 0.77 0.21]; +posInfo.linepos2=[0.095 0.47 0.77 0.21]; +posInfo.linepos3=[0.095 0.255 0.77 0.21]; + +posInfo.linepos4=[0.095 0.1 0.77 0.11];% + +fullszPlot = [0.095 0.255 0.77 0.63]; + + +checkpanel = uipanel('Title','','FontSize',fontsz,... + 'BackgroundColor',[.95 .95 .95],... + 'Position',[.096 .932 .68 .065]); + +guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox0],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox1=uicontrol(PSfig,'Style','checkbox','String','Gyro','fontsize',fontsz,'ForegroundColor',[linec.col1],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox1],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox2=uicontrol(PSfig,'Style','checkbox','String','P-term','fontsize',fontsz,'ForegroundColor',[linec.col2],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox2],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox3=uicontrol(PSfig,'Style','checkbox','String','I-term','fontsize',fontsz,'ForegroundColor',[linec.col3],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox3],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox4=uicontrol(PSfig,'Style','checkbox','String','D-term (prefilt)','fontsize',fontsz,'ForegroundColor',[linec.col4],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox4],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); +guiHandles.checkbox5=uicontrol(PSfig,'Style','checkbox','String','D-term','fontsize',fontsz,'ForegroundColor',[linec.col5],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox5],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); +guiHandles.checkbox6=uicontrol(PSfig,'Style','checkbox','String','F-term','fontsize',fontsz,'ForegroundColor',[linec.col6],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox6],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox7=uicontrol(PSfig,'Style','checkbox','String','Set point','fontsize',fontsz,'ForegroundColor',[linec.col7],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox7],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox8=uicontrol(PSfig,'Style','checkbox','String','PID sum','fontsize',fontsz,'ForegroundColor',[linec.col8],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox8],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox9=uicontrol(PSfig,'Style','checkbox','String','PID error','fontsize',fontsz,'ForegroundColor',[linec.col9],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox9],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox10=uicontrol(PSfig,'Style','checkbox','String','Motor 1','fontsize',fontsz,'ForegroundColor',[linec.col10],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox10],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); +guiHandles.checkbox11=uicontrol(PSfig,'Style','checkbox','String','Motor 2','fontsize',fontsz,'ForegroundColor',[linec.col11],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox11],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox12=uicontrol(PSfig,'Style','checkbox','String','Motor 3','fontsize',fontsz,'ForegroundColor',[linec.col12],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox12],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox13=uicontrol(PSfig,'Style','checkbox','String','Motor 4','fontsize',fontsz,'ForegroundColor',[linec.col13],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox13],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkbox14=uicontrol(PSfig,'Style','checkbox','String','Throttle','fontsize',fontsz,'ForegroundColor',[linec.col14],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox14],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); + +set(guiHandles.checkbox1, 'Value', 1); +set(guiHandles.checkbox7, 'Value', 1); +set(guiHandles.checkbox10, 'Value', 1); +set(guiHandles.checkbox11, 'Value', 1); +set(guiHandles.checkbox12, 'Value', 1); +set(guiHandles.checkbox13, 'Value', 1); +set(guiHandles.checkbox14, 'Value', 1); + +guiHandles.checkbox15=uicontrol(PSfig,'Style','checkbox','String','All','fontsize',fontsz,'TooltipString', ['Plot or clear all lines '],'ForegroundColor',[linec.col15],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); + +TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; +guiHandles.FileNum = uicontrol(PSfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... + 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameAText],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); + + +fileIdx = get(guiHandles.FileNum, 'Value'); +if exist('tta','var') && iscell(tta) && numel(tta) >= fileIdx + if numel(epoch1_A) < fileIdx || numel(epoch2_A) < fileIdx + epoch1_A(fileIdx)=tta{fileIdx}(1)/us2sec; + epoch2_A(fileIdx)=tta{fileIdx}(end)/us2sec; + end +end + +% set IND for data subset. Updated in logviewer. +for f = 1 : Nfiles + tIND{f} = tta{f} > (epoch1_A(f)*us2sec) & tta{f} < (epoch2_A(f)*us2sec); +end + +maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; +guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); +guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... + 'callback','PSplotLogViewer; '); + +guiHandles.nCols_text = uicontrol(PSfig,'style','text','string','N colors','fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.nCols_text]); +guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... + 'callback','if str2num(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2num(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2num(get(guiHandles.nCols_input, ''String''))); '); + +subplot('position',[posInfo.YTstick]); +set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); +box on +subplot('position',[posInfo.RPstick]) +set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); +box on + +try set(guiHandles.maxY_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ymax'))))), catch, end +try set(guiHandles.nCols_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ncolors'))))), catch, end + + diff --git a/src/util/PSerrorMessages.m b/src/util/PSerrorMessages.m index 9519630..6348baf 100644 --- a/src/util/PSerrorMessages.m +++ b/src/util/PSerrorMessages.m @@ -1,20 +1,20 @@ -function [errorMessage] = PSerrorMessages(message_string, err) -%% Displays Matlab error messages in a popup so users can report specific errors for development and debugging purposes -% - B. White -% -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- -% - -errorMessage = sprintf(['Error in ' message_string ' \n\n Message:\n%s'], err.message); -% Display pop up message and wait for user to click OK -% Print to command window. -fprintf(1, '%s\n', errorMessage); -uiwait(warndlg(errorMessage)); -%close % in a previous version, the program would close following an error, but this was problematic -end - +function [errorMessage] = PSerrorMessages(message_string, err) +%% Displays Matlab error messages in a popup so users can report specific errors for development and debugging purposes +% - B. White +% +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- +% + +errorMessage = sprintf(['Error in ' message_string ' \n\n Message:\n%s'], err.message); +% Display pop up message and wait for user to click OK +% Print to command window. +fprintf(1, '%s\n', errorMessage); +uiwait(warndlg(errorMessage)); +%close % in a previous version, the program would close following an error, but this was problematic +end + diff --git a/src/util/PSsaveFig.m b/src/util/PSsaveFig.m index c880db1..f6b7ab8 100644 --- a/src/util/PSsaveFig.m +++ b/src/util/PSsaveFig.m @@ -1,57 +1,57 @@ -%% PSsaveFig - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -%% create saveDirectory -if exist('fnameMaster','var') && ~isempty(fnameMaster) - saveDirectory='PS_FIGS'; - saveDirectory = [saveDirectory '_' currentDate]; - - % Try logfile_directory first, fall back to configDir (Flatpak: home is read-only) - saveBase = ''; - try - cd(logfile_directory); - if ~isfolder(saveDirectory), mkdir(saveDirectory); end - % Verify directory is writable (Flatpak mounts home read-only) - testf = fullfile(saveDirectory, '.writetest'); - fid = fopen(testf, 'w'); fclose(fid); delete(testf); - saveBase = logfile_directory; - catch - try - cd(configDir); - if ~isfolder(saveDirectory), mkdir(saveDirectory); end - saveBase = configDir; - catch - end - end - - if isempty(saveBase) - warndlg('Cannot create save directory (file system may be read-only)'); - return; - end - -%% -set(gcf, 'pointer', 'watch') -cd(saveBase) -cd(saveDirectory) -FigDoesNotExist=1; -n=0; -while FigDoesNotExist, - n=n+1; - FigDoesNotExist=isfile([saveDirectory '-' int2str(n) '.png']); -end -figname=[saveDirectory '-' int2str(n)]; -saveas(gcf, [figname '.png'] ); -try, print(figname,'-dpng','-r200'); catch, end - -set(gcf, 'pointer', 'arrow') -cd(configDir) - -else - warndlg('Please select file(s)'); +%% PSsaveFig + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +%% create saveDirectory +if exist('fnameMaster','var') && ~isempty(fnameMaster) + saveDirectory='PS_FIGS'; + saveDirectory = [saveDirectory '_' currentDate]; + + % Try logfile_directory first, fall back to configDir (Flatpak: home is read-only) + saveBase = ''; + try + cd(logfile_directory); + if ~isfolder(saveDirectory), mkdir(saveDirectory); end + % Verify directory is writable (Flatpak mounts home read-only) + testf = fullfile(saveDirectory, '.writetest'); + fid = fopen(testf, 'w'); fclose(fid); delete(testf); + saveBase = logfile_directory; + catch + try + cd(configDir); + if ~isfolder(saveDirectory), mkdir(saveDirectory); end + saveBase = configDir; + catch + end + end + + if isempty(saveBase) + warndlg('Cannot create save directory (file system may be read-only)'); + return; + end + +%% +set(gcf, 'pointer', 'watch') +cd(saveBase) +cd(saveDirectory) +FigDoesNotExist=1; +n=0; +while FigDoesNotExist, + n=n+1; + FigDoesNotExist=isfile([saveDirectory '-' int2str(n) '.png']); +end +figname=[saveDirectory '-' int2str(n)]; +saveas(gcf, [figname '.png'] ); +try, print(figname,'-dpng','-r200'); catch, end + +set(gcf, 'pointer', 'arrow') +cd(configDir) + +else + warndlg('Please select file(s)'); end \ No newline at end of file diff --git a/src/util/PSsaveSettings.m b/src/util/PSsaveSettings.m index 604483c..e8deff2 100644 --- a/src/util/PSsaveSettings.m +++ b/src/util/PSsaveSettings.m @@ -1,146 +1,146 @@ -%% PSsaveSettings - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -%% create saveDirectory -if exist('fnameMaster','var') && ~isempty(fnameMaster) - - -%% -set(gcf, 'pointer', 'watch') -try - cd(configDir) - clear defaults - if ~isfile('PSdefaults.txt') - clear var - var(1,:) = [{'firmware' 1}]; - var(2,:) = [{'LogViewer-SinglePanel' 0}]; - var(3,:) = [{'LogViewer-plotR' 1}]; - var(4,:) = [{'LogViewer-plotP' 1}]; - var(5,:) = [{'LogViewer-plotY' 1}]; - var(6,:) = [{'LogViewer-lineSmooth' 1}]; - var(7,:) = [{'LogViewer-lineWidth' 3}]; - var(8,:) = [{'LogViewer-Ymax' 500}]; - var(9,:) = [{'LogViewer-Ncolors' 8}]; - var(10,:) = [{'spec2D-term1' 1}]; - var(11,:) = [{'spec2D-term2' 2}]; - var(12,:) = [{'spec2D-smoothing' 3}]; - var(13,:) = [{'spec2D-delay' 1}]; - var(14,:) = [{'spec2D-plotR' 1}]; - var(15,:) = [{'spec2D-plotP' 1}]; - var(16,:) = [{'spec2D-plotY' 1}]; - var(17,:) = [{'spec2D-SinglePanel' 0}]; - var(18,:) = [{'FreqXthr-Column1' 3}]; - var(19,:) = [{'FreqXthr-Column2' 2}]; - var(20,:) = [{'FreqXthr-Column3' 8}]; - var(21,:) = [{'FreqXthr-Column4' 7}]; - var(22,:) = [{'FreqXthr-Preset' 1}]; - var(23,:) = [{'FreqXthr-Colormap' 3}]; - var(24,:) = [{'FreqXthr-Smoothing' 3}]; - var(25,:) = [{'FreqxTime-Preset' 2}]; - var(26,:) = [{'FreqxTime-FreqSmoothing' 2}]; - var(27,:) = [{'FreqxTime-TimeSmoothing' 2}]; - var(28,:) = [{'FreqxTime-Colormap' 3}]; - var(29,:) = [{'StepResp-plotR' 1}]; - var(30,:) = [{'StepResp-plotP' 1}]; - var(31,:) = [{'StepResp-plotY' 1}]; - var(32,:) = [{'StepResp-SinglePanel' 0}]; - var(33,:) = [{'StepResp-Ymax' 1.75}]; - - defaults = cell2table(var, 'VariableNames',{'Parameters' ; 'Values'}); - else - defaults = readtable('PSdefaults.txt'); - end -catch -end - -try - defaults.Values(1) = get(guiHandles.Firmware, 'Value'); - defaults.Values(2) = get(guiHandles.RPYcomboLV, 'Value'); - defaults.Values(3) = get(guiHandles.plotR, 'Value'); - defaults.Values(4) = get(guiHandles.plotP, 'Value'); - defaults.Values(5) = get(guiHandles.plotY, 'Value'); - defaults.Values(6) = get(guiHandles.lineSmooth, 'Value'); - defaults.Values(7) = get(guiHandles.linewidth, 'Value'); - defaults.Values(8) = str2num(get(guiHandles.maxY_input, 'String')); - defaults.Values(9) = str2num(get(guiHandles.nCols_input, 'String')); -catch -end -try - tmpSpecListVal = get(guiHandlesSpec2.SpecList, 'Value'); - defaults.Values(10) = tmpSpecListVal(1); - defaults.Values(11) = tmpSpecListVal(2); - defaults.Values(12) = get(guiHandlesSpec2.smoothFactor_select, 'Value'); - defaults.Values(13) = get(guiHandlesSpec2.Delay, 'Value'); - defaults.Values(14) = get(guiHandlesSpec2.plotR, 'Value'); - defaults.Values(15) = get(guiHandlesSpec2.plotP, 'Value'); - defaults.Values(16) = get(guiHandlesSpec2.plotY, 'Value'); - defaults.Values(17) = get(guiHandlesSpec2.RPYcomboSpec, 'Value'); -catch -end -try - defaults.Values(18) = get(guiHandlesSpec.SpecSelect{1}, 'Value'); - defaults.Values(19) = get(guiHandlesSpec.SpecSelect{2}, 'Value'); - defaults.Values(20) = get(guiHandlesSpec.SpecSelect{3}, 'Value'); - defaults.Values(21) = get(guiHandlesSpec.SpecSelect{4}, 'Value'); - defaults.Values(22) = get(guiHandlesSpec.specPresets, 'Value'); - defaults.Values(23) = get(guiHandlesSpec.ColormapSelect, 'Value'); - defaults.Values(24) = get(guiHandlesSpec.smoothFactor_select, 'Value'); -catch -end - -try - defaults.Values(25) = get(guiHandlesSpec3.SpecList, 'Value'); - defaults.Values(26) = get(guiHandlesSpec3.smoothFactor_select, 'Value'); - defaults.Values(27) = get(guiHandlesSpec3.subsampleFactor_select, 'Value'); - defaults.Values(28) = get(guiHandlesSpec3.ColormapSelect, 'Value'); -catch -end -try - defaults.Values(29) = get(guiHandlesTune.plotR, 'Value'); - defaults.Values(30) = get(guiHandlesTune.plotP, 'Value'); - defaults.Values(31) = get(guiHandlesTune.plotY, 'Value'); - defaults.Values(32) = get(guiHandlesTune.RPYcombo, 'Value'); - defaults.Values(33) = str2num(get(guiHandlesTune.maxYStepInput, 'String')); -catch -end - -try - writetable(defaults, 'PSdefaults') -catch -end - -try - fid = fopen('logfileDir.txt','r'); - logfile_directory = fscanf(fid, '%c'); - fclose(fid); -catch - logfile_directory = [pwd '/']; -end - -ldr = ['logfileDirectory: ' logfile_directory ]; - -try - defaults = readtable('PSdefaults.txt'); - a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) -catch - defaults = ' '; - a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) -end - -clear var - -set(gcf, 'pointer', 'arrow') - -else - warndlg('Please select file(s)'); +%% PSsaveSettings + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +%% create saveDirectory +if exist('fnameMaster','var') && ~isempty(fnameMaster) + + +%% +set(gcf, 'pointer', 'watch') +try + cd(configDir) + clear defaults + if ~isfile('PSdefaults.txt') + clear var + var(1,:) = [{'firmware' 1}]; + var(2,:) = [{'LogViewer-SinglePanel' 0}]; + var(3,:) = [{'LogViewer-plotR' 1}]; + var(4,:) = [{'LogViewer-plotP' 1}]; + var(5,:) = [{'LogViewer-plotY' 1}]; + var(6,:) = [{'LogViewer-lineSmooth' 1}]; + var(7,:) = [{'LogViewer-lineWidth' 3}]; + var(8,:) = [{'LogViewer-Ymax' 500}]; + var(9,:) = [{'LogViewer-Ncolors' 8}]; + var(10,:) = [{'spec2D-term1' 1}]; + var(11,:) = [{'spec2D-term2' 2}]; + var(12,:) = [{'spec2D-smoothing' 3}]; + var(13,:) = [{'spec2D-delay' 1}]; + var(14,:) = [{'spec2D-plotR' 1}]; + var(15,:) = [{'spec2D-plotP' 1}]; + var(16,:) = [{'spec2D-plotY' 1}]; + var(17,:) = [{'spec2D-SinglePanel' 0}]; + var(18,:) = [{'FreqXthr-Column1' 3}]; + var(19,:) = [{'FreqXthr-Column2' 2}]; + var(20,:) = [{'FreqXthr-Column3' 8}]; + var(21,:) = [{'FreqXthr-Column4' 7}]; + var(22,:) = [{'FreqXthr-Preset' 1}]; + var(23,:) = [{'FreqXthr-Colormap' 3}]; + var(24,:) = [{'FreqXthr-Smoothing' 3}]; + var(25,:) = [{'FreqxTime-Preset' 2}]; + var(26,:) = [{'FreqxTime-FreqSmoothing' 2}]; + var(27,:) = [{'FreqxTime-TimeSmoothing' 2}]; + var(28,:) = [{'FreqxTime-Colormap' 3}]; + var(29,:) = [{'StepResp-plotR' 1}]; + var(30,:) = [{'StepResp-plotP' 1}]; + var(31,:) = [{'StepResp-plotY' 1}]; + var(32,:) = [{'StepResp-SinglePanel' 0}]; + var(33,:) = [{'StepResp-Ymax' 1.75}]; + + defaults = cell2table(var, 'VariableNames',{'Parameters' ; 'Values'}); + else + defaults = readtable('PSdefaults.txt'); + end +catch +end + +try + defaults.Values(1) = get(guiHandles.Firmware, 'Value'); + defaults.Values(2) = get(guiHandles.RPYcomboLV, 'Value'); + defaults.Values(3) = get(guiHandles.plotR, 'Value'); + defaults.Values(4) = get(guiHandles.plotP, 'Value'); + defaults.Values(5) = get(guiHandles.plotY, 'Value'); + defaults.Values(6) = get(guiHandles.lineSmooth, 'Value'); + defaults.Values(7) = get(guiHandles.linewidth, 'Value'); + defaults.Values(8) = str2num(get(guiHandles.maxY_input, 'String')); + defaults.Values(9) = str2num(get(guiHandles.nCols_input, 'String')); +catch +end +try + tmpSpecListVal = get(guiHandlesSpec2.SpecList, 'Value'); + defaults.Values(10) = tmpSpecListVal(1); + defaults.Values(11) = tmpSpecListVal(2); + defaults.Values(12) = get(guiHandlesSpec2.smoothFactor_select, 'Value'); + defaults.Values(13) = get(guiHandlesSpec2.Delay, 'Value'); + defaults.Values(14) = get(guiHandlesSpec2.plotR, 'Value'); + defaults.Values(15) = get(guiHandlesSpec2.plotP, 'Value'); + defaults.Values(16) = get(guiHandlesSpec2.plotY, 'Value'); + defaults.Values(17) = get(guiHandlesSpec2.RPYcomboSpec, 'Value'); +catch +end +try + defaults.Values(18) = get(guiHandlesSpec.SpecSelect{1}, 'Value'); + defaults.Values(19) = get(guiHandlesSpec.SpecSelect{2}, 'Value'); + defaults.Values(20) = get(guiHandlesSpec.SpecSelect{3}, 'Value'); + defaults.Values(21) = get(guiHandlesSpec.SpecSelect{4}, 'Value'); + defaults.Values(22) = get(guiHandlesSpec.specPresets, 'Value'); + defaults.Values(23) = get(guiHandlesSpec.ColormapSelect, 'Value'); + defaults.Values(24) = get(guiHandlesSpec.smoothFactor_select, 'Value'); +catch +end + +try + defaults.Values(25) = get(guiHandlesSpec3.SpecList, 'Value'); + defaults.Values(26) = get(guiHandlesSpec3.smoothFactor_select, 'Value'); + defaults.Values(27) = get(guiHandlesSpec3.subsampleFactor_select, 'Value'); + defaults.Values(28) = get(guiHandlesSpec3.ColormapSelect, 'Value'); +catch +end +try + defaults.Values(29) = get(guiHandlesTune.plotR, 'Value'); + defaults.Values(30) = get(guiHandlesTune.plotP, 'Value'); + defaults.Values(31) = get(guiHandlesTune.plotY, 'Value'); + defaults.Values(32) = get(guiHandlesTune.RPYcombo, 'Value'); + defaults.Values(33) = str2num(get(guiHandlesTune.maxYStepInput, 'String')); +catch +end + +try + writetable(defaults, 'PSdefaults') +catch +end + +try + fid = fopen('logfileDir.txt','r'); + logfile_directory = fscanf(fid, '%c'); + fclose(fid); +catch + logfile_directory = [pwd '/']; +end + +ldr = ['logfileDirectory: ' logfile_directory ]; + +try + defaults = readtable('PSdefaults.txt'); + a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); + t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); + set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) +catch + defaults = ' '; + a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); + t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); + set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) +end + +clear var + +set(gcf, 'pointer', 'arrow') + +else + warndlg('Please select file(s)'); end \ No newline at end of file diff --git a/src/util/b2r.m b/src/util/b2r.m index 6376ea4..c9d2958 100644 --- a/src/util/b2r.m +++ b/src/util/b2r.m @@ -1,135 +1,135 @@ -function newmap = b2r(cmin_input,cmax_input) -%BLUEWHITERED Blue, white, and red color map. -% this matlab file is designed to draw anomaly figures. the color of -% the colorbar is from blue to white and then to red, corresponding to -% the anomaly values from negative to zero to positive, respectively. -% The color white always correspondes to value zero. -% -% You should input two values like caxis in matlab, that is the min and -% the max value of color values designed. e.g. colormap(b2r(-3,5)) -% -% the brightness of blue and red will change according to your setting, -% so that the brightness of the color corresponded to the color of his -% opposite number -% e.g. colormap(b2r(-3,6)) is from light blue to deep red -% e.g. colormap(b2r(-3,3)) is from deep blue to deep red -% -% I'd advise you to use colorbar first to make sure the caxis' cmax and cmin. -% Besides, there is also another similar colorbar named 'darkb2r', in which the -% color is darker. -% -% by Cunjie Zhang, 2011-3-14 -% find bugs ====> email : daisy19880411@126.com -% updated: Robert Beckman help to fix the bug when start point is zero, 2015-04-08 -% -% Examples: -% ------------------------------ -% figure -% peaks; -% colormap(b2r(-6,8)), colorbar, title('b2r') -% - - -%% check the input -if nargin ~= 2 ; - disp('input error'); - disp('input two variables, the range of caxis , for example : colormap(b2r(-3,3))'); -end - -if cmin_input >= cmax_input - disp('input error'); - disp('the color range must be from a smaller one to a larger one'); -end - -%% control the figure caxis -lims = get(gca, 'CLim'); % get figure caxis formation -caxis([cmin_input cmax_input]); - -%% color configuration : from blue to to white then to red - -red_top = [1 0 0]; -white_middle= [1 1 1]; -blue_bottom = [0 0 1]; - -%% color interpolation - -color_num = 251; -color_input = [blue_bottom; white_middle; red_top]; -oldsteps = linspace(-1, 1, size(color_input,1)); -newsteps = linspace(-1, 1, color_num); - -%% Category Discussion according to the cmin and cmax input - -% the color data will be remaped to color range from -max(abs(cmin_input),cmax_input) -% to max(abs(cmin_input),cmax_input) , and then squeeze the color data -% in order to make sure the blue and red color selected corresponded -% to their math values - -% for example : -% if b2r(-3,6) ,the color range is from light blue to deep red , so that -% the light blue valued at -3 correspondes to light red valued at 3 - - -%% Category Discussion according to the cmin and cmax input -% first : from negative to positive -% then : from positive to positive -% last : from negative to negative - -for j=1:3 - newmap_all(:,j) = min(max(transpose(interp1(oldsteps, color_input(:,j), newsteps)), 0), 1); -end - -if (cmin_input < 0) && (cmax_input > 0) ; - - - if abs(cmin_input) < cmax_input - - % |--------|---------|--------------------| - % -cmax cmin 0 cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - - elseif abs(cmin_input) >= cmax_input - - % |------------------|------|--------------| - % cmin 0 cmax -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); - end - - -elseif cmin_input >= 0 - - if lims(1) < 0 - disp('caution:') - disp('there are still values smaller than 0, but cmin is larger than 0.') - disp('some area will be in red color while it should be in blue color') - end - - % |-----------------|-------|-------------| - % -cmax 0 cmin cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - -elseif cmax_input <= 0 - - if lims(2) > 0 - disp('caution:') - disp('there are still values larger than 0, but cmax is smaller than 0.') - disp('some area will be in blue color while it should be in red color') - end - - % |------------|------|--------------------| - % cmin cmax 0 -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); -end - - - - - +function newmap = b2r(cmin_input,cmax_input) +%BLUEWHITERED Blue, white, and red color map. +% this matlab file is designed to draw anomaly figures. the color of +% the colorbar is from blue to white and then to red, corresponding to +% the anomaly values from negative to zero to positive, respectively. +% The color white always correspondes to value zero. +% +% You should input two values like caxis in matlab, that is the min and +% the max value of color values designed. e.g. colormap(b2r(-3,5)) +% +% the brightness of blue and red will change according to your setting, +% so that the brightness of the color corresponded to the color of his +% opposite number +% e.g. colormap(b2r(-3,6)) is from light blue to deep red +% e.g. colormap(b2r(-3,3)) is from deep blue to deep red +% +% I'd advise you to use colorbar first to make sure the caxis' cmax and cmin. +% Besides, there is also another similar colorbar named 'darkb2r', in which the +% color is darker. +% +% by Cunjie Zhang, 2011-3-14 +% find bugs ====> email : daisy19880411@126.com +% updated: Robert Beckman help to fix the bug when start point is zero, 2015-04-08 +% +% Examples: +% ------------------------------ +% figure +% peaks; +% colormap(b2r(-6,8)), colorbar, title('b2r') +% + + +%% check the input +if nargin ~= 2 ; + disp('input error'); + disp('input two variables, the range of caxis , for example : colormap(b2r(-3,3))'); +end + +if cmin_input >= cmax_input + disp('input error'); + disp('the color range must be from a smaller one to a larger one'); +end + +%% control the figure caxis +lims = get(gca, 'CLim'); % get figure caxis formation +caxis([cmin_input cmax_input]); + +%% color configuration : from blue to to white then to red + +red_top = [1 0 0]; +white_middle= [1 1 1]; +blue_bottom = [0 0 1]; + +%% color interpolation + +color_num = 251; +color_input = [blue_bottom; white_middle; red_top]; +oldsteps = linspace(-1, 1, size(color_input,1)); +newsteps = linspace(-1, 1, color_num); + +%% Category Discussion according to the cmin and cmax input + +% the color data will be remaped to color range from -max(abs(cmin_input),cmax_input) +% to max(abs(cmin_input),cmax_input) , and then squeeze the color data +% in order to make sure the blue and red color selected corresponded +% to their math values + +% for example : +% if b2r(-3,6) ,the color range is from light blue to deep red , so that +% the light blue valued at -3 correspondes to light red valued at 3 + + +%% Category Discussion according to the cmin and cmax input +% first : from negative to positive +% then : from positive to positive +% last : from negative to negative + +for j=1:3 + newmap_all(:,j) = min(max(transpose(interp1(oldsteps, color_input(:,j), newsteps)), 0), 1); +end + +if (cmin_input < 0) && (cmax_input > 0) ; + + + if abs(cmin_input) < cmax_input + + % |--------|---------|--------------------| + % -cmax cmin 0 cmax [cmin,cmax] + + start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); + newmap = squeeze(newmap_all(start_point:color_num,:)); + + elseif abs(cmin_input) >= cmax_input + + % |------------------|------|--------------| + % cmin 0 cmax -cmin [cmin,cmax] + + end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); + newmap = squeeze(newmap_all(1:end_point,:)); + end + + +elseif cmin_input >= 0 + + if lims(1) < 0 + disp('caution:') + disp('there are still values smaller than 0, but cmin is larger than 0.') + disp('some area will be in red color while it should be in blue color') + end + + % |-----------------|-------|-------------| + % -cmax 0 cmin cmax [cmin,cmax] + + start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); + newmap = squeeze(newmap_all(start_point:color_num,:)); + +elseif cmax_input <= 0 + + if lims(2) > 0 + disp('caution:') + disp('there are still values larger than 0, but cmax is smaller than 0.') + disp('some area will be in blue color while it should be in red color') + end + + % |------------|------|--------------------| + % cmin cmax 0 -cmin [cmin,cmax] + + end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); + newmap = squeeze(newmap_all(1:end_point,:)); +end + + + + + diff --git a/tests/run_tests.m b/tests/run_tests.m index b34f9b6..4b8f477 100644 --- a/tests/run_tests.m +++ b/tests/run_tests.m @@ -1,69 +1,69 @@ -% run_tests.m - PIDscope test runner for GNU Octave -% Usage: octave --no-gui --eval "run('tests/run_tests.m')" - -fprintf('\n=== PIDscope Test Suite ===\n\n'); - -% Setup paths -project_root = fileparts(fileparts(mfilename('fullpath'))); -addpath(project_root); -addpath(genpath(fullfile(project_root, 'src'))); -addpath(fullfile(project_root, 'tests')); - -% Load required packages -if exist('OCTAVE_VERSION', 'builtin') - pkg load signal; - pkg load statistics; -end - -% Collect test files -test_dir = fullfile(project_root, 'tests'); -test_files = dir(fullfile(test_dir, 'test_*.m')); - -total_pass = 0; -total_fail = 0; -total_skip = 0; -failed_files = {}; - -for i = 1:length(test_files) - fname = test_files(i).name; - fpath = fullfile(test_dir, fname); - fprintf('Running %s ... ', fname); - try - % Octave test() returns [n, nmax, nxfail, nskip] - % n = passed, nmax = total tests - [n, nmax, nxfail, nskip] = test(fpath, 'quiet'); - nfail = nmax - n - nxfail; - total_pass = total_pass + n; - total_fail = total_fail + nfail; - total_skip = total_skip + nskip; - if nfail > 0 - fprintf('FAIL (%d/%d passed, %d failed)\n', n, nmax, nfail); - failed_files{end+1} = fname; - elseif nmax == 0 - fprintf('no tests\n'); - else - fprintf('OK (%d passed', n); - if nskip > 0, fprintf(', %d skipped', nskip); end - fprintf(')\n'); - end - catch e - fprintf('ERROR: %s\n', e.message); - total_fail = total_fail + 1; - failed_files{end+1} = fname; - end -end - -fprintf('\n=== Results ===\n'); -fprintf('Passed: %d\n', total_pass); -fprintf('Failed: %d\n', total_fail); -fprintf('Skipped: %d\n', total_skip); - -if ~isempty(failed_files) - fprintf('\nFailed test files:\n'); - for i = 1:length(failed_files) - fprintf(' - %s\n', failed_files{i}); - end - fprintf('\nSOME TESTS FAILED\n'); -else - fprintf('\nALL TESTS PASSED\n'); -end +% run_tests.m - PIDscope test runner for GNU Octave +% Usage: octave --no-gui --eval "run('tests/run_tests.m')" + +fprintf('\n=== PIDscope Test Suite ===\n\n'); + +% Setup paths +project_root = fileparts(fileparts(mfilename('fullpath'))); +addpath(project_root); +addpath(genpath(fullfile(project_root, 'src'))); +addpath(fullfile(project_root, 'tests')); + +% Load required packages +if exist('OCTAVE_VERSION', 'builtin') + pkg load signal; + pkg load statistics; +end + +% Collect test files +test_dir = fullfile(project_root, 'tests'); +test_files = dir(fullfile(test_dir, 'test_*.m')); + +total_pass = 0; +total_fail = 0; +total_skip = 0; +failed_files = {}; + +for i = 1:length(test_files) + fname = test_files(i).name; + fpath = fullfile(test_dir, fname); + fprintf('Running %s ... ', fname); + try + % Octave test() returns [n, nmax, nxfail, nskip] + % n = passed, nmax = total tests + [n, nmax, nxfail, nskip] = test(fpath, 'quiet'); + nfail = nmax - n - nxfail; + total_pass = total_pass + n; + total_fail = total_fail + nfail; + total_skip = total_skip + nskip; + if nfail > 0 + fprintf('FAIL (%d/%d passed, %d failed)\n', n, nmax, nfail); + failed_files{end+1} = fname; + elseif nmax == 0 + fprintf('no tests\n'); + else + fprintf('OK (%d passed', n); + if nskip > 0, fprintf(', %d skipped', nskip); end + fprintf(')\n'); + end + catch e + fprintf('ERROR: %s\n', e.message); + total_fail = total_fail + 1; + failed_files{end+1} = fname; + end +end + +fprintf('\n=== Results ===\n'); +fprintf('Passed: %d\n', total_pass); +fprintf('Failed: %d\n', total_fail); +fprintf('Skipped: %d\n', total_skip); + +if ~isempty(failed_files) + fprintf('\nFailed test files:\n'); + for i = 1:length(failed_files) + fprintf(' - %s\n', failed_files{i}); + end + fprintf('\nSOME TESTS FAILED\n'); +else + fprintf('\nALL TESTS PASSED\n'); +end diff --git a/tests/test_PSSpec2d.m b/tests/test_PSSpec2d.m index 051bdd6..38258f0 100644 --- a/tests/test_PSSpec2d.m +++ b/tests/test_PSSpec2d.m @@ -1,42 +1,42 @@ -% test_PSSpec2d.m - Tests for FFT computation engine -% NOTE: PSSpec2d expects Y as ROW vector, F in kHz - -%!test -%! % Pure 100Hz sine at 4kHz -> peak at 100Hz -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! t = (0 : 1/Fs : 0.3 - 1/Fs); % row vector -%! y = sin(2 * pi * 100 * t); -%! [freq, spec] = PSSpec2d(y, Fs_khz, 0); -%! [~, peak_idx] = max(spec); -%! peak_freq = freq(peak_idx); -%! assert(abs(peak_freq - 100) < 10); - -%!test -%! % Output has N/2+1 frequencies (DC to Nyquist) -%! Fs_khz = 4; -%! N = 1200; -%! y = randn(1, N); -%! [freq, spec] = PSSpec2d(y, Fs_khz, 0); -%! assert(length(freq), N/2 + 1); -%! assert(length(spec), N/2 + 1); - -%!test -%! % PSD mode produces values in dB range -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! t = (0 : 1/Fs : 0.3 - 1/Fs); -%! y = sin(2 * pi * 200 * t); -%! [~, spec_psd] = PSSpec2d(y, Fs_khz, 1); -%! % PSD in dB should have negative values for most bins -%! assert(any(spec_psd < 0)); - -%!test -%! % Frequency vector spans 0 to Nyquist -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = 1200; -%! y = randn(1, N); -%! [freq, ~] = PSSpec2d(y, Fs_khz, 0); -%! assert(freq(1), 0, 1e-10); % DC at 0 -%! assert(abs(freq(end) - Fs/2) < Fs/N); % last bin near Nyquist +% test_PSSpec2d.m - Tests for FFT computation engine +% NOTE: PSSpec2d expects Y as ROW vector, F in kHz + +%!test +%! % Pure 100Hz sine at 4kHz -> peak at 100Hz +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! t = (0 : 1/Fs : 0.3 - 1/Fs); % row vector +%! y = sin(2 * pi * 100 * t); +%! [freq, spec] = PSSpec2d(y, Fs_khz, 0); +%! [~, peak_idx] = max(spec); +%! peak_freq = freq(peak_idx); +%! assert(abs(peak_freq - 100) < 10); + +%!test +%! % Output has N/2+1 frequencies (DC to Nyquist) +%! Fs_khz = 4; +%! N = 1200; +%! y = randn(1, N); +%! [freq, spec] = PSSpec2d(y, Fs_khz, 0); +%! assert(length(freq), N/2 + 1); +%! assert(length(spec), N/2 + 1); + +%!test +%! % PSD mode produces values in dB range +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! t = (0 : 1/Fs : 0.3 - 1/Fs); +%! y = sin(2 * pi * 200 * t); +%! [~, spec_psd] = PSSpec2d(y, Fs_khz, 1); +%! % PSD in dB should have negative values for most bins +%! assert(any(spec_psd < 0)); + +%!test +%! % Frequency vector spans 0 to Nyquist +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = 1200; +%! y = randn(1, N); +%! [freq, ~] = PSSpec2d(y, Fs_khz, 0); +%! assert(freq(1), 0, 1e-10); % DC at 0 +%! assert(abs(freq(end) - Fs/2) < Fs/N); % last bin near Nyquist diff --git a/tests/test_PSbfFilters.m b/tests/test_PSbfFilters.m index a894595..bc9f2ae 100644 --- a/tests/test_PSbfFilters.m +++ b/tests/test_PSbfFilters.m @@ -1,59 +1,59 @@ -% test_PSbfFilters.m - tests for BF-compatible filter implementations - -%!test -%! % PT1 lowpass attenuates above cutoff -%! Fs = 4000; fc = 200; -%! [b, a] = PSbfFilters('pt1', fc, Fs); -%! t = (0:4000-1)'/Fs; -%! sig_lo = sin(2*pi*50*t); % 50 Hz — should pass -%! sig_hi = sin(2*pi*800*t); % 800 Hz — should be attenuated -%! out_lo = filter(b, a, sig_lo); -%! out_hi = filter(b, a, sig_hi); -%! rms_lo = sqrt(mean(out_lo(2000:end).^2)); -%! rms_hi = sqrt(mean(out_hi(2000:end).^2)); -%! assert(rms_lo > 0.5); % passes through -%! assert(rms_hi < 0.2); % attenuated - -%!test -%! % PT2 steeper rolloff than PT1 -%! Fs = 4000; fc = 200; -%! [b1, a1] = PSbfFilters('pt1', fc, Fs); -%! [b2, a2] = PSbfFilters('pt2', fc, Fs); -%! t = (0:4000-1)'/Fs; -%! sig = sin(2*pi*400*t); -%! out1 = filter(b1, a1, sig); -%! out2 = filter(b2, a2, sig); -%! rms1 = sqrt(mean(out1(2000:end).^2)); -%! rms2 = sqrt(mean(out2(2000:end).^2)); -%! assert(rms2 < rms1); % PT2 attenuates more at 2x cutoff - -%!test -%! % Notch filter removes target frequency -%! Fs = 4000; fc = 300; Q = 5; -%! [b, a] = PSbfFilters('notch', fc, Fs, Q); -%! t = (0:8000-1)'/Fs; -%! sig = sin(2*pi*300*t) + sin(2*pi*100*t); -%! out = filter(b, a, sig); -%! % 300 Hz should be removed, 100 Hz should remain -%! N = length(out); f = (0:N/2)*Fs/N; -%! Y = abs(fft(out .* hann(N))); -%! Y = Y(1:N/2+1); -%! [~, i100] = min(abs(f - 100)); -%! [~, i300] = min(abs(f - 300)); -%! assert(Y(i100) > Y(i300) * 5); % 100 Hz much stronger than 300 Hz - -%!test -%! % Edge case: fc=0 returns passthrough -%! [b, a] = PSbfFilters('pt1', 0, 4000); -%! assert(b, 1); -%! assert(a, 1); - -%!test -%! % Biquad is second-order lowpass -%! Fs = 4000; fc = 200; -%! [b, a] = PSbfFilters('biquad', fc, Fs); -%! assert(length(b), 3); -%! assert(length(a), 3); -%! % DC gain should be ~1 -%! dc_gain = sum(b) / sum(a); -%! assert(abs(dc_gain - 1) < 0.01); +% test_PSbfFilters.m - tests for BF-compatible filter implementations + +%!test +%! % PT1 lowpass attenuates above cutoff +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! t = (0:4000-1)'/Fs; +%! sig_lo = sin(2*pi*50*t); % 50 Hz — should pass +%! sig_hi = sin(2*pi*800*t); % 800 Hz — should be attenuated +%! out_lo = filter(b, a, sig_lo); +%! out_hi = filter(b, a, sig_hi); +%! rms_lo = sqrt(mean(out_lo(2000:end).^2)); +%! rms_hi = sqrt(mean(out_hi(2000:end).^2)); +%! assert(rms_lo > 0.5); % passes through +%! assert(rms_hi < 0.2); % attenuated + +%!test +%! % PT2 steeper rolloff than PT1 +%! Fs = 4000; fc = 200; +%! [b1, a1] = PSbfFilters('pt1', fc, Fs); +%! [b2, a2] = PSbfFilters('pt2', fc, Fs); +%! t = (0:4000-1)'/Fs; +%! sig = sin(2*pi*400*t); +%! out1 = filter(b1, a1, sig); +%! out2 = filter(b2, a2, sig); +%! rms1 = sqrt(mean(out1(2000:end).^2)); +%! rms2 = sqrt(mean(out2(2000:end).^2)); +%! assert(rms2 < rms1); % PT2 attenuates more at 2x cutoff + +%!test +%! % Notch filter removes target frequency +%! Fs = 4000; fc = 300; Q = 5; +%! [b, a] = PSbfFilters('notch', fc, Fs, Q); +%! t = (0:8000-1)'/Fs; +%! sig = sin(2*pi*300*t) + sin(2*pi*100*t); +%! out = filter(b, a, sig); +%! % 300 Hz should be removed, 100 Hz should remain +%! N = length(out); f = (0:N/2)*Fs/N; +%! Y = abs(fft(out .* hann(N))); +%! Y = Y(1:N/2+1); +%! [~, i100] = min(abs(f - 100)); +%! [~, i300] = min(abs(f - 300)); +%! assert(Y(i100) > Y(i300) * 5); % 100 Hz much stronger than 300 Hz + +%!test +%! % Edge case: fc=0 returns passthrough +%! [b, a] = PSbfFilters('pt1', 0, 4000); +%! assert(b, 1); +%! assert(a, 1); + +%!test +%! % Biquad is second-order lowpass +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('biquad', fc, Fs); +%! assert(length(b), 3); +%! assert(length(a), 3); +%! % DC gain should be ~1 +%! dc_gain = sum(b) / sum(a); +%! assert(abs(dc_gain - 1) < 0.01); diff --git a/tests/test_PSdebugModeIndices.m b/tests/test_PSdebugModeIndices.m index 864a632..8bbc3ce 100644 --- a/tests/test_PSdebugModeIndices.m +++ b/tests/test_PSdebugModeIndices.m @@ -1,34 +1,34 @@ -% test_PSdebugModeIndices.m - tests for PSdebugModeIndices - -%!test -%! % BF 4.5 (old indices) -%! idx = PSdebugModeIndices('Betaflight', 4, 5); -%! assert(idx.GYRO_SCALED, 6); -%! assert(idx.GYRO_FILTERED, 3); -%! assert(idx.RC_INTERPOLATION, 7); -%! assert(idx.FFT_FREQ, 17); -%! assert(idx.RPM_FILTER, 46); -%! assert(idx.FEEDFORWARD, 59); - -%!test -%! % BF 2025.12 (new indices, GYRO_SCALED removed) -%! idx = PSdebugModeIndices('Betaflight', 2025, 12); -%! assert(idx.GYRO_SCALED, -1); -%! assert(idx.GYRO_FILTERED, 3); -%! assert(idx.RC_INTERPOLATION, 6); -%! assert(idx.FFT_FREQ, 16); -%! assert(idx.RPM_FILTER, 45); -%! assert(idx.FEEDFORWARD, 58); - -%!test -%! % INAV (uses default old indices) -%! idx = PSdebugModeIndices('INAV', 7, 1); -%! assert(idx.GYRO_SCALED, 6); -%! assert(idx.RC_INTERPOLATION, 7); -%! assert(idx.FFT_FREQ, 17); - -%!test -%! % Emuflight (uses default old indices) -%! idx = PSdebugModeIndices('Emuflight', 0, 4); -%! assert(idx.GYRO_SCALED, 6); -%! assert(idx.FFT_FREQ, 17); +% test_PSdebugModeIndices.m - tests for PSdebugModeIndices + +%!test +%! % BF 4.5 (old indices) +%! idx = PSdebugModeIndices('Betaflight', 4, 5); +%! assert(idx.GYRO_SCALED, 6); +%! assert(idx.GYRO_FILTERED, 3); +%! assert(idx.RC_INTERPOLATION, 7); +%! assert(idx.FFT_FREQ, 17); +%! assert(idx.RPM_FILTER, 46); +%! assert(idx.FEEDFORWARD, 59); + +%!test +%! % BF 2025.12 (new indices, GYRO_SCALED removed) +%! idx = PSdebugModeIndices('Betaflight', 2025, 12); +%! assert(idx.GYRO_SCALED, -1); +%! assert(idx.GYRO_FILTERED, 3); +%! assert(idx.RC_INTERPOLATION, 6); +%! assert(idx.FFT_FREQ, 16); +%! assert(idx.RPM_FILTER, 45); +%! assert(idx.FEEDFORWARD, 58); + +%!test +%! % INAV (uses default old indices) +%! idx = PSdebugModeIndices('INAV', 7, 1); +%! assert(idx.GYRO_SCALED, 6); +%! assert(idx.RC_INTERPOLATION, 7); +%! assert(idx.FFT_FREQ, 17); + +%!test +%! % Emuflight (uses default old indices) +%! idx = PSdebugModeIndices('Emuflight', 0, 4); +%! assert(idx.GYRO_SCALED, 6); +%! assert(idx.FFT_FREQ, 17); diff --git a/tests/test_PSestimateRPM.m b/tests/test_PSestimateRPM.m index d7f6656..86a0e9e 100644 --- a/tests/test_PSestimateRPM.m +++ b/tests/test_PSestimateRPM.m @@ -1,35 +1,35 @@ -% test_PSestimateRPM.m - tests for PSestimateRPM - -%!test -%! % Synthetic motor noise at 200 Hz fundamental -%! freqAxis = 1:500; -%! ampMatrix = zeros(100, 500); -%! for t = 30:80 -%! f0 = 150 + t; % fundamental scales with throttle -%! ampMatrix(t, round(f0)) = 10; % fundamental -%! ampMatrix(t, round(f0*2)) = 5; % 2nd harmonic -%! end -%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 3); -%! % check mid-throttle detection -%! assert(~isnan(fund(50))); -%! assert(abs(fund(50) - 200) < 20); % should be near 200 Hz at throttle=50 - -%!test -%! % Empty matrix returns NaN -%! freqAxis = 1:100; -%! ampMatrix = zeros(100, 100); -%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 2); -%! assert(all(isnan(fund))); -%! assert(size(harm, 2), 2); - -%!test -%! % Harmonics are multiples of fundamental -%! freqAxis = 1:500; -%! ampMatrix = zeros(100, 500); -%! ampMatrix(50, 180) = 10; -%! ampMatrix(50, 360) = 5; -%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 3); -%! assert(~isnan(fund(50))); -%! assert(size(harm), [100 3]); -%! % 2nd harmonic should be ~2x fundamental -%! assert(abs(harm(50,2) / harm(50,1) - 2) < 0.1); +% test_PSestimateRPM.m - tests for PSestimateRPM + +%!test +%! % Synthetic motor noise at 200 Hz fundamental +%! freqAxis = 1:500; +%! ampMatrix = zeros(100, 500); +%! for t = 30:80 +%! f0 = 150 + t; % fundamental scales with throttle +%! ampMatrix(t, round(f0)) = 10; % fundamental +%! ampMatrix(t, round(f0*2)) = 5; % 2nd harmonic +%! end +%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 3); +%! % check mid-throttle detection +%! assert(~isnan(fund(50))); +%! assert(abs(fund(50) - 200) < 20); % should be near 200 Hz at throttle=50 + +%!test +%! % Empty matrix returns NaN +%! freqAxis = 1:100; +%! ampMatrix = zeros(100, 100); +%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 2); +%! assert(all(isnan(fund))); +%! assert(size(harm, 2), 2); + +%!test +%! % Harmonics are multiples of fundamental +%! freqAxis = 1:500; +%! ampMatrix = zeros(100, 500); +%! ampMatrix(50, 180) = 10; +%! ampMatrix(50, 360) = 5; +%! [fund, harm] = PSestimateRPM(freqAxis, ampMatrix, 3); +%! assert(~isnan(fund(50))); +%! assert(size(harm), [100 3]); +%! % 2nd harmonic should be ~2x fundamental +%! assert(abs(harm(50,2) / harm(50,1) - 2) < 0.1); diff --git a/tests/test_PSparseBFversion.m b/tests/test_PSparseBFversion.m index ba2d6ad..c0cd2a9 100644 --- a/tests/test_PSparseBFversion.m +++ b/tests/test_PSparseBFversion.m @@ -1,47 +1,47 @@ -% test_PSparseBFversion.m - tests for PSparseBFversion - -%!test -%! % BF 4.5.3 -%! si = {'Firmware version', ' Betaflight / STM32F405 4.5.3 Dec 14 2024 / 11:27:01'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'Betaflight')); -%! assert(maj, 4); -%! assert(mnr, 5); - -%!test -%! % BF 2025.12.2 (CalVer) -%! si = {'Firmware version', ' Betaflight / STM32H743 2025.12.2 Jan 5 2026 / 09:15:00'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'Betaflight')); -%! assert(maj, 2025); -%! assert(mnr, 12); - -%!test -%! % INAV 7.1.0 -%! si = {'Firmware version', ' INAV / STM32F405 7.1.0 Aug 2024'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'INAV')); -%! assert(maj, 7); -%! assert(mnr, 1); - -%!test -%! % Emuflight 0.4.1 -%! si = {'Firmware version', ' Emuflight / STM32F411 0.4.1 Mar 2023'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'Emuflight')); - -%!test -%! % Malformed / missing version row -%! si = {'other_param', 'some_value'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'Unknown')); -%! assert(maj, 0); -%! assert(mnr, 0); - -%!test -%! % Firmware revision (alternate key) -%! si = {'Firmware revision', ' Betaflight / STM32F7X2 4.4.0 Jun 2024'}; -%! [t, maj, mnr] = PSparseBFversion(si); -%! assert(strcmp(t, 'Betaflight')); -%! assert(maj, 4); -%! assert(mnr, 4); +% test_PSparseBFversion.m - tests for PSparseBFversion + +%!test +%! % BF 4.5.3 +%! si = {'Firmware version', ' Betaflight / STM32F405 4.5.3 Dec 14 2024 / 11:27:01'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Betaflight')); +%! assert(maj, 4); +%! assert(mnr, 5); + +%!test +%! % BF 2025.12.2 (CalVer) +%! si = {'Firmware version', ' Betaflight / STM32H743 2025.12.2 Jan 5 2026 / 09:15:00'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Betaflight')); +%! assert(maj, 2025); +%! assert(mnr, 12); + +%!test +%! % INAV 7.1.0 +%! si = {'Firmware version', ' INAV / STM32F405 7.1.0 Aug 2024'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'INAV')); +%! assert(maj, 7); +%! assert(mnr, 1); + +%!test +%! % Emuflight 0.4.1 +%! si = {'Firmware version', ' Emuflight / STM32F411 0.4.1 Mar 2023'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Emuflight')); + +%!test +%! % Malformed / missing version row +%! si = {'other_param', 'some_value'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Unknown')); +%! assert(maj, 0); +%! assert(mnr, 0); + +%!test +%! % Firmware revision (alternate key) +%! si = {'Firmware revision', ' Betaflight / STM32F7X2 4.4.0 Jun 2024'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Betaflight')); +%! assert(maj, 4); +%! assert(mnr, 4); diff --git a/tests/test_PSquicJson2csv.m b/tests/test_PSquicJson2csv.m index 6ee2880..fe6b096 100644 --- a/tests/test_PSquicJson2csv.m +++ b/tests/test_PSquicJson2csv.m @@ -1,104 +1,104 @@ -%% Tests for PSquicJson2csv - QuickSilver JSON blackbox parser - -%!test -%! % Create a minimal QuickSilver JSON file -%! jsonFile = [tempname() '.json']; -%! RAD2DEG = 180 / pi; -%! fid = fopen(jsonFile, 'w'); -%! fprintf(fid, '{\n'); -%! fprintf(fid, ' "blackbox_rate": 1,\n'); -%! fprintf(fid, ' "looptime": 125.0,\n'); -%! fprintf(fid, ' "fields": ["loop", "time", "gyro_filter", "setpoint", "motor"],\n'); -%! fprintf(fid, ' "entries": [\n'); -%! % Entry 1: loop=0, time=1000, gyro=[1000,0,0] (1 rad/s), setpoint=[500,0,0,800], motor=[500,500,500,500] -%! fprintf(fid, ' [0, 1000, [1000, 0, 0], [500, 0, 0, 800], [500, 500, 500, 500]],\n'); -%! % Entry 2: loop=1, time=2000 -%! fprintf(fid, ' [1, 2000, [2000, 1000, -500], [1000, 500, -250, 900], [600, 600, 600, 600]]\n'); -%! fprintf(fid, ' ]\n'); -%! fprintf(fid, '}\n'); -%! fclose(fid); -%! -%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); -%! -%! % Check files were created -%! assert(exist(csvFile, 'file') == 2); -%! assert(exist(headerFile, 'file') == 2); -%! -%! % Check CSV has correct number of rows (header + 2 data rows) -%! fid = fopen(csvFile, 'r'); -%! lines = {}; -%! while ~feof(fid) -%! l = fgetl(fid); -%! if ischar(l) && ~isempty(strtrim(l)), lines{end+1} = l; end -%! end -%! fclose(fid); -%! assert(numel(lines) >= 3, 'Expected at least 3 lines (header + 2 data)'); -%! -%! % Check header file has required fields -%! hdr = fileread(headerFile); -%! assert(~isempty(strfind(hdr, 'Firmware version'))); -%! assert(~isempty(strfind(hdr, 'debug_mode'))); -%! assert(~isempty(strfind(hdr, 'rollPID'))); -%! -%! % Read CSV with readtable and check values -%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); -%! T = readtable(csvFile); -%! -%! % Check loop counter -%! assert(T.loopIteration(1), 0); -%! assert(T.loopIteration(2), 1); -%! -%! % Check time -%! assert(T.time_us_(1), 1000); -%! -%! % Check gyro conversion: 1000 / 1000 * RAD2DEG = 57.296 deg/s -%! assert(abs(T.gyroADC_0_(1) - RAD2DEG) < 0.1, 'Gyro should be converted to deg/s'); -%! -%! % Check motor conversion: 500 / 1000 * 2000 = 1000 -%! assert(T.motor_0_(1), 1000); -%! -%! % Cleanup -%! delete(jsonFile); -%! delete(csvFile); -%! delete(headerFile); - -%!test -%! % Test with PID terms -%! jsonFile = [tempname() '.json']; -%! fid = fopen(jsonFile, 'w'); -%! fprintf(fid, '{"blackbox_rate":1,"looptime":250,"fields":["loop","time","pid_p_term","pid_i_term","pid_d_term"],"entries":[[0,100,[300,-150,50],[100,200,50],[80,40,0]]]}'); -%! fclose(fid); -%! -%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); -%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); -%! T = readtable(csvFile); -%! -%! % PID P: 300/1000 = 0.3 -%! assert(abs(T.axisP_0_(1) - 0.3) < 0.001); -%! assert(abs(T.axisP_1_(1) - (-0.15)) < 0.001); -%! -%! delete(jsonFile); delete(csvFile); delete(headerFile); - -%!test -%! % Test yaw negation on setpoint and gyro -%! jsonFile = [tempname() '.json']; -%! RAD2DEG = 180 / pi; -%! fid = fopen(jsonFile, 'w'); -%! fprintf(fid, '{"blackbox_rate":1,"looptime":125,"fields":["loop","time","setpoint","gyro_filter"],"entries":[[0,100,[0,0,1000,500],[0,0,1000]]]}'); -%! fclose(fid); -%! -%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); -%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); -%! T = readtable(csvFile); -%! -%! % Yaw setpoint: 1000/1000 * RAD2DEG = 57.296, but NEGATED -> -57.296 -%! assert(T.setpoint_2_(1) < 0, 'Yaw setpoint should be negated'); -%! assert(abs(T.setpoint_2_(1) + RAD2DEG) < 0.1); -%! -%! % Yaw gyro: same negation -%! assert(T.gyroADC_2_(1) < 0, 'Yaw gyro should be negated'); -%! -%! % Throttle setpoint: 500/1000 * 1000 = 500 -%! assert(T.setpoint_3_(1), 500); -%! -%! delete(jsonFile); delete(csvFile); delete(headerFile); +%% Tests for PSquicJson2csv - QuickSilver JSON blackbox parser + +%!test +%! % Create a minimal QuickSilver JSON file +%! jsonFile = [tempname() '.json']; +%! RAD2DEG = 180 / pi; +%! fid = fopen(jsonFile, 'w'); +%! fprintf(fid, '{\n'); +%! fprintf(fid, ' "blackbox_rate": 1,\n'); +%! fprintf(fid, ' "looptime": 125.0,\n'); +%! fprintf(fid, ' "fields": ["loop", "time", "gyro_filter", "setpoint", "motor"],\n'); +%! fprintf(fid, ' "entries": [\n'); +%! % Entry 1: loop=0, time=1000, gyro=[1000,0,0] (1 rad/s), setpoint=[500,0,0,800], motor=[500,500,500,500] +%! fprintf(fid, ' [0, 1000, [1000, 0, 0], [500, 0, 0, 800], [500, 500, 500, 500]],\n'); +%! % Entry 2: loop=1, time=2000 +%! fprintf(fid, ' [1, 2000, [2000, 1000, -500], [1000, 500, -250, 900], [600, 600, 600, 600]]\n'); +%! fprintf(fid, ' ]\n'); +%! fprintf(fid, '}\n'); +%! fclose(fid); +%! +%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); +%! +%! % Check files were created +%! assert(exist(csvFile, 'file') == 2); +%! assert(exist(headerFile, 'file') == 2); +%! +%! % Check CSV has correct number of rows (header + 2 data rows) +%! fid = fopen(csvFile, 'r'); +%! lines = {}; +%! while ~feof(fid) +%! l = fgetl(fid); +%! if ischar(l) && ~isempty(strtrim(l)), lines{end+1} = l; end +%! end +%! fclose(fid); +%! assert(numel(lines) >= 3, 'Expected at least 3 lines (header + 2 data)'); +%! +%! % Check header file has required fields +%! hdr = fileread(headerFile); +%! assert(~isempty(strfind(hdr, 'Firmware version'))); +%! assert(~isempty(strfind(hdr, 'debug_mode'))); +%! assert(~isempty(strfind(hdr, 'rollPID'))); +%! +%! % Read CSV with readtable and check values +%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); +%! T = readtable(csvFile); +%! +%! % Check loop counter +%! assert(T.loopIteration(1), 0); +%! assert(T.loopIteration(2), 1); +%! +%! % Check time +%! assert(T.time_us_(1), 1000); +%! +%! % Check gyro conversion: 1000 / 1000 * RAD2DEG = 57.296 deg/s +%! assert(abs(T.gyroADC_0_(1) - RAD2DEG) < 0.1, 'Gyro should be converted to deg/s'); +%! +%! % Check motor conversion: 500 / 1000 * 2000 = 1000 +%! assert(T.motor_0_(1), 1000); +%! +%! % Cleanup +%! delete(jsonFile); +%! delete(csvFile); +%! delete(headerFile); + +%!test +%! % Test with PID terms +%! jsonFile = [tempname() '.json']; +%! fid = fopen(jsonFile, 'w'); +%! fprintf(fid, '{"blackbox_rate":1,"looptime":250,"fields":["loop","time","pid_p_term","pid_i_term","pid_d_term"],"entries":[[0,100,[300,-150,50],[100,200,50],[80,40,0]]]}'); +%! fclose(fid); +%! +%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); +%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); +%! T = readtable(csvFile); +%! +%! % PID P: 300/1000 = 0.3 +%! assert(abs(T.axisP_0_(1) - 0.3) < 0.001); +%! assert(abs(T.axisP_1_(1) - (-0.15)) < 0.001); +%! +%! delete(jsonFile); delete(csvFile); delete(headerFile); + +%!test +%! % Test yaw negation on setpoint and gyro +%! jsonFile = [tempname() '.json']; +%! RAD2DEG = 180 / pi; +%! fid = fopen(jsonFile, 'w'); +%! fprintf(fid, '{"blackbox_rate":1,"looptime":125,"fields":["loop","time","setpoint","gyro_filter"],"entries":[[0,100,[0,0,1000,500],[0,0,1000]]]}'); +%! fclose(fid); +%! +%! [headerFile, csvFile] = PSquicJson2csv(jsonFile); +%! addpath(genpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'src'))); +%! T = readtable(csvFile); +%! +%! % Yaw setpoint: 1000/1000 * RAD2DEG = 57.296, but NEGATED -> -57.296 +%! assert(T.setpoint_2_(1) < 0, 'Yaw setpoint should be negated'); +%! assert(abs(T.setpoint_2_(1) + RAD2DEG) < 0.1); +%! +%! % Yaw gyro: same negation +%! assert(T.gyroADC_2_(1) < 0, 'Yaw gyro should be negated'); +%! +%! % Throttle setpoint: 500/1000 * 1000 = 500 +%! assert(T.setpoint_3_(1), 500); +%! +%! delete(jsonFile); delete(csvFile); delete(headerFile); diff --git a/tests/test_PSstepcalc.m b/tests/test_PSstepcalc.m index 0370e64..3144101 100644 --- a/tests/test_PSstepcalc.m +++ b/tests/test_PSstepcalc.m @@ -1,54 +1,54 @@ -% test_PSstepcalc.m - Tests for step response deconvolution -% NOTE: PSstepcalc expects SP,GY as column vectors, lograte in kHz - -%!test -%! % Smoke test: function runs without error -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; % 5 seconds -%! sp = zeros(N, 1); -%! gy = zeros(N, 1); -%! % Create step inputs above 20 deg/s threshold -%! for k = 1:3 -%! st = round(k * Fs + 100); -%! en = min(st + round(0.4*Fs), N); -%! sp(st:en) = 300; -%! gy(st:en) = 285; -%! end -%! [stepresponse, t] = PSstepcalc(sp, gy, Fs_khz, 0, 1); -%! assert(~isempty(t)); - -%!test -%! % Time vector starts at 0 and ends at 500ms -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! sp = zeros(N, 1); -%! gy = zeros(N, 1); -%! for k = 1:3 -%! st = round(k * Fs + 100); -%! en = min(st + round(0.4*Fs), N); -%! sp(st:en) = 300; -%! gy(st:en) = 280; -%! end -%! [~, t] = PSstepcalc(sp, gy, Fs_khz, 0, 1); -%! assert(t(1), 0, 1e-10); -%! assert(t(end), 500, 1); - -%!test -%! % Step response values are finite when valid segments exist -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! sp = zeros(N, 1); -%! gy = zeros(N, 1); -%! for k = 1:3 -%! st = round(k * Fs + 100); -%! en = min(st + round(0.4*Fs), N); -%! sp(st:en) = 400; -%! gy(st:en) = 380; -%! end -%! [stepresponse, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1); -%! if ~isempty(stepresponse) -%! assert(all(isfinite(stepresponse(:)))); -%! end +% test_PSstepcalc.m - Tests for step response deconvolution +% NOTE: PSstepcalc expects SP,GY as column vectors, lograte in kHz + +%!test +%! % Smoke test: function runs without error +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; % 5 seconds +%! sp = zeros(N, 1); +%! gy = zeros(N, 1); +%! % Create step inputs above 20 deg/s threshold +%! for k = 1:3 +%! st = round(k * Fs + 100); +%! en = min(st + round(0.4*Fs), N); +%! sp(st:en) = 300; +%! gy(st:en) = 285; +%! end +%! [stepresponse, t] = PSstepcalc(sp, gy, Fs_khz, 0, 1); +%! assert(~isempty(t)); + +%!test +%! % Time vector starts at 0 and ends at 500ms +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! sp = zeros(N, 1); +%! gy = zeros(N, 1); +%! for k = 1:3 +%! st = round(k * Fs + 100); +%! en = min(st + round(0.4*Fs), N); +%! sp(st:en) = 300; +%! gy(st:en) = 280; +%! end +%! [~, t] = PSstepcalc(sp, gy, Fs_khz, 0, 1); +%! assert(t(1), 0, 1e-10); +%! assert(t(end), 500, 1); + +%!test +%! % Step response values are finite when valid segments exist +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! sp = zeros(N, 1); +%! gy = zeros(N, 1); +%! for k = 1:3 +%! st = round(k * Fs + 100); +%! en = min(st + round(0.4*Fs), N); +%! sp(st:en) = 400; +%! gy(st:en) = 380; +%! end +%! [stepresponse, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1); +%! if ~isempty(stepresponse) +%! assert(all(isfinite(stepresponse(:)))); +%! end diff --git a/tests/test_PSthrSpec.m b/tests/test_PSthrSpec.m index a0c11f7..3e28c28 100644 --- a/tests/test_PSthrSpec.m +++ b/tests/test_PSthrSpec.m @@ -1,36 +1,36 @@ -% test_PSthrSpec.m - Tests for throttle x frequency spectrogram -% NOTE: PSthrSpec expects X,Y as column vectors, F in kHz - -%!test -%! % Smoke test: function runs on synthetic data -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! duration = 5; -%! N = Fs * duration; -%! throttle = ones(N, 1) * 50; -%! signal = sin(2 * pi * 150 * (0:N-1)' / Fs); -%! [freq, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); -%! assert(~isempty(ampMat)); -%! assert(~isempty(freq)); - -%!test -%! % Output matrix is 100 rows (throttle bins) -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! throttle = ones(N, 1) * 50; -%! signal = randn(N, 1); -%! [~, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); -%! assert(size(ampMat, 1), 100); - -%!test -%! % Energy concentrated at correct throttle bin -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 10; -%! throttle = ones(N, 1) * 50; -%! signal = sin(2 * pi * 100 * (0:N-1)' / Fs); -%! [~, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); -%! energy_at_50 = sum(ampMat(50, :)); -%! energy_at_10 = sum(ampMat(10, :)); -%! assert(energy_at_50 > energy_at_10); +% test_PSthrSpec.m - Tests for throttle x frequency spectrogram +% NOTE: PSthrSpec expects X,Y as column vectors, F in kHz + +%!test +%! % Smoke test: function runs on synthetic data +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! duration = 5; +%! N = Fs * duration; +%! throttle = ones(N, 1) * 50; +%! signal = sin(2 * pi * 150 * (0:N-1)' / Fs); +%! [freq, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); +%! assert(~isempty(ampMat)); +%! assert(~isempty(freq)); + +%!test +%! % Output matrix is 100 rows (throttle bins) +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! throttle = ones(N, 1) * 50; +%! signal = randn(N, 1); +%! [~, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); +%! assert(size(ampMat, 1), 100); + +%!test +%! % Energy concentrated at correct throttle bin +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 10; +%! throttle = ones(N, 1) * 50; +%! signal = sin(2 * pi * 100 * (0:N-1)' / Fs); +%! [~, ampMat] = PSthrSpec(throttle, signal, Fs_khz, 0); +%! energy_at_50 = sum(ampMat(50, :)); +%! energy_at_10 = sum(ampMat(10, :)); +%! assert(energy_at_50 > energy_at_10); diff --git a/tests/test_PStimeFreqCalc.m b/tests/test_PStimeFreqCalc.m index 72d2854..ad6f212 100644 --- a/tests/test_PStimeFreqCalc.m +++ b/tests/test_PStimeFreqCalc.m @@ -1,45 +1,45 @@ -% test_PStimeFreqCalc.m - Tests for time x frequency computation -% NOTE: PStimeFreqCalc expects Y as column vector, F in kHz - -%!test -%! % Smoke test: function runs on synthetic data -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! signal = sin(2 * pi * 200 * (0:N-1)' / Fs); -%! [Tm, freq, specMat] = PStimeFreqCalc(signal, Fs_khz, 1, 1); -%! assert(~isempty(specMat)); -%! assert(~isempty(Tm)); -%! assert(~isempty(freq)); - -%!test -%! % Time vector spans signal duration -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! duration = 5; -%! N = Fs * duration; -%! signal = randn(N, 1); -%! [Tm, ~, ~] = PStimeFreqCalc(signal, Fs_khz, 1, 1); -%! assert(Tm(1) >= 0); -%! assert(Tm(end) <= duration + 1); - -%!test -%! % Frequency vector reaches toward Nyquist -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! signal = randn(N, 1); -%! [~, freq, ~] = PStimeFreqCalc(signal, Fs_khz, 1, 1); -%! assert(max(freq) <= Fs/2 + 100); -%! assert(max(freq) > 100); - -%!test -%! % specMat is 2D with reasonable dimensions -%! Fs_khz = 4; -%! Fs = Fs_khz * 1000; -%! N = Fs * 5; -%! signal = randn(N, 1); -%! [~, ~, specMat] = PStimeFreqCalc(signal, Fs_khz, 1, 1); -%! assert(ndims(specMat), 2); -%! assert(size(specMat, 1) > 0); -%! assert(size(specMat, 2) > 0); +% test_PStimeFreqCalc.m - Tests for time x frequency computation +% NOTE: PStimeFreqCalc expects Y as column vector, F in kHz + +%!test +%! % Smoke test: function runs on synthetic data +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! signal = sin(2 * pi * 200 * (0:N-1)' / Fs); +%! [Tm, freq, specMat] = PStimeFreqCalc(signal, Fs_khz, 1, 1); +%! assert(~isempty(specMat)); +%! assert(~isempty(Tm)); +%! assert(~isempty(freq)); + +%!test +%! % Time vector spans signal duration +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! duration = 5; +%! N = Fs * duration; +%! signal = randn(N, 1); +%! [Tm, ~, ~] = PStimeFreqCalc(signal, Fs_khz, 1, 1); +%! assert(Tm(1) >= 0); +%! assert(Tm(end) <= duration + 1); + +%!test +%! % Frequency vector reaches toward Nyquist +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! signal = randn(N, 1); +%! [~, freq, ~] = PStimeFreqCalc(signal, Fs_khz, 1, 1); +%! assert(max(freq) <= Fs/2 + 100); +%! assert(max(freq) > 100); + +%!test +%! % specMat is 2D with reasonable dimensions +%! Fs_khz = 4; +%! Fs = Fs_khz * 1000; +%! N = Fs * 5; +%! signal = randn(N, 1); +%! [~, ~, specMat] = PStimeFreqCalc(signal, Fs_khz, 1, 1); +%! assert(ndims(specMat), 2); +%! assert(size(specMat, 1) > 0); +%! assert(size(specMat, 2) > 0); diff --git a/tests/test_compat.m b/tests/test_compat.m index b84519c..c91730c 100644 --- a/tests/test_compat.m +++ b/tests/test_compat.m @@ -1,60 +1,60 @@ -% test_compat.m - Tests for compat/ shim functions - -%!test -%! % smooth() - moving average with span 5 -%! y = [1 2 3 4 5 6 7 8 9 10]'; -%! ys = smooth(y, 5); -%! assert(length(ys), 10); -%! assert(ys(3), 3, 1e-10); % middle of window [1,2,3,4,5] = 3 -%! assert(ys(5), 5, 1e-10); % middle of window [3,4,5,6,7] = 5 - -%!test -%! % smooth() - moving average preserves length -%! y = randn(100, 1); -%! ys = smooth(y, 11); -%! assert(length(ys), 100); - -%!test -%! % smooth() - lowess method runs without error -%! y = sin(linspace(0, 4*pi, 100))' + 0.1*randn(100, 1); -%! ys = smooth(y, 21, 'lowess'); -%! assert(length(ys), 100); - -%!test -%! % nanmean() - basic mean ignoring NaN -%! x = [1 2 NaN 4 5]; -%! assert(nanmean(x), 3, 1e-10); - -%!test -%! % nanmean() - column-wise with dim=1 -%! x = [1 2; NaN 4; 3 6]; -%! m = nanmean(x, 1); -%! assert(m(1), 2, 1e-10); -%! assert(m(2), 4, 1e-10); - -%!test -%! % nanmedian() - basic median ignoring NaN -%! x = [1 NaN 3 4 5]; -%! assert(nanmedian(x), 3.5, 1e-10); - -%!test -%! % finddelay() - detect known delay -%! x = [zeros(1,10) ones(1,90)]'; -%! y = [zeros(1,15) ones(1,85)]'; % delayed by 5 samples -%! d = finddelay(x, y, 20); -%! assert(abs(d), 5, 2); % allow +-2 sample tolerance - -%!test -%! % contains() - string matching -%! assert(contains('hello world', 'world')); -%! assert(!contains('hello world', 'foo')); - -%!test -%! % contains() - cell array -%! c = {'alpha', 'beta', 'gamma'}; -%! result = contains(c, 'bet'); -%! assert(result, [false true false]); - -%!test -%! % contains() - case insensitive -%! assert(contains('Hello', 'hello', 'IgnoreCase', true)); +% test_compat.m - Tests for compat/ shim functions + +%!test +%! % smooth() - moving average with span 5 +%! y = [1 2 3 4 5 6 7 8 9 10]'; +%! ys = smooth(y, 5); +%! assert(length(ys), 10); +%! assert(ys(3), 3, 1e-10); % middle of window [1,2,3,4,5] = 3 +%! assert(ys(5), 5, 1e-10); % middle of window [3,4,5,6,7] = 5 + +%!test +%! % smooth() - moving average preserves length +%! y = randn(100, 1); +%! ys = smooth(y, 11); +%! assert(length(ys), 100); + +%!test +%! % smooth() - lowess method runs without error +%! y = sin(linspace(0, 4*pi, 100))' + 0.1*randn(100, 1); +%! ys = smooth(y, 21, 'lowess'); +%! assert(length(ys), 100); + +%!test +%! % nanmean() - basic mean ignoring NaN +%! x = [1 2 NaN 4 5]; +%! assert(nanmean(x), 3, 1e-10); + +%!test +%! % nanmean() - column-wise with dim=1 +%! x = [1 2; NaN 4; 3 6]; +%! m = nanmean(x, 1); +%! assert(m(1), 2, 1e-10); +%! assert(m(2), 4, 1e-10); + +%!test +%! % nanmedian() - basic median ignoring NaN +%! x = [1 NaN 3 4 5]; +%! assert(nanmedian(x), 3.5, 1e-10); + +%!test +%! % finddelay() - detect known delay +%! x = [zeros(1,10) ones(1,90)]'; +%! y = [zeros(1,15) ones(1,85)]'; % delayed by 5 samples +%! d = finddelay(x, y, 20); +%! assert(abs(d), 5, 2); % allow +-2 sample tolerance + +%!test +%! % contains() - string matching +%! assert(contains('hello world', 'world')); +%! assert(!contains('hello world', 'foo')); + +%!test +%! % contains() - cell array +%! c = {'alpha', 'beta', 'gamma'}; +%! result = contains(c, 'bet'); +%! assert(result, [false true false]); + +%!test +%! % contains() - case insensitive +%! assert(contains('Hello', 'hello', 'IgnoreCase', true)); diff --git a/tests/test_helpers.m b/tests/test_helpers.m index b579357..2607efc 100644 --- a/tests/test_helpers.m +++ b/tests/test_helpers.m @@ -1,48 +1,48 @@ -function out = test_helpers() - % Test helpers - mock data generators for PIDscope tests - % Usage: h = test_helpers(); y = h.mock_sine(100, 2, 4); - out.mock_sine = @mock_sine; - out.mock_step = @mock_step; - out.mock_throttle = @mock_throttle; -end - -function y = mock_sine(freq_hz, duration_s, sample_rate_khz) - % Generate pure sine wave - % freq_hz - frequency in Hz - % duration_s - duration in seconds - % sample_rate_khz - sample rate in kHz (matching PIDscope convention) - Fs = sample_rate_khz * 1000; - t = (0 : 1/Fs : duration_s - 1/Fs)'; - y = sin(2 * pi * freq_hz * t); -end - -function [sp, gy] = mock_step(delay_samples, duration_s, sample_rate_khz) - % Generate step input (setpoint) and delayed step response (gyro) - % delay_samples - response delay in samples - % duration_s - total duration in seconds - % sample_rate_khz - sample rate in kHz - Fs = sample_rate_khz * 1000; - N = round(Fs * duration_s); - sp = zeros(N, 1); - gy = zeros(N, 1); - % Step at 25% of signal - step_start = round(N * 0.25); - sp(step_start:end) = 500; % 500 deg/s step (above 20 deg/s threshold) - % Delayed response with first-order dynamics - resp_start = step_start + delay_samples; - if resp_start <= N - tau = round(Fs * 0.02); % 20ms time constant - t_resp = (0 : N - resp_start)'; - gy(resp_start:end) = 500 * (1 - exp(-t_resp / tau)); - end -end - -function x = mock_throttle(level, duration_s, sample_rate_khz) - % Generate constant throttle signal - % level - throttle percentage (0-100) - % duration_s - duration in seconds - % sample_rate_khz - sample rate in kHz - Fs = sample_rate_khz * 1000; - N = round(Fs * duration_s); - x = ones(N, 1) * level; -end +function out = test_helpers() + % Test helpers - mock data generators for PIDscope tests + % Usage: h = test_helpers(); y = h.mock_sine(100, 2, 4); + out.mock_sine = @mock_sine; + out.mock_step = @mock_step; + out.mock_throttle = @mock_throttle; +end + +function y = mock_sine(freq_hz, duration_s, sample_rate_khz) + % Generate pure sine wave + % freq_hz - frequency in Hz + % duration_s - duration in seconds + % sample_rate_khz - sample rate in kHz (matching PIDscope convention) + Fs = sample_rate_khz * 1000; + t = (0 : 1/Fs : duration_s - 1/Fs)'; + y = sin(2 * pi * freq_hz * t); +end + +function [sp, gy] = mock_step(delay_samples, duration_s, sample_rate_khz) + % Generate step input (setpoint) and delayed step response (gyro) + % delay_samples - response delay in samples + % duration_s - total duration in seconds + % sample_rate_khz - sample rate in kHz + Fs = sample_rate_khz * 1000; + N = round(Fs * duration_s); + sp = zeros(N, 1); + gy = zeros(N, 1); + % Step at 25% of signal + step_start = round(N * 0.25); + sp(step_start:end) = 500; % 500 deg/s step (above 20 deg/s threshold) + % Delayed response with first-order dynamics + resp_start = step_start + delay_samples; + if resp_start <= N + tau = round(Fs * 0.02); % 20ms time constant + t_resp = (0 : N - resp_start)'; + gy(resp_start:end) = 500 * (1 - exp(-t_resp / tau)); + end +end + +function x = mock_throttle(level, duration_s, sample_rate_khz) + % Generate constant throttle signal + % level - throttle percentage (0-100) + % duration_s - duration in seconds + % sample_rate_khz - sample rate in kHz + Fs = sample_rate_khz * 1000; + N = round(Fs * duration_s); + x = ones(N, 1) * level; +end From 736559f62d2c66df63213288a6167feb2a498590 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 11:02:41 +0100 Subject: [PATCH 04/80] perf(ui): add PStheme, defer spectral delay computation to Run click --- src/plot/PSplotSpec2D.m | 651 +++++++++++++++++++++---------------- src/ui/PSspec2DUIcontrol.m | 114 +------ src/util/PSstyleAxes.m | 14 +- src/util/PSstyleFig.m | 12 +- src/util/PSstyleLegend.m | 10 +- src/util/PStheme.m | 118 +++---- 6 files changed, 459 insertions(+), 460 deletions(-) diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 4edf45a..2b4071d 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -1,279 +1,372 @@ -%% PSplotSpec2D - script that computes and plots spectrograms - - -% ---------------------------------------------------------------------------------- -% "THE BEER-WARE LICENSE" (Revision 42): -% wrote this file. As long as you retain this notice you -% can do whatever you want with this stuff. If we meet some day, and you think -% this stuff is worth it, you can buy me a beer in return. -Brian White -% ---------------------------------------------------------------------------------- - -if exist('fnameMaster','var') && ~isempty(fnameMaster) -%% update fonts -PSspecfig2_pos = get(PSspecfig2, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSspecfig2_pos(3) > 10, PSspecfig2_pos(3:4) = PSspecfig2_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSspecfig2_pos(3) PSspecfig2_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); - -f = fields(guiHandlesSpec2); -for i = 1 : size(f,1) - try set(guiHandlesSpec2.(f{i}), 'FontSize', fontsz); catch, end -end - -set(spec2Crtlpanel, 'FontSize', fontsz); - -guiHandlesSpec2.climMax1_text = uicontrol(PSspecfig2,'style','text','string','Y min','fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax1_text]); -guiHandlesSpec2.climMax1_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','Position',[posInfo.climMax1_input],... - 'callback','@textinput_call2; climScale1(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax1_input, ''String''));updateSpec=1;PSplotSpec2D;'); - - guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); -guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... - 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));updateSpec=1;PSplotSpec2D;'); - - -%% - -s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; - -datSelectionString=[s1]; -axesOptionsSpec = find([get(guiHandlesSpec2.plotR, 'Value') get(guiHandlesSpec2.plotP, 'Value') get(guiHandlesSpec2.plotY, 'Value')]); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%% compute fft %%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -set(PSspecfig2, 'pointer', 'watch') - -clear s dat a RC smat amp2d2 freq2d2 -freq2d2 = {}; -amp2d2 = {}; -p=0; -hw = waitbar(0,['please wait... ' ]); -tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); -tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); -tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); -pTotal = max(1, length(tmpSpecVal) * size(tmpFileVal,2) * length(axesOptionsSpec)); -for k = 1 : length(tmpSpecVal) - s = char(datSelectionString(tmpSpecVal(k))); - for f = 1 : size(tmpFileVal,2) - for a = axesOptionsSpec - if ( ( ~isempty(strfind(s,'axisD'))) && a==3) || isempty(s) - p=p+1; - smat{p}=[];%string - amp2d2{p}=[];%spec 2d - freq2d2{p}=[];% freq2d2 - else - p = p + 1; - clear dat - eval(['dat = T{tmpFileVal(f)}.' char(datSelectionString(tmpSpecVal(k))) '_' int2str(a-1) '_(tIND{tmpFileVal(f)})'';';]) - lograte = A_lograte(tmpFileVal(f));%in kHz - waitbar(min(1, p/pTotal), hw, ['processing spectrogram... ' int2str(p) ]); - smat{p}=s; - eval(['[freq2d2{p}.f' int2str(f) ' amp2d2{p}.f' int2str(f) ' ]=PSSpec2d(dat,lograte, tmpPSDVal);']) %compute 2d amp spec at same time - end - end - end -end -close(hw) - - - - -figure(PSspecfig2); -baselineYlines = [0 -50]; -multilineStyle = {'-' ; ':'; '--'}; -rpyLineStyle = {'-' ; '--'; ':'}; - -delete(subplot('position',posInfo.Spec2Pos(1,:))) -delete(subplot('position',posInfo.Spec2Pos(2,:))) -delete(subplot('position',posInfo.Spec2Pos(3,:))) -delete(subplot('position',posInfo.Spec2Pos(4,:))) -delete(subplot('position',posInfo.Spec2Pos(5,:))) -delete(subplot('position',posInfo.Spec2Pos(6,:))) -%%%%% plot 2d amp spec -axLabel={'Roll';'Pitch';'Yaw'}; - -p = 0; -tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); -tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); -tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); -tmpSmoothVal = get(guiHandlesSpec2.smoothFactor_select, 'Value'); -for k = 1 : length(tmpSpecVal) - s = char(datSelectionString(k)); - for f = 1 : size(tmpFileVal,2) - cnt = 0; - for a = axesOptionsSpec - cnt = cnt + 1; - p = p + 1; - if ~isempty(freq2d2) - if ~isempty(freq2d2{p}) && ~isempty(amp2d2{p}) - - if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - - h2=subplot('position',posInfo.Spec2Pos(a,:)); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) - hold on - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) - set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) - m = (A_lograte(tmpFileVal(f)) * 1000) / 2; - set(h2,'xtick',[0:m/10:m], 'yminortick','on') - axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); - if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); - else - ylabel(['Amplitude'],'fontweight','bold'); - end - if a == 1 - title('Full Spectrum','fontweight','bold'); - end - if p < 4 - h=text(2,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); - end - grid on - - h2=subplot('position',posInfo.Spec2Pos(a+3,:)); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) - hold on - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) - set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) - m = (A_lograte(tmpFileVal(f)) * 1000) / 2; - set(h2,'xtick',[0 20 40 60 80 100],'yminortick','on') - axis([0 100 climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); - if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); - else - ylabel(['Amplitude'],'fontweight','bold'); - end - if a == 1 - title('Sub 100Hz','fontweight','bold'); - end - if p < 4 - h=text(1,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); - end - - %%%%%%%%%%%%%%%%%%% Plot Latencies %%%%%%%%%%%%%%% - tmpFileSelVals = get(guiHandlesSpec2.FileSelect, 'Value'); - tmpFileIdx = tmpFileSelVals(f); - % Per-file debug mode indices (BF version-aware) - if exist('debugIdx','var') && numel(debugIdx) >= tmpFileIdx - tmpDbgIdx = debugIdx{tmpFileIdx}; - else - tmpDbgIdx = struct('GYRO_SCALED',6,'GYRO_FILTERED',3,'RC_INTERPOLATION',7,'FFT_FREQ',17,'FEEDFORWARD',59); - end - if get(guiHandlesSpec2.Delay, 'Value') == 1 && a == 1 - if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 - h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' Debug01{tmpFileIdx} 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - else - h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - end - end - if get(guiHandlesSpec2.Delay, 'Value') == 2 - h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP-Gyro: ' int2str(SPGyroDelay(tmpFileIdx, a)) 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - end - if get(guiHandlesSpec2.Delay, 'Value') == 3 && a == 1 - if debugmode(tmpFileIdx) == tmpDbgIdx.RC_INTERPOLATION || debugmode(tmpFileIdx) == tmpDbgIdx.FEEDFORWARD - h=text(75, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP smoothing delay: ' Debug02{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - else - h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['debug mode not set ']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - end - end - if get(guiHandlesSpec2.Delay, 'Value') == 4 && a == 1 - if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 - h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' num2str(gyro_phase_shift_deg(tmpFileIdx)) 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - else - h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); - end - end - - - else - % combine R P Y - h2=subplot('position',[0.0500 0.1000 0.800 0.840]); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) - hold on - if k == 1 - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/1.4,'linestyle',rpyLineStyle{cnt}) - end - if k == 2 - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2.6,'linestyle',rpyLineStyle{cnt}) - end - set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) - m = (A_lograte(tmpFileVal(f)) * 1000) / 2; - set(h2,'xtick',[0:m/10:m], 'yminortick','on') - axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); - if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); - else - ylabel(['Amplitude'],'fontweight','bold'); - end - if a == 1 - title('Full Spectrum','fontweight','bold'); - end - grid on - - - end - - grid on - - else - end - end - end - end -end - -l=0;legnd={}; -l2=0; -tmpSpecListStr = get(guiHandlesSpec2.SpecList, 'String'); -tmpSpecListVal = get(guiHandlesSpec2.SpecList, 'Value'); -tmpFileSelStr = get(guiHandlesSpec2.FileSelect, 'String'); -tmpFileSelVal = get(guiHandlesSpec2.FileSelect, 'Value'); -for m = 1 : length(tmpSpecListVal) - for n = 1 : length(tmpFileSelVal) - l = l + 1; - clear fstr fltDelayStr - fstr = char(tmpFileSelStr(tmpFileSelVal(n))); - if size(fstr,2) > 12, fstr = fstr(1,1:12); end % only use first 20 characters of file name - if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - legnd{l} = [char(tmpSpecListStr(tmpSpecListVal(m))) ' | ' fstr]; - else - for a = axesOptionsSpec - l2 = l2 + 1; - legnd{l2} = [axLabel{a} ' | ' char(tmpSpecListStr(tmpSpecListVal(m))) ' | ' fstr ]; - end - end - end -end -if ~isempty(freq2d2) && ~isempty(amp2d2) - if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - h=legend(legnd); - hPos = get(h, 'Position'); set(h, 'Position', [0.35 0.01 hPos(3:4)]); - else - h=legend(legnd, 'Location','NorthEast') - end -end - - -% Set up click-to-show-value datatips + double-click expand on all axes -PSdatatipSetup(PSspecfig2); - -set(PSspecfig2, 'pointer', 'arrow') -updateSpec=0; -end - - +%% PSplotSpec2D - script that computes and plots spectrograms + + +% ---------------------------------------------------------------------------------- +% "THE BEER-WARE LICENSE" (Revision 42): +% wrote this file. As long as you retain this notice you +% can do whatever you want with this stuff. If we meet some day, and you think +% this stuff is worth it, you can buy me a beer in return. -Brian White +% ---------------------------------------------------------------------------------- + +if exist('fnameMaster','var') && ~isempty(fnameMaster) +%% update fonts +PSspecfig2_pos = get(PSspecfig2, 'Position'); +screensz_tmp = get(0,'ScreenSize'); if PSspecfig2_pos(3) > 10, PSspecfig2_pos(3:4) = PSspecfig2_pos(3:4) ./ screensz_tmp(3:4); end +prop_max_screen=(max([PSspecfig2_pos(3) PSspecfig2_pos(4)])); +fontsz=(screensz_multiplier*prop_max_screen); + +f = fields(guiHandlesSpec2); +for i = 1 : size(f,1) + try set(guiHandlesSpec2.(f{i}), 'FontSize', fontsz); catch, end +end + +set(spec2Crtlpanel, 'FontSize', fontsz); + +guiHandlesSpec2.climMax1_text = uicontrol(PSspecfig2,'style','text','string','Y min','fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax1_text]); +guiHandlesSpec2.climMax1_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','Position',[posInfo.climMax1_input],... + 'callback','@textinput_call2; climScale1(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax1_input, ''String''));updateSpec=1;PSplotSpec2D;'); + + guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); +guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... + 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));updateSpec=1;PSplotSpec2D;'); + + +%% + +s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; + +datSelectionString=[s1]; +axesOptionsSpec = find([get(guiHandlesSpec2.plotR, 'Value') get(guiHandlesSpec2.plotP, 'Value') get(guiHandlesSpec2.plotY, 'Value')]); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% compute fft %%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +set(PSspecfig2, 'pointer', 'watch') + +%%% compute delay/overlay data (deferred from UI open to Run click) +if ~exist('delayDataReady','var') || ~delayDataReady + FilterDelayDterm={}; + SPGyroDelay=[]; + Debug01={}; + Debug02={}; + gyro_phase_shift_deg=zeros(Nfiles,1); + dterm_phase_shift_deg=zeros(Nfiles,1); + notchData={}; + rpmFilterData={}; + for k = 1 : Nfiles + Fs=1000/A_lograte(k); + maxlag=round(30000/Fs); + + try + pg = smooth(T{k}.debug_0_(tIND{k}),50); + catch + pg = 0; + end + g1 = smooth(T{k}.gyroADC_0_(tIND{k}),50); + s1 = smooth(T{k}.setpoint_0_(tIND{k}),50); + g2 = smooth(T{k}.gyroADC_1_(tIND{k}),50); + s2 = smooth(T{k}.setpoint_1_(tIND{k}),50); + g3 = smooth(T{k}.gyroADC_2_(tIND{k}),50); + s3 = smooth(T{k}.setpoint_2_(tIND{k}),50); + + [c,lags] = xcorr(g1,pg,maxlag); + d = lags(find(c==max(c),1)); + d = d * (Fs / 1000); + if d<.1, Debug01{k} = ' '; else Debug01{k} = num2str(d); end + + [c,lags] = xcorr(s1,pg,maxlag); + d = lags(find(c==max(c),1)); + d = d * (Fs / 1000); + if d<.1, Debug02{k} = ' '; else Debug02{k} = num2str(d); end + + [c,lags] = xcorr(g1,s1,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,1) = 0; else, SPGyroDelay(k,1) = d; end + + [c,lags] = xcorr(g2,s2,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,2) = 0; else, SPGyroDelay(k,2) = d; end + + [c,lags] = xcorr(g3,s3,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,3) = 0; else, SPGyroDelay(k,3) = d; end + + try + d1 = smooth(T{k}.axisDpf_0_(tIND{k}),50); + d2 = smooth(T{k}.axisD_0_(tIND{k}),50); + [c,lags] = xcorr(d2,d1,maxlag); + d = lags(find(c==max(c))); + d = d * (Fs / 1000); + if d<.1, FilterDelayDterm{k} = ' '; else FilterDelayDterm{k} = num2str(d); end + catch + FilterDelayDterm{k} = ' '; + end + + try + if ~isempty(str2num(Debug01{k})) && SPGyroDelay(k,1) > 0 + gyro_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2num(Debug01{k}), 1000/(SPGyroDelay(k,1)))); + end + if ~isempty(str2num(FilterDelayDterm{k})) && SPGyroDelay(k,1) > 0 + dterm_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2num(FilterDelayDterm{k}), 1000/(SPGyroDelay(k,1)))); + end + catch, end + + % dynamic notch data for FFT_FREQ overlay + tmpFFTidx = FFT_FREQ; + if exist('debugIdx','var') && numel(debugIdx) >= k + tmpFFTidx = debugIdx{k}.FFT_FREQ; + end + if exist('debugmode','var') && numel(debugmode) >= k && debugmode(k) == tmpFFTidx + if exist('fwMajor','var') && numel(fwMajor) >= k && fwMajor(k) >= 2025 + notchData{k} = [T{k}.debug_1_(tIND{k}), T{k}.debug_2_(tIND{k}), T{k}.debug_3_(tIND{k})]; + else + notchData{k} = [T{k}.debug_0_(tIND{k}), T{k}.debug_1_(tIND{k}), T{k}.debug_2_(tIND{k})]; + end + else + notchData{k} = []; + end + + % RPM filter data for motor noise overlay + tmpRPMidx = 46; + if exist('debugIdx','var') && numel(debugIdx) >= k + tmpRPMidx = debugIdx{k}.RPM_FILTER; + end + if exist('debugmode','var') && numel(debugmode) >= k && debugmode(k) == tmpRPMidx + rpmFilterData{k} = [T{k}.debug_0_(tIND{k}), T{k}.debug_1_(tIND{k}), ... + T{k}.debug_2_(tIND{k}), T{k}.debug_3_(tIND{k})]; + else + rpmFilterData{k} = []; + end + end + delayDataReady = true; +end + +clear s dat a RC smat amp2d2 freq2d2 +freq2d2 = {}; +amp2d2 = {}; +p=0; +tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); +tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); +tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); +for k = 1 : length(tmpSpecVal) + s = char(datSelectionString(tmpSpecVal(k))); + for f = 1 : size(tmpFileVal,2) + for a = axesOptionsSpec + if ( ( ~isempty(strfind(s,'axisD'))) && a==3) || isempty(s) + p=p+1; + smat{p}=[];%string + amp2d2{p}=[];%spec 2d + freq2d2{p}=[];% freq2d2 + else + p = p + 1; + clear dat + eval(['dat = T{tmpFileVal(f)}.' char(datSelectionString(tmpSpecVal(k))) '_' int2str(a-1) '_(tIND{tmpFileVal(f)})'';';]) + lograte = A_lograte(tmpFileVal(f));%in kHz + smat{p}=s; + eval(['[freq2d2{p}.f' int2str(f) ' amp2d2{p}.f' int2str(f) ' ]=PSSpec2d(dat,lograte, tmpPSDVal);']) %compute 2d amp spec at same time + end + end + end +end + + + +figure(PSspecfig2); +baselineYlines = [0 -50]; +multilineStyle = {'-' ; ':'; '--'}; +rpyLineStyle = {'-' ; '--'; ':'}; + +delete(subplot('position',posInfo.Spec2Pos(1,:))) +delete(subplot('position',posInfo.Spec2Pos(2,:))) +delete(subplot('position',posInfo.Spec2Pos(3,:))) +delete(subplot('position',posInfo.Spec2Pos(4,:))) +delete(subplot('position',posInfo.Spec2Pos(5,:))) +delete(subplot('position',posInfo.Spec2Pos(6,:))) +%%%%% plot 2d amp spec +axLabel={'Roll';'Pitch';'Yaw'}; + +p = 0; +tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); +tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); +tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); +tmpSmoothVal = get(guiHandlesSpec2.smoothFactor_select, 'Value'); +for k = 1 : length(tmpSpecVal) + s = char(datSelectionString(k)); + for f = 1 : size(tmpFileVal,2) + cnt = 0; + for a = axesOptionsSpec + cnt = cnt + 1; + p = p + 1; + if ~isempty(freq2d2) + if ~isempty(freq2d2{p}) && ~isempty(amp2d2{p}) + + if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 + + h2=subplot('position',posInfo.Spec2Pos(a,:)); + eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + hold on + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) + set(h2,'fontsize',fontsz) + set(h,'Color',[multiLineCols(f,:)]) + m = (A_lograte(tmpFileVal(f)) * 1000) / 2; + set(h2,'xtick',[0:m/10:m], 'yminortick','on') + axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + xlabel('Frequency (Hz)','fontweight','bold'); + if get(guiHandlesSpec2.checkboxPSD, 'Value') + ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + else + ylabel(['Amplitude'],'fontweight','bold'); + end + if a == 1 + title('Full Spectrum','fontweight','bold'); + end + if p < 4 + h=text(2,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); + set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); + end + grid on + + h2=subplot('position',posInfo.Spec2Pos(a+3,:)); + eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + hold on + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) + set(h2,'fontsize',fontsz) + set(h,'Color',[multiLineCols(f,:)]) + m = (A_lograte(tmpFileVal(f)) * 1000) / 2; + set(h2,'xtick',[0 20 40 60 80 100],'yminortick','on') + axis([0 100 climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + xlabel('Frequency (Hz)','fontweight','bold'); + if get(guiHandlesSpec2.checkboxPSD, 'Value') + ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + else + ylabel(['Amplitude'],'fontweight','bold'); + end + if a == 1 + title('Sub 100Hz','fontweight','bold'); + end + if p < 4 + h=text(1,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); + set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); + end + + %%%%%%%%%%%%%%%%%%% Plot Latencies %%%%%%%%%%%%%%% + tmpFileSelVals = get(guiHandlesSpec2.FileSelect, 'Value'); + tmpFileIdx = tmpFileSelVals(f); + % Per-file debug mode indices (BF version-aware) + if exist('debugIdx','var') && numel(debugIdx) >= tmpFileIdx + tmpDbgIdx = debugIdx{tmpFileIdx}; + else + tmpDbgIdx = struct('GYRO_SCALED',6,'GYRO_FILTERED',3,'RC_INTERPOLATION',7,'FFT_FREQ',17,'FEEDFORWARD',59); + end + if get(guiHandlesSpec2.Delay, 'Value') == 1 && a == 1 + if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 + h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' Debug01{tmpFileIdx} 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + else + h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + end + end + if get(guiHandlesSpec2.Delay, 'Value') == 2 + h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP-Gyro: ' int2str(SPGyroDelay(tmpFileIdx, a)) 'ms']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + end + if get(guiHandlesSpec2.Delay, 'Value') == 3 && a == 1 + if debugmode(tmpFileIdx) == tmpDbgIdx.RC_INTERPOLATION || debugmode(tmpFileIdx) == tmpDbgIdx.FEEDFORWARD + h=text(75, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP smoothing delay: ' Debug02{tmpFileIdx} 'ms']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + else + h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['debug mode not set ']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + end + end + if get(guiHandlesSpec2.Delay, 'Value') == 4 && a == 1 + if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 + h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' num2str(gyro_phase_shift_deg(tmpFileIdx)) 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + else + h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); + set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + end + end + + + else + % combine R P Y + h2=subplot('position',[0.0500 0.1000 0.800 0.840]); + eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + hold on + if k == 1 + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/1.4,'linestyle',rpyLineStyle{cnt}) + end + if k == 2 + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2.6,'linestyle',rpyLineStyle{cnt}) + end + set(h2,'fontsize',fontsz) + set(h,'Color',[multiLineCols(f,:)]) + m = (A_lograte(tmpFileVal(f)) * 1000) / 2; + set(h2,'xtick',[0:m/10:m], 'yminortick','on') + axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + xlabel('Frequency (Hz)','fontweight','bold'); + if get(guiHandlesSpec2.checkboxPSD, 'Value') + ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + else + ylabel(['Amplitude'],'fontweight','bold'); + end + if a == 1 + title('Full Spectrum','fontweight','bold'); + end + grid on + + + end + + grid on + + else + end + end + end + end +end + +l=0;legnd={}; +l2=0; +tmpSpecListStr = get(guiHandlesSpec2.SpecList, 'String'); +tmpSpecListVal = get(guiHandlesSpec2.SpecList, 'Value'); +tmpFileSelStr = get(guiHandlesSpec2.FileSelect, 'String'); +tmpFileSelVal = get(guiHandlesSpec2.FileSelect, 'Value'); +for m = 1 : length(tmpSpecListVal) + for n = 1 : length(tmpFileSelVal) + l = l + 1; + clear fstr fltDelayStr + fstr = char(tmpFileSelStr(tmpFileSelVal(n))); + if size(fstr,2) > 12, fstr = fstr(1,1:12); end % only use first 20 characters of file name + if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 + legnd{l} = [char(tmpSpecListStr(tmpSpecListVal(m))) ' | ' fstr]; + else + for a = axesOptionsSpec + l2 = l2 + 1; + legnd{l2} = [axLabel{a} ' | ' char(tmpSpecListStr(tmpSpecListVal(m))) ' | ' fstr ]; + end + end + end +end +if ~isempty(freq2d2) && ~isempty(amp2d2) + if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 + h=legend(legnd); + hPos = get(h, 'Position'); set(h, 'Position', [0.35 0.01 hPos(3:4)]); + else + h=legend(legnd, 'Location','NorthEast') + end +end + + +% Set up click-to-show-value datatips + double-click expand on all axes +PSdatatipSetup(PSspecfig2); + +set(PSspecfig2, 'pointer', 'arrow') +updateSpec=0; +end + + diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 65434bd..a578f5d 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -231,110 +231,16 @@ try set(guiHandlesSpec2.plotY, 'Value', defaults.Values(find(strcmp(defaults.Par try set(guiHandlesSpec2.RPYcomboSpec, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-SinglePanel')))), catch, set(guiHandlesSpec2.RPYcomboSpec, 'Value', 0), end -FilterDelayDterm={}; -SPGyroDelay=[]; -Debug01={}; -Debug02={}; -gyro_phase_shift_deg=zeros(Nfiles,1); -dterm_phase_shift_deg=zeros(Nfiles,1); -for k = 1 : Nfiles - Fs=1000/A_lograte(k);% yields more consistent results (mode(diff(tta))); - maxlag=round(30000/Fs); %~30ms delay - - - clear d pg g1 g1 s1 g2 s2 g3 s3 - try - pg = smooth(T{k}.debug_0_(tIND{k}),50); - catch - pg = 0; - end - g1 = smooth(T{k}.gyroADC_0_(tIND{k}),50); - s1 = smooth(T{k}.setpoint_0_(tIND{k}),50); - - g2 = smooth(T{k}.gyroADC_1_(tIND{k}),50); - s2 = smooth(T{k}.setpoint_1_(tIND{k}),50); - - g3 = smooth(T{k}.gyroADC_2_(tIND{k}),50); - s3 = smooth(T{k}.setpoint_2_(tIND{k}),50); - - - [c,lags] = xcorr(g1,pg,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, Debug01{k} = ' '; else Debug01{k} = num2str(d);end - - [c,lags] = xcorr(s1,pg,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, Debug02{k} = ' '; else Debug02{k} = num2str(d);end - - [c,lags] = xcorr(g1,s1,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,1) = 0; else, SPGyroDelay(k,1) = d; end - - [c,lags] = xcorr(g2,s2,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,2) = 0; else, SPGyroDelay(k,2) = d; end - - [c,lags] = xcorr(g3,s3,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,3) = 0; else, SPGyroDelay(k,3) = d; end - - clear d d1 d2 - try - d1 = smooth(T{k}.axisDpf_0_(tIND{k}),50); - d2 = smooth(T{k}.axisD_0_(tIND{k}),50); - [c,lags] = xcorr(d2,d1,maxlag); - d = lags(find(c==max(c))); - d=d * (Fs / 1000); - if d<.1, FilterDelayDterm{k} = ' '; else FilterDelayDterm{k} = num2str(d); end - catch - FilterDelayDterm{k} = ' '; - end - - try - if ~isempty(str2num(Debug01{k})) && SPGyroDelay(k,1) > 0 - [gyro_phase_shift_deg(k,1)] = round(PSphaseShiftDeg(str2num(Debug01{k}), 1000/(SPGyroDelay(k,1)) )); - end - if ~isempty(str2num(FilterDelayDterm{k})) && SPGyroDelay(k,1) > 0 - [dterm_phase_shift_deg(k,1)] = round(PSphaseShiftDeg(str2num(FilterDelayDterm{k}), 1000/(SPGyroDelay(k,1)) )); - end - catch, end - - %%%%%%%%%% extract dynamic notch data for FFT_FREQ overlay %%%%%%%%%% - tmpFFTidx = FFT_FREQ; % global default - if exist('debugIdx','var') && numel(debugIdx) >= k - tmpFFTidx = debugIdx{k}.FFT_FREQ; - end - if exist('debugmode','var') && numel(debugmode) >= k && debugmode(k) == tmpFFTidx - % FFT_FREQ debug field layout depends on BF version - if exist('fwMajor','var') && numel(fwMajor) >= k && fwMajor(k) >= 2025 - % BF 2025.12+: [0]=pre_DN_gyro, [1-3]=notch_Hz - notchData{k} = [T{k}.debug_1_(tIND{k}), T{k}.debug_2_(tIND{k}), T{k}.debug_3_(tIND{k})]; - else - % BF 4.3-4.5: [0-2]=notch_Hz, [3]=pre_DN_gyro - notchData{k} = [T{k}.debug_0_(tIND{k}), T{k}.debug_1_(tIND{k}), T{k}.debug_2_(tIND{k})]; - end - else - notchData{k} = []; - end - - %%%%%%%%%% extract RPM filter data for motor noise overlay %%%%%%%%%% - tmpRPMidx = 46; % default BF 4.x - if exist('debugIdx','var') && numel(debugIdx) >= k - tmpRPMidx = debugIdx{k}.RPM_FILTER; - end - if exist('debugmode','var') && numel(debugmode) >= k && debugmode(k) == tmpRPMidx - % RPM_FILTER: debug[0-3] = motor 1-4 fundamental frequency in Hz - rpmFilterData{k} = [T{k}.debug_0_(tIND{k}), T{k}.debug_1_(tIND{k}), ... - T{k}.debug_2_(tIND{k}), T{k}.debug_3_(tIND{k})]; - else - rpmFilterData{k} = []; - end -end +% Delay/overlay data computed lazily in PSplotSpec2D on "Run" click +if ~exist('FilterDelayDterm','var'), FilterDelayDterm = {}; end +if ~exist('SPGyroDelay','var'), SPGyroDelay = []; end +if ~exist('Debug01','var'), Debug01 = {}; end +if ~exist('Debug02','var'), Debug02 = {}; end +if ~exist('gyro_phase_shift_deg','var'), gyro_phase_shift_deg = zeros(Nfiles,1); end +if ~exist('dterm_phase_shift_deg','var'), dterm_phase_shift_deg = zeros(Nfiles,1); end +if ~exist('notchData','var'), notchData = {}; end +if ~exist('rpmFilterData','var'), rpmFilterData = {}; end +delayDataReady = false; diff --git a/src/util/PSstyleAxes.m b/src/util/PSstyleAxes.m index a7f3c9b..fad01ea 100644 --- a/src/util/PSstyleAxes.m +++ b/src/util/PSstyleAxes.m @@ -1,7 +1,7 @@ -function PSstyleAxes(ax, th) -%% PSstyleAxes - apply dark theme to axes -if nargin < 2, th = PStheme(); end -set(ax, 'Color', th.axesBg, 'XColor', th.axesFg, 'YColor', th.axesFg, ... - 'GridColor', th.gridColor, 'FontWeight', 'bold'); -grid(ax, 'on'); -end +function PSstyleAxes(ax, th) +%% PSstyleAxes - apply dark theme to axes +if nargin < 2, th = PStheme(); end +set(ax, 'Color', th.axesBg, 'XColor', th.axesFg, 'YColor', th.axesFg, ... + 'GridColor', th.gridColor, 'FontWeight', 'bold'); +grid(ax, 'on'); +end diff --git a/src/util/PSstyleFig.m b/src/util/PSstyleFig.m index 4dd6405..f737080 100644 --- a/src/util/PSstyleFig.m +++ b/src/util/PSstyleFig.m @@ -1,6 +1,6 @@ -function PSstyleFig(fig, titleStr) -%% PSstyleFig - apply dark theme to figure -th = PStheme(); -set(fig, 'Color', th.figBg, 'InvertHardcopy', 'off'); -if nargin >= 2, set(fig, 'Name', titleStr, 'NumberTitle', 'off'); end -end +function PSstyleFig(fig, titleStr) +%% PSstyleFig - apply dark theme to figure +th = PStheme(); +set(fig, 'Color', th.figBg, 'InvertHardcopy', 'off'); +if nargin >= 2, set(fig, 'Name', titleStr, 'NumberTitle', 'off'); end +end diff --git a/src/util/PSstyleLegend.m b/src/util/PSstyleLegend.m index 916bc62..6a85b85 100644 --- a/src/util/PSstyleLegend.m +++ b/src/util/PSstyleLegend.m @@ -1,5 +1,5 @@ -function PSstyleLegend(lg, th) -%% PSstyleLegend - apply dark theme to legend -if nargin < 2, th = PStheme(); end -set(lg, 'TextColor', th.legendFg, 'Color', th.legendBg, 'EdgeColor', th.legendEdge); -end +function PSstyleLegend(lg, th) +%% PSstyleLegend - apply dark theme to legend +if nargin < 2, th = PStheme(); end +set(lg, 'TextColor', th.legendFg, 'Color', th.legendBg, 'EdgeColor', th.legendEdge); +end diff --git a/src/util/PStheme.m b/src/util/PStheme.m index e513c02..6ec9ff8 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -1,59 +1,59 @@ -function th = PStheme() -%% PStheme - central UI theme for PIDscope (dark) - -% figure & panel -th.figBg = [.18 .18 .20]; -th.panelBg = [.22 .22 .24]; -th.panelFg = [.90 .90 .90]; -th.panelBorder = [.35 .35 .38]; - -% axes -th.axesBg = [.10 .10 .12]; -th.axesFg = [.75 .75 .75]; -th.gridColor = [.28 .28 .30]; - -% text -th.textPrimary = [.90 .90 .90]; -th.textSecondary = [.65 .65 .65]; -th.textAccent = [.40 .80 1.0]; - -% legend -th.legendBg = [.16 .16 .18]; -th.legendFg = [.80 .80 .80]; -th.legendEdge = [.35 .35 .38]; - -% epoch shading (trim regions) -th.epochFill = [.30 .30 .32]; -th.epochAlpha = 0.7; - -% buttons -th.btnRun = [.20 .70 .30]; -th.btnReset = [.85 .55 .15]; -th.btnSave = [.65 .65 .65]; -th.btnDash1 = [.85 .25 .25]; % Spectral Analyzer -th.btnDash2 = [.25 .50 .90]; % Step Response -th.btnDash3 = [.85 .55 .15]; % Filter Sim -th.btnDash4 = [.20 .70 .30]; % Motor Noise -th.btnDash5 = [.20 .80 .80]; % Chirp Analysis -th.btnLink = [.85 .55 .15]; % Support PIDscope - -% checkbox / input bg -th.checkBg = [.18 .18 .20]; -th.inputBg = [.14 .14 .16]; -th.inputFg = [.90 .90 .90]; - -% signal colors (bright for dark bg) -th.sigDebug = [.50 .50 .50]; -th.sigGyro = [.85 .85 .85]; -th.sigPterm = [.20 .85 .20]; -th.sigIterm = [.90 .75 .20]; -th.sigDprefilt = [.45 .80 .95]; -th.sigDterm = [.30 .40 .95]; -th.sigFterm = [.75 .45 .45]; -th.sigSetpoint = [.90 .25 .35]; -th.sigPIDsum = [1.0 .35 .90]; -th.sigPIDerr = [.55 .20 .95]; -th.sigMotor = {[.20 .85 .30], [.85 .70 .15], [.20 .40 .95], [.30 .95 .85]}; -th.sigThrottle = [.85 .85 .85]; - -end +function th = PStheme() +%% PStheme - central UI theme for PIDscope (dark) + +% figure & panel +th.figBg = [.18 .18 .20]; +th.panelBg = [.22 .22 .24]; +th.panelFg = [.90 .90 .90]; +th.panelBorder = [.35 .35 .38]; + +% axes +th.axesBg = [.10 .10 .12]; +th.axesFg = [.75 .75 .75]; +th.gridColor = [.28 .28 .30]; + +% text +th.textPrimary = [.90 .90 .90]; +th.textSecondary = [.65 .65 .65]; +th.textAccent = [.40 .80 1.0]; + +% legend +th.legendBg = [.16 .16 .18]; +th.legendFg = [.80 .80 .80]; +th.legendEdge = [.35 .35 .38]; + +% epoch shading (trim regions) +th.epochFill = [.30 .30 .32]; +th.epochAlpha = 0.7; + +% buttons +th.btnRun = [.20 .70 .30]; +th.btnReset = [.85 .55 .15]; +th.btnSave = [.65 .65 .65]; +th.btnDash1 = [.85 .25 .25]; % Spectral Analyzer +th.btnDash2 = [.25 .50 .90]; % Step Response +th.btnDash3 = [.85 .55 .15]; % Filter Sim +th.btnDash4 = [.20 .70 .30]; % Motor Noise +th.btnDash5 = [.20 .80 .80]; % Chirp Analysis +th.btnLink = [.85 .55 .15]; % Support PIDscope + +% checkbox / input bg +th.checkBg = [.18 .18 .20]; +th.inputBg = [.14 .14 .16]; +th.inputFg = [.90 .90 .90]; + +% signal colors (bright for dark bg) +th.sigDebug = [.50 .50 .50]; +th.sigGyro = [.85 .85 .85]; +th.sigPterm = [.20 .85 .20]; +th.sigIterm = [.90 .75 .20]; +th.sigDprefilt = [.45 .80 .95]; +th.sigDterm = [.30 .40 .95]; +th.sigFterm = [.75 .45 .45]; +th.sigSetpoint = [.90 .25 .35]; +th.sigPIDsum = [1.0 .35 .90]; +th.sigPIDerr = [.55 .20 .95]; +th.sigMotor = {[.20 .85 .30], [.85 .70 .15], [.20 .40 .95], [.30 .95 .85]}; +th.sigThrottle = [.85 .85 .85]; + +end From 8606ff9e5aa79fc3cce5497c87a2159b8cbebf37 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 11:21:33 +0100 Subject: [PATCH 05/80] perf(spectral): replace eval() with dynamic field access in plot files --- src/plot/PSfreqTime.m | 3 ++- src/plot/PSplotSpec.m | 3 ++- src/plot/PSplotSpec2D.m | 25 +++++++++++++++++-------- src/plot/PStuningParams.m | 13 +++++++------ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 13ccb2e..ee1f582 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -42,7 +42,8 @@ delete(subplot('position',posInfo.Spec3Pos(i,:))); try if ~updateSpec - eval(['dat = T{tmpFileVal3}.' char(datSelectionString(tmpSpecVal3)) '_' int2str(i-1) '_(tIND{tmpFileVal3})'';';]) + fld = [char(datSelectionString(tmpSpecVal3)) '_' int2str(i-1) '_']; + dat = T{tmpFileVal3}.(fld)(tIND{tmpFileVal3})'; [Tm F specMat{i}] = PStimeFreqCalc(dat', A_lograte(tmpFileVal3), specSmoothFactors(tmpSmoothVal3), timeSmoothFactors(tmpSubVal3)); end diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index d275733..5b8640c 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -77,7 +77,8 @@ else p=p+1; try - eval(['dat{k}(a,:) = T{tmpFileSelK}.' char(datSelectionString(vars(k))) '_' int2str(a-1) '_(tIND{tmpFileSelK});';]) + fld = [char(datSelectionString(vars(k))) '_' int2str(a-1) '_']; + dat{k}(a,:) = T{tmpFileSelK}.(fld)(tIND{tmpFileSelK}); Throt=T{tmpFileSelK}.setpoint_3_(tIND{tmpFileSelK}) / 10;% throttle lograte = A_lograte(tmpFileSelK);%in kHz waitbar(min(1, p/12), hw, ['processing spectrogram... ' int2str(p) ]); diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 2b4071d..3cc51f0 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -45,6 +45,7 @@ %%% compute delay/overlay data (deferred from UI open to Run click) if ~exist('delayDataReady','var') || ~delayDataReady + hw_delay = waitbar(0, 'computing delays...'); FilterDelayDterm={}; SPGyroDelay=[]; Debug01={}; @@ -54,6 +55,7 @@ notchData={}; rpmFilterData={}; for k = 1 : Nfiles + waitbar(k/Nfiles, hw_delay, ['computing delays... file ' int2str(k) '/' int2str(Nfiles)]); Fs=1000/A_lograte(k); maxlag=round(30000/Fs); @@ -139,12 +141,14 @@ end end delayDataReady = true; + try close(hw_delay); catch, end end clear s dat a RC smat amp2d2 freq2d2 freq2d2 = {}; amp2d2 = {}; p=0; +hw_fft = waitbar(0, 'computing FFT...'); tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); @@ -159,16 +163,18 @@ freq2d2{p}=[];% freq2d2 else p = p + 1; - clear dat - eval(['dat = T{tmpFileVal(f)}.' char(datSelectionString(tmpSpecVal(k))) '_' int2str(a-1) '_(tIND{tmpFileVal(f)})'';';]) - lograte = A_lograte(tmpFileVal(f));%in kHz + fld = [s '_' int2str(a-1) '_']; + dat = T{tmpFileVal(f)}.(fld)(tIND{tmpFileVal(f)})'; + lograte = A_lograte(tmpFileVal(f)); smat{p}=s; - eval(['[freq2d2{p}.f' int2str(f) ' amp2d2{p}.f' int2str(f) ' ]=PSSpec2d(dat,lograte, tmpPSDVal);']) %compute 2d amp spec at same time + waitbar(min(1, p/(length(tmpSpecVal)*size(tmpFileVal,2)*length(axesOptionsSpec))), hw_fft, ['computing FFT... ' int2str(p)]); + ff = ['f' int2str(f)]; + [freq2d2{p}.(ff) amp2d2{p}.(ff)] = PSSpec2d(dat,lograte, tmpPSDVal); end end end end - +try close(hw_fft); catch, end figure(PSspecfig2); @@ -203,7 +209,8 @@ if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 h2=subplot('position',posInfo.Spec2Pos(a,:)); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + ff = ['f' int2str(f)]; + h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) set(h2,'fontsize',fontsz) @@ -227,7 +234,8 @@ grid on h2=subplot('position',posInfo.Spec2Pos(a+3,:)); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + ff = ['f' int2str(f)]; + h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) set(h2,'fontsize',fontsz) @@ -294,7 +302,8 @@ else % combine R P Y h2=subplot('position',[0.0500 0.1000 0.800 0.840]); - eval(['h=plot(freq2d2{p}.f' int2str(f) ', smooth(amp2d2{p}.f' int2str(f) ', log10(size(amp2d2{p}.f' int2str(f) ',1)) * (tmpSmoothVal^3), ''lowess''));hold on']) + ff = ['f' int2str(f)]; + h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on if k == 1 set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/1.4,'linestyle',rpyLineStyle{cnt}) diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 025ba91..5b91b9d 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -51,8 +51,8 @@ try if ~updateStep clear H G L - eval(['H = T{f}.setpoint_' int2str(p-1) '_(tIND{f});']) - eval(['G = T{f}.gyroADC_' int2str(p-1) '_(tIND{f});']) + H = T{f}.(['setpoint_' int2str(p-1) '_'])(tIND{f}); + G = T{f}.(['gyroADC_' int2str(p-1) '_'])(tIND{f}); [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value')); % xcorrLag(p) = finddelay(H, G) * A_lograte(f); end @@ -75,9 +75,10 @@ peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); - eval(['PID=' ylab2{p} 'PIDF{f};']) + pidvar = [ylab2{p} 'PIDF']; + PID = eval([pidvar '{f}']); if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); + h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') set(h,'fontsize',fontsz) else @@ -150,8 +151,8 @@ peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); - eval(['PID=' ylab2{p} 'PIDF{f};']) - if cnt <= 3 + PID = eval([ylab2{p} 'PIDF{f}']); + if cnt <= 3 if size(axesOptions,2) < 2 h=text(505, ypos(p)+0.04, [ylab{p}]); else From 8d4eb68670b0bcab1b834c4e95c1c7a347d04848 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 17:01:04 +0100 Subject: [PATCH 06/80] feat(ui): unified dark theme across all dashboards --- PIDscope.m | 76 +++++++-------- src/plot/PSdispSetupInfo.m | 15 ++- src/plot/PSdynSpecPlayer.m | 25 +++-- src/plot/PSfilterSim.m | 42 ++++----- src/plot/PSfreqTime.m | 11 ++- src/plot/PSplotBode.m | 41 ++++----- src/plot/PSplotLogViewer.m | 38 ++++---- src/plot/PSplotMotorNoise.m | 71 +++++++------- src/plot/PSplotPIDerror.m | 7 +- src/plot/PSplotSpec.m | 25 ++--- src/plot/PSplotSpec2D.m | 43 +++++---- src/plot/PSplotStats.m | 148 +++++++++++++++--------------- src/plot/PStuningParams.m | 41 +++++---- src/ui/PSdispSetupInfoUIcontrol.m | 1 + src/ui/PSerrUIcontrol.m | 4 +- src/ui/PSfreqTimeUIcontrol.m | 7 +- src/ui/PSspec2DUIcontrol.m | 5 +- src/ui/PSspecUIcontrol.m | 5 +- src/ui/PSstatsUIcontrol.m | 5 +- src/ui/PStuneUIcontrol.m | 4 +- src/ui/PSviewerUIcontrol.m | 4 +- src/util/PSstyleAxes.m | 8 +- src/util/PSstyleControls.m | 20 ++++ 23 files changed, 347 insertions(+), 299 deletions(-) create mode 100644 src/util/PSstyleControls.m diff --git a/PIDscope.m b/PIDscope.m index 63c6daa..9b048e3 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -89,10 +89,14 @@ set(0,'defaultUicontrolFontName', 'Helvetica') % defaultUicontrolFontSize is set after fontsz is calculated (below) -%%%% assign main figure handle and define some UI variables +%%%% assign main figure handle and define some UI variables PSfig = figure(1); set(PSfig, 'InvertHardcopy', 'off'); -bgcolor=[.95 .95 .95]; +th = PStheme(); +bgcolor = th.figBg; +panelBg = th.panelBg; +panelFg = th.panelFg; +panelBorder = th.panelBorder; set(PSfig,'color',bgcolor); wikipage = 'https://buymeacoffee.com/dzikus'; @@ -123,16 +127,16 @@ plotall_flag=-1; -colorA=[.8 .1 .2]; +colorA = th.btnDash1; colorA2=[.4 .0 .6]; -colorB=[.1 .4 .8]; +colorB = th.btnDash2; colorC=[1 .2 .2]; colorD=[.1 .7 .2]; -colRun = [0 .5 0]; -saveCol = [.1 .1 .1]; -setUpCol = [.1 .1 .1]; -cautionCol = [0.6 0.3 0]; +colRun = th.btnRun; +saveCol = th.btnSave; +setUpCol = th.textSecondary; +cautionCol = th.btnReset; %use_phsCorrErr=0; flightSpec=0; screensz = get(0,'ScreenSize'); @@ -163,6 +167,8 @@ rs = 0.034; rh = 0.030; end set(0,'defaultUicontrolFontSize', fontsz) +set(0,'defaultUicontrolForegroundColor', th.textPrimary) +set(0,'defaultUicontrolBackgroundColor', th.panelBg) row = 1; posInfo.firmware =[cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; @@ -188,7 +194,8 @@ posInfo.PIDtuningService = [cpL+.003 vPos-rs*row cpW-.006 rh]; cpH = rs*row + 0.04; % control panel height = rows + title margin controlpanel = uipanel('Title','Control Panel','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[cpL vPos-cpH+0.02 cpW cpH]); % Position info table just below control panel @@ -202,30 +209,24 @@ fnameMaster = {}; fcnt = 0; -% ColorSet=colormap(jet);%hsv jet gray lines colorcube -% j=[1 8 17 20 23 27 45 50 58 64]; -ColorSet=[.6 .6 .6;..., % gray - Gyro raw - 0 0 0;..., % black - Gyro filt - 0 .7 0;..., % green - Pterm - .8 .65 .1;..., % yellow - I term - .3 .7 .9;..., % light blue - Dterm raw - .1 .2 .8;..., % dark blue -Dterm Filt - .6 .3 .3;..., % brown - Fterm - .8 0 .2;..., % dark red - 1 .2 .9;..., % light purple - .4 0 .9;..., % dark purple - .9 0 0;..., %M1 - 1 .6 0;..., %M2 -0 0 .9;..., %M3 -.1 1 .8;..., %M4 - 0 0 0;..., % throttle - 0 0 0]; % all -j=[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]; - -k=1; -for i=1:length(j) - eval(['linec.col' int2str(k-1) '=ColorSet(j(i),:);']); - k=k+1; +ColorSet=[th.sigDebug;... % Debug + th.sigGyro;... % Gyro + th.sigPterm;... % Pterm + th.sigIterm;... % Iterm + th.sigDprefilt;... % Dterm prefilt + th.sigDterm;... % Dterm + th.sigFterm;... % Fterm + th.sigSetpoint;... % Setpoint + th.sigPIDsum;... % PIDsum + th.sigPIDerr;... % PIDerr + th.sigMotor{1};... % M1 + th.sigMotor{2};... % M2 + th.sigMotor{3};... % M3 + th.sigMotor{4};... % M4 + th.sigThrottle;... % Throttle + th.textPrimary]; % All +for k=0:15 + linec.(['col' int2str(k)]) = ColorSet(k+1,:); end %%% tooltips @@ -350,12 +351,14 @@ defaults = readtable('PSdefaults.txt'); a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) + set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']); + try set(t, 'BackgroundColor', th.panelBg, 'ForegroundColor', th.textSecondary); catch, end catch - defaults = ' '; + defaults = ' '; a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) + set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']); + try set(t, 'BackgroundColor', th.panelBg, 'ForegroundColor', th.textSecondary); catch, end end @@ -376,3 +379,4 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para set(PSfig, 'Position', tmpPos); drawnow; end +PSstyleControls(PSfig, th); diff --git a/src/plot/PSdispSetupInfo.m b/src/plot/PSdispSetupInfo.m index 7ed0884..c781ab2 100644 --- a/src/plot/PSdispSetupInfo.m +++ b/src/plot/PSdispSetupInfo.m @@ -22,17 +22,18 @@ setupB=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,2)); end + th = PStheme(); BGCol = []; try for i = 1 : size(setupA,1) if strcmp(setupA{i}, setupB{i}) - BGCol(i,:) = [1 1 1]; + BGCol(i,:) = th.panelBg; else - BGCol(i,:) = [1 .7 .7]; + BGCol(i,:) = [.6 .2 .2]; end end catch - BGCol=[1 1 1]; + BGCol=th.panelBg; end u=[]; u = (sum(BGCol,2)/3) < 1; @@ -40,20 +41,24 @@ if get(guiHandlesInfo.checkboxDIFF, 'Value') == 1 t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) - set(t,'BackgroundColor', [1 .7 .7]) + set(t,'BackgroundColor', [.6 .2 .2]) + try set(t,'ForegroundColor', th.textPrimary); catch, end if Nfiles > 1 t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) - set(t,'BackgroundColor', [1 .7 .7]) + set(t,'BackgroundColor', [.6 .2 .2]) + try set(t,'ForegroundColor', th.textPrimary); catch, end end else t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) set(t,'BackgroundColor', [BGCol]) + try set(t,'ForegroundColor', th.textPrimary); catch, end if Nfiles > 1 t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) set(t,'BackgroundColor', [BGCol]) + try set(t,'ForegroundColor', th.textPrimary); catch, end end end end diff --git a/src/plot/PSdynSpecPlayer.m b/src/plot/PSdynSpecPlayer.m index 3c866ae..173fd7d 100644 --- a/src/plot/PSdynSpecPlayer.m +++ b/src/plot/PSdynSpecPlayer.m @@ -33,8 +33,9 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) screensz = get(0, 'ScreenSize'); figW = round(.6 * screensz(3)); figH = round(.85 * screensz(4)); +thm = PStheme(); fig = figure('Name', ['Spectrum Player - ' signalName], 'NumberTitle', 'off', ... - 'Color', [.15 .15 .15], 'Position', [round(.2*screensz(3)) round(.07*screensz(4)) figW figH]); + 'Color', thm.figBg, 'Position', [round(.2*screensz(3)) round(.07*screensz(4)) figW figH]); axColors = {[0 .85 .85], [.85 .85 0], [.85 .3 .85]}; @@ -52,19 +53,16 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) ax = axes('Parent', fig, 'Units', 'normalized', ... 'Position', [.07 yPos .86 specH - specGap]); specLines{vi} = plot(ax, F, specData{k}(:, 1), 'Color', axColors{k}, 'LineWidth', 1.2); - set(ax, 'XLim', [F(1) F(end)], 'YLim', [yLo-yPad yHi+yPad], ... - 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 13, 'FontWeight', 'bold'); - set(get(ax, 'YLabel'), 'String', 'dB', 'Color', [.8 .8 .8]); + set(ax, 'XLim', [F(1) F(end)], 'YLim', [yLo-yPad yHi+yPad]); + PSstyleAxes(ax, thm); + set(get(ax, 'YLabel'), 'String', 'dB'); if vi == nValid - set(get(ax, 'XLabel'), 'String', 'Frequency (Hz)', 'Color', [.8 .8 .8]); + set(get(ax, 'XLabel'), 'String', 'Frequency (Hz)'); else set(ax, 'XTickLabel', []); end titleHandles{vi} = title(ax, axisLabels{k}); set(titleHandles{vi}, 'Color', axColors{k}); - grid(ax, 'on'); - set(ax, 'GridColor', [.3 .3 .3]); axSpec{vi} = ax; end @@ -72,8 +70,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) sgIdx = chIdx(1); axSg = axes('Parent', fig, 'Units', 'normalized', 'Position', [.07 .10 .86 .16]); imagesc(axSg, specMatCell{sgIdx}); -set(axSg, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 11, 'FontWeight', 'bold'); +PSstyleAxes(axSg, thm); nYt = 4; yTk = linspace(1, nFreq, nYt+1); @@ -84,7 +81,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) xTk = linspace(1, nFrames, nXt+1); xLb = arrayfun(@(x) sprintf('%.1f', interp1(1:nFrames, Tm, x)), xTk, 'UniformOutput', false); set(axSg, 'XTick', xTk, 'XTickLabel', xLb); -set(get(axSg, 'XLabel'), 'String', 'Time (s)', 'Color', [.8 .8 .8]); +set(get(axSg, 'XLabel'), 'String', 'Time (s)'); try colormap(axSg, hot); catch, end hold(axSg, 'on'); @@ -96,7 +93,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) 'String', sprintf('%s t = %.2fs', signalName, Tm(1)), ... 'Units', 'normalized', 'Position', [.07 .27 .40 .025], ... 'FontSize', 12, 'FontWeight', 'bold', ... - 'BackgroundColor', [.15 .15 .15], 'ForegroundColor', [.9 .9 .9], ... + 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textPrimary, ... 'HorizontalAlignment', 'left'); % transport controls @@ -118,7 +115,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) uicontrol(fig, 'Style', 'text', 'String', 'Speed:', ... 'Units', 'normalized', 'Position', [.30 btnY+.005 .045 .030], ... - 'FontSize', 11, 'BackgroundColor', [.15 .15 .15], 'ForegroundColor', [.8 .8 .8]); + 'FontSize', 11, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textPrimary); speedMenu = uicontrol(fig, 'Style', 'popupmenu', ... 'String', {'0.25x', '0.5x', '1x', '2x', '4x'}, 'Value', 3, ... 'Units', 'normalized', 'Position', [.35 btnY .06 btnH], 'FontSize', 11); @@ -126,7 +123,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) frameLbl = uicontrol(fig, 'Style', 'text', ... 'String', sprintf('1 / %d', nFrames), ... 'Units', 'normalized', 'Position', [.42 btnY+.005 .08 .030], ... - 'FontSize', 10, 'BackgroundColor', [.15 .15 .15], 'ForegroundColor', [.6 .6 .6], ... + 'FontSize', 10, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textSecondary, ... 'HorizontalAlignment', 'center'); timeSlider = uicontrol(fig, 'Style', 'slider', 'Min', 1, 'Max', nFrames, 'Value', 1, ... diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 85179d7..26019aa 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -4,9 +4,10 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) % Fs - sample rate (Hz) % setupInfo - cell array {param, value} from header +thm = PStheme(); screensz = get(0, 'ScreenSize'); fig = figure('Name', 'Filter Simulation', 'NumberTitle', 'off', ... - 'Color', [.15 .15 .15], ... + 'Color', thm.figBg, ... 'Position', round([.1*screensz(3) .08*screensz(4) .75*screensz(3) .8*screensz(4)])); fp = parseFilterParams(setupInfo); @@ -18,12 +19,13 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) axTime = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .10 .62 .36]); cpL = .72; cpW = .27; -uipanel('Parent', fig, 'Title', 'Filter Settings', 'FontSize', 9, 'FontWeight', 'bold', ... - 'BackgroundColor', [.25 .25 .25], 'ForegroundColor', [.9 .9 .9], ... +uipanel('Parent', fig, 'Title', 'Filter Settings', 'FontWeight', 'bold', ... + 'BackgroundColor', thm.panelBg, 'ForegroundColor', thm.panelFg, ... + 'HighlightColor', thm.panelBorder, ... 'FontSize', 12, 'Position', [cpL .02 cpW .96]); row = .92; rh = .032; gap = .005; -bgc = [.25 .25 .25]; fgc = [.9 .9 .9]; +bgc = thm.panelBg; fgc = thm.panelFg; cb = @(~,~) doUpdate(); % axis selector @@ -137,16 +139,13 @@ function doUpdate() plot(axSpec, fF, sF, 'c', 'LineWidth', 1.3); plot(axSpec, fO, sD, 'Color', [.4 .9 .4], 'LineWidth', 0.8); hold(axSpec, 'off'); - set(axSpec, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 13, 'FontWeight', 'bold', 'XLim', [0 Fs/2]); - set(get(axSpec, 'XLabel'), 'String', 'Frequency (Hz)', 'Color', [.8 .8 .8]); - set(get(axSpec, 'YLabel'), 'String', 'PSD (dB)', 'Color', [.8 .8 .8]); - th = title(axSpec, [axNames{ai} ' - Spectrum']); - set(th, 'Color', [.9 .9 .9]); - grid(axSpec, 'on'); set(axSpec, 'GridColor', [.3 .3 .3]); - legend(axSpec, {'Raw gyro', 'Filtered gyro', 'D-term (filtered)'}, ... - 'TextColor', [.8 .8 .8], 'Color', [.2 .2 .2], 'EdgeColor', [.4 .4 .4], ... - 'Location', 'northeast', 'FontSize', 11); + PSstyleAxes(axSpec, thm); set(axSpec, 'XLim', [0 Fs/2]); + set(get(axSpec, 'XLabel'), 'String', 'Frequency (Hz)'); + set(get(axSpec, 'YLabel'), 'String', 'PSD (dB)'); + title(axSpec, [axNames{ai} ' - Spectrum']); + h_leg = legend(axSpec, {'Raw gyro', 'Filtered gyro', 'D-term (filtered)'}, ... + 'Location', 'northeast'); + try PSstyleLegend(h_leg, thm); catch, end N = min(2000, length(dat)); t = (0:N-1) / Fs * 1000; @@ -155,15 +154,12 @@ function doUpdate() hold(axTime, 'on'); plot(axTime, t, filt(1:N), 'c', 'LineWidth', 1.2); hold(axTime, 'off'); - set(axTime, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 13, 'FontWeight', 'bold'); - set(get(axTime, 'XLabel'), 'String', 'Time (ms)', 'Color', [.8 .8 .8]); - set(get(axTime, 'YLabel'), 'String', 'deg/s', 'Color', [.8 .8 .8]); - th2 = title(axTime, [axNames{ai} ' - Time Domain']); - set(th2, 'Color', [.9 .9 .9]); - grid(axTime, 'on'); set(axTime, 'GridColor', [.3 .3 .3]); - legend(axTime, {'Raw', 'Filtered'}, 'TextColor', [.8 .8 .8], ... - 'Color', [.2 .2 .2], 'EdgeColor', [.4 .4 .4], 'Location', 'northeast', 'FontSize', 11); + PSstyleAxes(axTime, thm); + set(get(axTime, 'XLabel'), 'String', 'Time (ms)'); + set(get(axTime, 'YLabel'), 'String', 'deg/s'); + title(axTime, [axNames{ai} ' - Time Domain']); + h_leg = legend(axTime, {'Raw', 'Filtered'}, 'Location', 'northeast'); + try PSstyleLegend(h_leg, thm); catch, end end end diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index ee1f582..b3de8c2 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -8,7 +8,8 @@ % ---------------------------------------------------------------------------------- -%% update fonts +th = PStheme(); +%% update fonts set(PSspecfig3, 'pointer', 'watch') figure(PSspecfig3) @@ -52,8 +53,8 @@ set(gca,'Clim',[ClimScale3], 'fontsize',fontsz,'fontweight','bold') title(''); - set(get(gca,'Ylabel'), 'String', ['Frequency (Hz) ' axisLabel{i}]); - set(get(gca,'Xlabel'), 'String', 'Time (sec)'); + set(get(gca,'Ylabel'), 'String', ['Frequency (Hz) ' axisLabel{i}], 'Color', th.textPrimary); + set(get(gca,'Xlabel'), 'String', 'Time (sec)', 'Color', th.textPrimary); F2 = F(F<=fLim_freqTime); freqStr = flipud(int2str((0: F2(end) / 5: F2(end))')); timeStr = int2str((0: round(Tm(end)) / 10: round(Tm(end)))'); @@ -77,6 +78,7 @@ catch, end cbar = colorbar('EastOutside'); set(get(cbar, 'Label'), 'String', 'Power Spectral density (dB)'); + try set(cbar, 'Color', th.axesFg); catch, end if i == 3 && (strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisD') || strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisDpf')) delete(subplot('position',posInfo.Spec3Pos(i,:))); @@ -118,6 +120,9 @@ end updateSpec = 0; +% Apply dark theme to all axes +allax = findobj(PSspecfig3, 'Type', 'axes'); +for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end % Set up click-to-show-value datatips + double-click expand on all axes PSdatatipSetup(PSspecfig3); diff --git a/src/plot/PSplotBode.m b/src/plot/PSplotBode.m index 3932c21..3f8fb82 100644 --- a/src/plot/PSplotBode.m +++ b/src/plot/PSplotBode.m @@ -7,9 +7,10 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) % stepData - struct with .t_ms and .step (or [] to skip) % titleStr - plot title suffix +th = PStheme(); screensz = get(0, 'ScreenSize'); fig = figure('Name', ['Chirp Analysis - ' titleStr], 'NumberTitle', 'off', ... - 'Color', [.15 .15 .15], ... + 'Color', th.figBg, ... 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); freq = freq(:); @@ -23,14 +24,14 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) if ~isempty(G_plant) mag_P = 20*log10(abs(G_plant(freq > 0))); semilogx(ax1, fPlot, mag_P, 'Color', [1 .5 0], 'LineWidth', 1.2); - legend(ax1, {'Tracking (T)', 'Plant (P)'}, 'TextColor', [.8 .8 .8], ... - 'Color', [.2 .2 .2], 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'southwest'); + h_leg = legend(ax1, {'Tracking (T)', 'Plant (P)'}, 'Location', 'southwest'); + try PSstyleLegend(h_leg, th); catch, end end line(ax1, [fPlot(1) fPlot(end)], [0 0], 'Color', [.5 .5 .5], 'LineStyle', '--'); hold(ax1, 'off'); -styleDark(ax1); -set(get(ax1, 'YLabel'), 'String', 'Magnitude (dB)', 'Color', [.8 .8 .8]); -th1 = title(ax1, ['Bode - ' titleStr]); set(th1, 'Color', [.9 .9 .9]); +PSstyleAxes(ax1, th); +set(get(ax1, 'YLabel'), 'String', 'Magnitude (dB)'); +th1 = title(ax1, ['Bode - ' titleStr]); % --- phase --- ax2 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.08 .42 .55 .22]); @@ -43,8 +44,8 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) end line(ax2, [fPlot(1) fPlot(end)], [-180 -180], 'Color', [.8 .3 .3], 'LineStyle', '--'); hold(ax2, 'off'); -styleDark(ax2); -set(get(ax2, 'YLabel'), 'String', 'Phase (deg)', 'Color', [.8 .8 .8]); +PSstyleAxes(ax2, th); +set(get(ax2, 'YLabel'), 'String', 'Phase (deg)'); % --- coherence --- ax3 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.08 .08 .55 .26]); @@ -52,10 +53,10 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) hold(ax3, 'on'); line(ax3, [fPlot(1) fPlot(end)], [.8 .8], 'Color', [.5 .5 .5], 'LineStyle', '--'); hold(ax3, 'off'); -styleDark(ax3); +PSstyleAxes(ax3, th); set(ax3, 'YLim', [0 1.05]); -set(get(ax3, 'XLabel'), 'String', 'Frequency (Hz)', 'Color', [.8 .8 .8]); -set(get(ax3, 'YLabel'), 'String', 'Coherence', 'Color', [.8 .8 .8]); +set(get(ax3, 'XLabel'), 'String', 'Frequency (Hz)'); +set(get(ax3, 'YLabel'), 'String', 'Coherence'); linkaxes([ax1 ax2 ax3], 'x'); if ~isempty(fPlot) @@ -79,12 +80,10 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) end hold(ax4, 'off'); end -set(ax4, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 12, 'FontWeight', 'bold'); -grid(ax4, 'on'); set(ax4, 'GridColor', [.3 .3 .3]); -set(get(ax4, 'XLabel'), 'String', 'Time (ms)', 'Color', [.8 .8 .8]); -set(get(ax4, 'YLabel'), 'String', 'Step Response', 'Color', [.8 .8 .8]); -th4 = title(ax4, 'Step (from FRD)'); set(th4, 'Color', [.9 .9 .9]); +PSstyleAxes(ax4, th); +set(get(ax4, 'XLabel'), 'String', 'Time (ms)'); +set(get(ax4, 'YLabel'), 'String', 'Step Response'); +title(ax4, 'Step (from FRD)'); % --- info panel (bottom right) --- ax5 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.72 .08 .24 .26]); @@ -105,7 +104,7 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) infoLines{end+1} = sprintf('Settling (2%%): %.0f ms', stepData.t_ms(settled)); end end -text(0.05, 0.9, infoLines, 'Parent', ax5, 'Color', [.9 .9 .3], ... +text(0.05, 0.9, infoLines, 'Parent', ax5, 'Color', th.textAccent, ... 'FontSize', 12, 'FontWeight', 'bold', 'VerticalAlignment', 'top', ... 'Units', 'normalized'); @@ -114,12 +113,6 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) end -function styleDark(ax) - set(ax, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 12, 'FontWeight', 'bold'); - grid(ax, 'on'); set(ax, 'GridColor', [.3 .3 .3]); -end - function [gm_dB, pm_deg, wcg, wcp] = margins_from_G(freq, G) % gain margin: gain at -180 deg phase crossing diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index a45f11d..fa1a138 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -11,6 +11,7 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) set(PSfig, 'pointer', 'watch') + th = PStheme(); global logviewerYscale logviewerYscale = str2num(get(guiHandles.maxY_input, 'String')); @@ -175,12 +176,12 @@ xmax=max(tta{fileIdx}/us2sec); - h=plot([0 xmax],[-maxY -maxY],'k'); + h=plot([0 xmax],[-maxY -maxY],'Color',th.axesFg); set(h,'linewidth',.2) hold on - - set(gca,'ytick',[ -(maxY/2) 0 maxY/2 ],'yticklabel',{num2str(-(maxY/2)) '0' num2str((maxY/2)) ''},'YColor',[.2 .2 .2],'fontweight','bold') - set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) + set(gca,'Color',th.axesBg); + set(gca,'ytick',[ -(maxY/2) 0 maxY/2 ],'yticklabel',{num2str(-(maxY/2)) '0' num2str((maxY/2)) ''},'YColor',th.axesFg,'fontweight','bold') + set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',th.axesFg,'GridColor',th.gridColor) sFactor = lineSmoothFactors(get(guiHandles.lineSmooth, 'Value')); fileIdx = get(guiHandles.FileNum, 'Value'); @@ -198,10 +199,10 @@ if get(guiHandles.checkbox9, 'Value'), hch10=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['piderr_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - h=fill([t2,xmax,xmax,t2],[-maxY,-maxY,maxY,maxY],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); + h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); + h=fill([t2,xmax,xmax,t2],[-maxY,-maxY,maxY,maxY],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); try zoomOn2 = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn2 = 0; end if zoomOn2 @@ -219,9 +220,9 @@ end - set(y,'Units','normalized', 'position', [-.035 .5 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [-.035 .5 1],'color',th.textPrimary); y=xlabel('Time (s)','fontweight','bold'); - set(y,'color',[.2 .2 .2]); + set(y,'color',th.textPrimary); set(gca,'fontsize',fontsz,'XMinorGrid','on') grid on @@ -240,17 +241,18 @@ if get(guiHandles.checkbox14, 'Value'), hch19=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.setpoint_3_/10, sFactor, 'loess'));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end axis([0 xmax 0 100]) - - h=fill([0,t1,t1,0],[0, 0, 100, 100],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - h=fill([t2,xmax,xmax,t2],[0, 0, 100, 100],[.8 .8 .8]); - set(h,'FaceAlpha',0.8,'EdgeColor',[.8 .8 .8]); - + set(gca,'Color',th.axesBg); + h=fill([0,t1,t1,0],[0, 0, 100, 100],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); + h=fill([t2,xmax,xmax,t2],[0, 0, 100, 100],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); + y=xlabel('Time (s)','fontweight','bold'); - set(y,'color',[.2 .2 .2]); + set(y,'color',th.textPrimary); y=ylabel({'Throttle | Motor (%)'},'fontweight','bold'); set(gca,'fontsize',fontsz,'XMinorGrid','on','ylim',[0 100],'ytick',[0 20 40 60 80 100],'fontweight','bold') - set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',[.2 .2 .2]) + set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',th.axesFg,'YColor',th.axesFg,'GridColor',th.gridColor) + set(y,'color',th.textPrimary); grid on diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index 1193329..c8a3b39 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -5,9 +5,10 @@ function PSplotMotorNoise(T, f, tIND, Fs) % tIND - logical time index mask % Fs - sample rate (Hz) +th = PStheme(); screensz = get(0, 'ScreenSize'); fig = figure('Name', 'Motor / Prop Noise Analysis', 'NumberTitle', 'off', ... - 'Color', [.15 .15 .15], ... + 'Color', th.figBg, ... 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); F_kHz = Fs / 1000; @@ -78,7 +79,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) motorLbl{worst}, rmsAll(worst), motorLbl{best}, rmsAll(best), spread); uicontrol(fig, 'Style', 'text', 'String', infoStr, 'Units', 'normalized', ... 'Position', [.06 .965 .88 .03], 'FontSize', 13, 'FontWeight', 'bold', ... - 'ForegroundColor', [.9 .9 .3], 'BackgroundColor', [.2 .2 .2], ... + 'ForegroundColor', th.textAccent, 'BackgroundColor', th.panelBg, ... 'HorizontalAlignment', 'center'); % --- TOP LEFT: All motors overlay PSD --- @@ -89,11 +90,11 @@ function PSplotMotorNoise(T, f, tIND, Fs) end addBandShading(axOvl); hold(axOvl, 'off'); -styleDark(axOvl, fMax); -set(get(axOvl, 'YLabel'), 'String', 'dB', 'Color', [.8 .8 .8]); -th1 = title(axOvl, 'Motor PSD Comparison'); set(th1, 'Color', [.9 .9 .9]); -legend(axOvl, motorLbl(1:nMotors), 'TextColor', [.8 .8 .8], 'Color', [.2 .2 .2], ... - 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'northeast'); +PSstyleAxes(axOvl, th); set(axOvl, 'XLim', [0 fMax]); +set(get(axOvl, 'YLabel'), 'String', 'dB'); +title(axOvl, 'Motor PSD Comparison'); +h_leg = legend(axOvl, motorLbl(1:nMotors), 'Location', 'northeast'); +try PSstyleLegend(h_leg, th); catch, end % --- TOP RIGHT: Gyro PSD --- axGyro = axes('Parent', fig, 'Units', 'normalized', 'Position', [.56 .70 .38 .22]); @@ -104,11 +105,11 @@ function PSplotMotorNoise(T, f, tIND, Fs) end addBandShading(axGyro); hold(axGyro, 'off'); -styleDark(axGyro, fMax); -set(get(axGyro, 'YLabel'), 'String', 'dB', 'Color', [.8 .8 .8]); -th2 = title(axGyro, 'Gyro Noise'); set(th2, 'Color', [.9 .9 .9]); -legend(axGyro, gyroAxLbl, 'TextColor', [.8 .8 .8], 'Color', [.2 .2 .2], ... - 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'northeast'); +PSstyleAxes(axGyro, th); set(axGyro, 'XLim', [0 fMax]); +set(get(axGyro, 'YLabel'), 'String', 'dB'); +title(axGyro, 'Gyro Noise'); +h_leg = legend(axGyro, gyroAxLbl, 'Location', 'northeast'); +try PSstyleLegend(h_leg, th); catch, end % --- MID LEFT: Motor-Gyro coherence --- axCoh = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .39 .42 .25]); @@ -139,14 +140,12 @@ function PSplotMotorNoise(T, f, tIND, Fs) for m = 1:nMotors set(bh(m), 'FaceColor', mCol{m}); end -set(axBar, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 12, 'FontWeight', 'bold', 'XTick', 1:size(bands,1), ... - 'XTickLabel', bandLabelsClean); -set(get(axBar, 'YLabel'), 'String', 'Mean PSD (dB)', 'Color', [.8 .8 .8]); -th4 = title(axBar, 'Noise by Frequency Band'); set(th4, 'Color', [.9 .9 .9]); -grid(axBar, 'on'); set(axBar, 'GridColor', [.3 .3 .3]); -legend(axBar, motorLbl(1:nMotors), 'TextColor', [.8 .8 .8], 'Color', [.2 .2 .2], ... - 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'northeast'); +PSstyleAxes(axBar, th); +set(axBar, 'XTick', 1:size(bands,1), 'XTickLabel', bandLabelsClean); +set(get(axBar, 'YLabel'), 'String', 'Mean PSD (dB)'); +title(axBar, 'Noise by Frequency Band'); +h_leg = legend(axBar, motorLbl(1:nMotors), 'Location', 'northeast'); +try PSstyleLegend(h_leg, th); catch, end % --- BOTTOM: Motor output time domain --- axTime = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .06 .88 .26]); @@ -158,13 +157,13 @@ function PSplotMotorNoise(T, f, tIND, Fs) end plot(axTime, t, throttle, 'Color', [.9 .9 .9], 'LineWidth', 1.5, 'LineStyle', '--'); hold(axTime, 'off'); -styleDark(axTime, max(t)); +PSstyleAxes(axTime, th); set(axTime, 'XLim', [0 max(t)]); set(axTime, 'YLim', [0 100]); -set(get(axTime, 'XLabel'), 'String', 'Time (s)', 'Color', [.8 .8 .8]); -set(get(axTime, 'YLabel'), 'String', 'Motor %', 'Color', [.8 .8 .8]); -th5 = title(axTime, 'Motor Output'); set(th5, 'Color', [.9 .9 .9]); -legend(axTime, [motorLbl(1:nMotors), {'Throttle avg'}], 'TextColor', [.8 .8 .8], ... - 'Color', [.2 .2 .2], 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'northeast'); +set(get(axTime, 'XLabel'), 'String', 'Time (s)'); +set(get(axTime, 'YLabel'), 'String', 'Motor %'); +title(axTime, 'Motor Output'); +h_leg = legend(axTime, [motorLbl(1:nMotors), {'Throttle avg'}], 'Location', 'northeast'); +try PSstyleLegend(h_leg, th); catch, end PSdatatipSetup(fig); @@ -183,6 +182,7 @@ function cohAxisCb(src, ~, fig) function plotCoherence(ax, motorData, gdat, nMotors, mCol, motorLbl, Fs, fMax, axLabel) cla(ax); + thm = PStheme(); gdat_ac = gdat - mean(gdat); for m = 1:nMotors mdat = motorData{m} - mean(motorData{m}); @@ -191,20 +191,13 @@ function plotCoherence(ax, motorData, gdat, nMotors, mCol, motorLbl, Fs, fMax, a hold(ax, 'on'); end hold(ax, 'off'); - styleDark(ax, fMax); + PSstyleAxes(ax, thm); set(ax, 'XLim', [0 fMax]); set(ax, 'YLim', [0 1.05]); - set(get(ax, 'XLabel'), 'String', 'Hz', 'Color', [.8 .8 .8]); - set(get(ax, 'YLabel'), 'String', 'Coherence', 'Color', [.8 .8 .8]); - th = title(ax, ['Motor-Gyro Coherence (' axLabel ')']); set(th, 'Color', [.9 .9 .9]); - legend(ax, motorLbl(1:nMotors), 'TextColor', [.8 .8 .8], 'Color', [.2 .2 .2], ... - 'EdgeColor', [.4 .4 .4], 'FontSize', 11, 'Location', 'northeast'); -end - - -function styleDark(ax, xMax) - set(ax, 'Color', [.1 .1 .1], 'XColor', [.8 .8 .8], 'YColor', [.8 .8 .8], ... - 'FontSize', 14, 'FontWeight', 'bold', 'XLim', [0 xMax]); - grid(ax, 'on'); set(ax, 'GridColor', [.3 .3 .3]); + set(get(ax, 'XLabel'), 'String', 'Hz'); + set(get(ax, 'YLabel'), 'String', 'Coherence'); + title(ax, ['Motor-Gyro Coherence (' axLabel ')']); + h_leg = legend(ax, motorLbl(1:nMotors), 'Location', 'northeast'); + try PSstyleLegend(h_leg, thm); catch, end end diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index 00f02f6..a187f32 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -10,6 +10,7 @@ try set(PSerrfig, 'pointer', 'watch') +th = PStheme(); if ~isempty(filenameA) || ~isempty(filenameB) %% update fonts @@ -137,7 +138,7 @@ minyA=min(Perr_a_m(p,:))-.5;if minyA<0, minyA=0;end maxyA=max(Perr_a_m(p,:))+.5; h=errorbar([posA],[Perr_a_m(p,:) ], [Perr_a_se(p,:) ] );hold on - set(h, 'color','k', 'LineStyle','none'); + set(h, 'color',th.axesFg, 'LineStyle','none'); h=bar([posA], (Perr_a_m(p,:) )); set(h, 'facecolor',[colorA],'facealpha',.8,'BarWidth',.4) set(h1,'tickdir','out','xminortick','off','yminortick','on'); @@ -158,7 +159,7 @@ minyB=min(Perr_b_m(p,:))-.5;if minyB<0, minyB=0;end maxyB=max(Perr_b_m(p,:))+.5; h=errorbar([posB],[ Perr_b_m(p,:)], [ Perr_b_se(p,:)] ); - set(h, 'color','k', 'LineStyle','none'); + set(h, 'color',th.axesFg, 'LineStyle','none'); h=bar([posB], (Perr_b_m(p,:))); set(h, 'facecolor',[colorB],'facealpha',.8,'BarWidth',.4) set(h1,'tickdir','out','xminortick','off','yminortick','on'); @@ -184,6 +185,8 @@ % end +allax = findobj(PSerrfig, 'Type', 'axes'); +for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end set(PSerrfig, 'pointer', 'arrow') diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 5b8640c..166280e 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -9,7 +9,8 @@ % ---------------------------------------------------------------------------------- if exist('fnameMaster','var') && ~isempty(fnameMaster) - + +th = PStheme(); PSspecfig_pos = get(PSspecfig, 'Position'); screensz_tmp = get(0,'ScreenSize'); if PSspecfig_pos(3) > 10, PSspecfig_pos(3:4) = PSspecfig_pos(3:4) ./ screensz_tmp(3:4); end prop_max_screen=(max([PSspecfig_pos(3) PSspecfig_pos(4)])); @@ -177,13 +178,14 @@ grid on ax = gca; + PSstyleAxes(ax, th); set(ax, 'GridColor', [1 1 1]); if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 - set(ax, 'GridColor', [0 0 0]); % black on white background - set(h,'Color',[0 0 0],'fontsize',fontsz,'fontweight','bold') + set(ax, 'GridColor', [0 0 0]); + set(h,'Color',[0 0 0],'fontsize',fontsz,'fontweight','bold') end - ylabel('Frequency (Hz)','fontweight','bold') - xlabel('% Throttle','fontweight','bold') + ylabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary) + xlabel('% Throttle','fontweight','bold','Color',th.textPrimary) %% Dynamic notch overlay for FFT_FREQ mode if exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') @@ -332,27 +334,28 @@ if get(guiHandlesSpec.Sub100HzCheck{c1(p)}, 'Value')==1 set(h2,'xtick',[0 20 40 60 80 100], 'yminortick','on') axis([0 100 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) - h=plot([round(Flim1) round(Flim1)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); + h=plot([round(Flim1) round(Flim1)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'--','Color',th.axesFg); set(h,'linewidth',1) - h=plot([round(Flim2) round(Flim2)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'k--'); + h=plot([round(Flim2) round(Flim2)],[baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'--','Color',th.axesFg); set(h,'linewidth',1) else set(h2,'xtick',[0 : ((A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 / 5) : (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000],'yminortick','on') axis([0 (A_lograte(get(guiHandlesSpec.FileSelect{k}, 'Value')) / 2) * 1000 baselineYlines(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))]) end - xlabel('Frequency (Hz)') + xlabel('Frequency (Hz)','Color',th.textPrimary) if get(guiHandlesSpec.checkboxPSD, 'Value') - ylabel(['PSD (dB)']) + ylabel(['PSD (dB)'],'Color',th.textPrimary) else - ylabel(['Amplitude']) + ylabel(['Amplitude'],'Color',th.textPrimary) end h=text(2,climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))*.95,axLabel{c2(p)}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold') + set(h,'Color',th.textPrimary,'fontsize',fontsz,'fontweight','bold') grid on + PSstyleAxes(gca, th); end end end diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 3cc51f0..8b048bc 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -9,7 +9,8 @@ % ---------------------------------------------------------------------------------- if exist('fnameMaster','var') && ~isempty(fnameMaster) -%% update fonts +th = PStheme(); +%% update fonts PSspecfig2_pos = get(PSspecfig2, 'Position'); screensz_tmp = get(0,'ScreenSize'); if PSspecfig2_pos(3) > 10, PSspecfig2_pos(3:4) = PSspecfig2_pos(3:4) ./ screensz_tmp(3:4); end prop_max_screen=(max([PSspecfig2_pos(3) PSspecfig2_pos(4)])); @@ -218,43 +219,43 @@ m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0:m/10:m], 'yminortick','on') axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); + xlabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary); if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + ylabel(['Power Spectral Density (dB)'],'fontweight','bold','Color',th.textPrimary); else - ylabel(['Amplitude'],'fontweight','bold'); + ylabel(['Amplitude'],'fontweight','bold','Color',th.textPrimary); end if a == 1 - title('Full Spectrum','fontweight','bold'); + title('Full Spectrum','fontweight','bold','Color',th.textPrimary); end if p < 4 h=text(2,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); + set(h,'Color',th.textPrimary,'fontsize',fontsz,'fontweight','bold'); end grid on - h2=subplot('position',posInfo.Spec2Pos(a+3,:)); + h2=subplot('position',posInfo.Spec2Pos(a+3,:)); ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) + set(h,'Color',[multiLineCols(f,:)]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0 20 40 60 80 100],'yminortick','on') - axis([0 100 climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); + axis([0 100 climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + xlabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary); if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + ylabel(['Power Spectral Density (dB)'],'fontweight','bold','Color',th.textPrimary); else - ylabel(['Amplitude'],'fontweight','bold'); + ylabel(['Amplitude'],'fontweight','bold','Color',th.textPrimary); end if a == 1 - title('Sub 100Hz','fontweight','bold'); + title('Sub 100Hz','fontweight','bold','Color',th.textPrimary); end if p < 4 h=text(1,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); - set(h,'Color',[.2 .2 .2],'fontsize',fontsz,'fontweight','bold'); + set(h,'Color',th.textPrimary,'fontsize',fontsz,'fontweight','bold'); end %%%%%%%%%%%%%%%%%%% Plot Latencies %%%%%%%%%%%%%%% @@ -316,18 +317,18 @@ m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0:m/10:m], 'yminortick','on') axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) - xlabel('Frequency (Hz)','fontweight','bold'); + xlabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary); if get(guiHandlesSpec2.checkboxPSD, 'Value') - ylabel(['Power Spectral Density (dB)'],'fontweight','bold'); + ylabel(['Power Spectral Density (dB)'],'fontweight','bold','Color',th.textPrimary); else - ylabel(['Amplitude'],'fontweight','bold'); + ylabel(['Amplitude'],'fontweight','bold','Color',th.textPrimary); end if a == 1 - title('Full Spectrum','fontweight','bold'); + title('Full Spectrum','fontweight','bold','Color',th.textPrimary); end grid on - + end grid on @@ -368,9 +369,13 @@ else h=legend(legnd, 'Location','NorthEast') end + try PSstyleLegend(h, th); catch, end end +% Apply dark theme to all axes +allax = findobj(PSspecfig2, 'Type', 'axes'); +for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end % Set up click-to-show-value datatips + double-click expand on all axes PSdatatipSetup(PSspecfig2); diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index 4308194..bb2b186 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -8,8 +8,8 @@ % ---------------------------------------------------------------------------------- if ~isempty(filenameA) || ~isempty(filenameB) + th = PStheme(); set(PSstatsfig, 'pointer', 'watch') - pause(.05) %% update fonts PSstatsfig_pos = get(PSstatsfig, 'Position'); @@ -63,7 +63,7 @@ cla h=histogram(Rpercent_A,'Normalization','probability','BinWidth',1); y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold') set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) @@ -71,14 +71,14 @@ [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_A)-1],RateCurveRoll_A(1,2:end)); set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_A(20) RateCurveRoll_A(40) RateCurveRoll_A(60) RateCurveRoll_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(1,:)]); axis([1 100 0 .1]) @@ -98,19 +98,19 @@ h=histogram(Ppercent_A,'Normalization','probability','BinWidth',1); set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold') hold on [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_A)-1],RateCurvePitch_A(1,2:end)); set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_A(20) RateCurvePitch_A(40) RateCurvePitch_A(60) RateCurvePitch_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(2,:)]); axis([1 100 0 .1]) @@ -130,20 +130,20 @@ h=histogram(Ypercent_A,'Normalization','probability','BinWidth',1); set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold') hold on [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_A)-1],RateCurveYaw_A(1,2:end)); set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_A(20) RateCurveYaw_A(40) RateCurveYaw_A(60) RateCurveYaw_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(3,:)]); axis([1 100 0 .1]) @@ -164,7 +164,7 @@ set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7); grid on y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight', 'color',[colorA],'fontweight','bold') set(hhist,'ycolor',[colorA],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(4,:)]); axis([1 100 0 .1]) @@ -202,7 +202,7 @@ cla h=histogram(Rpercent_B,'Normalization','probability','BinWidth',1); y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold') set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) @@ -210,14 +210,14 @@ [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_B)-1],RateCurveRoll_B(1,2:end)); set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_B(20) RateCurveRoll_B(40) RateCurveRoll_B(60) RateCurveRoll_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(5,:)]); axis([1 100 0 .1]) @@ -238,20 +238,20 @@ h=histogram(Ppercent_B,'Normalization','probability','BinWidth',1); set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold'); hold on [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_B)-1],RateCurvePitch_B(1,2:end)); set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_B(20) RateCurvePitch_B(40) RateCurvePitch_B(60) RateCurvePitch_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(6,:)]); axis([1 100 0 .1]) @@ -272,20 +272,20 @@ h=histogram(Ypercent_B,'Normalization','probability','BinWidth',1); set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight','fontweight','bold') hold on [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_B)-1],RateCurveYaw_B(1,2:end)); set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor','k','fontsize',fontsz5) + set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) set(get(ax(2), 'YLabel'), 'String', 'deg/s'); if get(guiHandlesStats.degsecStick, 'Value')==1, set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); end set(h2,'color',[.5 .5 .5],'LineWidth',1.5) hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_B(20) RateCurveYaw_B(40) RateCurveYaw_B(60) RateCurveYaw_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor','k') + set(h,'markerfacecolor',th.axesFg) set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(7,:)]); axis([1 100 0 .1]) @@ -306,7 +306,7 @@ set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) grid on y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',[.2 .2 .2]); + set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); ylabel('% of flight', 'color',[colorB],'fontweight','bold') set(hhist,'ycolor',[colorB],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(8,:)]); axis([1 100 0 .1]) @@ -340,15 +340,15 @@ % gyro h1=subplot('position',posInfo.statsPos2(1,:)); cla s1=errorbar([1],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.GyroFilt(1,:))));hold on set(s1,'FaceColor',[colorA]);%[ColorSet(11,:)]) s1=errorbar([2],mean(abs(DATtmpA.GyroFilt(2,:))), std(abs(DATtmpA.GyroFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.GyroFilt(2,:)))); set(s2,'FaceColor',[colorA]);%,[ColorSet(12,:)]) s1=errorbar([3],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpA.GyroFilt(1,:)))); set(s3,'FaceColor',[colorA]);%,[ColorSet(13,:)]) set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -362,19 +362,19 @@ % RCRate h1=subplot('position',posInfo.statsPos2(5,:)); cla s1=errorbar([1],mean(abs(Rpercent_A)), std(abs(Rpercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(Rpercent_A)));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(Ppercent_A)), std(abs(Ppercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(Ppercent_A))); set(s2,'FaceColor',[colorA]);% s1=errorbar([3],mean(abs(Ypercent_A)), std(abs(Ypercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(Ypercent_A))); set(s3,'FaceColor',[colorA]);% s1=errorbar([4],mean(abs(Tpercent_A)), std(abs(Tpercent_A)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(Tpercent_A)); set(s4,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -387,15 +387,15 @@ % pterm h1=subplot('position',posInfo.statsPos2(2,:)); cla s1=errorbar([1],mean(abs(DATtmpA.Pterm(1,:))), std(abs(DATtmpA.Pterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.Pterm(1,:))));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(DATtmpA.Pterm(2,:))), std(abs(DATtmpA.Pterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.Pterm(2,:)))); set(s2,'FaceColor',[colorA]);% s1=errorbar([3],mean(abs(DATtmpA.Pterm(3,:))), std(abs(DATtmpA.Pterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpA.Pterm(3,:)))); set(s3,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -409,15 +409,15 @@ % fterm h1=subplot('position',posInfo.statsPos2(6,:)); cla s1=errorbar([1],mean(abs(DATtmpA.Fterm(1,:))), std(abs(DATtmpA.Fterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.Fterm(1,:))));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(DATtmpA.Fterm(2,:))), std(abs(DATtmpA.Fterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.Fterm(2,:)))); set(s2,'FaceColor',[colorA]);% % s1=errorbar([3],mean(abs(DATtmpA.Fterm(3,:))), std(abs(DATtmpA.Fterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpA.Fterm(3,:)))); set(s3,'FaceColor',[colorA]); set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -432,15 +432,15 @@ % Iterm h1=subplot('position',posInfo.statsPos2(3,:)); cla s1=errorbar([1],mean(abs(DATtmpA.Iterm(1,:))), std(abs(DATtmpA.Iterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.Iterm(1,:))));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(DATtmpA.Iterm(2,:))), std(abs(DATtmpA.Iterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.Iterm(2,:)))); set(s2,'FaceColor',[colorA]);% s1=errorbar([3],mean(abs(DATtmpA.Iterm(3,:))), std(abs(DATtmpA.Iterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpA.Iterm(3,:)))); set(s3,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -454,11 +454,11 @@ % dterm h1=subplot('position',posInfo.statsPos2(7,:)); cla s1=errorbar([1],mean(abs(DATtmpA.DtermFilt(1,:))), std(abs(DATtmpA.DtermFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.DtermFilt(1,:))));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(DATtmpA.DtermFilt(2,:))), std(abs(DATtmpA.DtermFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.DtermFilt(2,:)))); set(s2,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -471,19 +471,19 @@ h1=subplot('position',posInfo.statsPos2(4,:)); cla s1=errorbar([1],mean(DATtmpA.Motor12(1,:)), std(DATtmpA.Motor12(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(DATtmpA.Motor12(1,:)));hold on set(s1,'FaceColor',[colorA]); s1=errorbar([2],mean(DATtmpA.Motor12(2,:)), std(DATtmpA.Motor12(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(DATtmpA.Motor12(2,:))); set(s2,'FaceColor',[colorA]);% s1=errorbar([3],mean(DATtmpA.Motor34(1,:)), std(DATtmpA.Motor34(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(DATtmpA.Motor34(1,:))); set(s3,'FaceColor',[colorA]);% s1=errorbar([4],mean(DATtmpA.Motor34(2,:)), std(DATtmpA.Motor34(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(DATtmpA.Motor34(2,:))); set(s4,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -496,19 +496,19 @@ h1=subplot('position',posInfo.statsPos2(8,:)); cla s1=errorbar([1],mean(abs(DATtmpA.debug12(1,:))), std(abs(DATtmpA.debug12(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpA.debug12(1,:))));hold on set(s1,'FaceColor',[colorA]);% s1=errorbar([2],mean(abs(DATtmpA.debug12(2,:))), std(abs(DATtmpA.debug12(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpA.debug12(2,:)))); set(s2,'FaceColor',[colorA]);% s1=errorbar([3],mean(abs(DATtmpA.debug34(1,:))), std(abs(DATtmpA.debug34(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpA.debug34(1,:)))); set(s3,'FaceColor',[colorA]);% s1=errorbar([4],mean(abs(DATtmpA.debug34(2,:))), std(abs(DATtmpA.debug34(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(abs(DATtmpA.debug34(2,:)))); set(s4,'FaceColor',[colorA]);% set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') @@ -533,15 +533,15 @@ % gyro h1=subplot('position',posInfo.statsPos2(9,:)); cla s1=errorbar([1],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.GyroFilt(1,:))));hold on set(s1,'FaceColor',[colorB]);%[ColorSet(11,:)]) s1=errorbar([2],mean(abs(DATtmpB.GyroFilt(2,:))), std(abs(DATtmpB.GyroFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.GyroFilt(2,:)))); set(s2,'FaceColor',[colorB]);%,[ColorSet(12,:)]) s1=errorbar([3],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpB.GyroFilt(1,:)))); set(s3,'FaceColor',[colorB]);%,[ColorSet(13,:)]) set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -555,19 +555,19 @@ % RCRate h1=subplot('position',posInfo.statsPos2(13,:)); cla s1=errorbar([1],mean(abs(Rpercent_B)), std(abs(Rpercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(Rpercent_B)));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(Ppercent_B)), std(abs(Ppercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(Ppercent_B))); set(s2,'FaceColor',[colorB]);% s1=errorbar([3],mean(abs(Ypercent_B)), std(abs(Ypercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(Ypercent_B))); set(s3,'FaceColor',[colorB]);% s1=errorbar([4],mean(abs(Tpercent_B)), std(abs(Tpercent_B)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(Tpercent_B)); set(s4,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -580,15 +580,15 @@ % pterm h1=subplot('position',posInfo.statsPos2(10,:)); cla s1=errorbar([1],mean(abs(DATtmpB.Pterm(1,:))), std(abs(DATtmpB.Pterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.Pterm(1,:))));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(DATtmpB.Pterm(2,:))), std(abs(DATtmpB.Pterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.Pterm(2,:)))); set(s2,'FaceColor',[colorB]);% s1=errorbar([3],mean(abs(DATtmpB.Pterm(3,:))), std(abs(DATtmpB.Pterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpB.Pterm(3,:)))); set(s3,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -602,15 +602,15 @@ % fterm h1=subplot('position',posInfo.statsPos2(14,:)); cla s1=errorbar([1],mean(abs(DATtmpB.Fterm(1,:))), std(abs(DATtmpB.Fterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.Fterm(1,:))));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(DATtmpB.Fterm(2,:))), std(abs(DATtmpB.Fterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.Fterm(2,:)))); set(s2,'FaceColor',[colorB]);% % s1=errorbar([3],mean(abs(DATtmpB.Fterm(3,:))), std(abs(DATtmpB.Fterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpB.Fterm(3,:)))); set(s3,'FaceColor',[colorB]); set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -625,15 +625,15 @@ % Iterm h1=subplot('position',posInfo.statsPos2(11,:)); cla s1=errorbar([1],mean(abs(DATtmpB.Iterm(1,:))), std(abs(DATtmpB.Iterm(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.Iterm(1,:))));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(DATtmpB.Iterm(2,:))), std(abs(DATtmpB.Iterm(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.Iterm(2,:)))); set(s2,'FaceColor',[colorB]);% s1=errorbar([3],mean(abs(DATtmpB.Iterm(3,:))), std(abs(DATtmpB.Iterm(3,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpB.Iterm(3,:)))); set(s3,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -647,11 +647,11 @@ % dterm h1=subplot('position',posInfo.statsPos2(15,:)); cla s1=errorbar([1],mean(abs(DATtmpB.DtermFilt(1,:))), std(abs(DATtmpB.DtermFilt(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.DtermFilt(1,:))));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(DATtmpB.DtermFilt(2,:))), std(abs(DATtmpB.DtermFilt(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.DtermFilt(2,:)))); set(s2,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -664,19 +664,19 @@ h1=subplot('position',posInfo.statsPos2(12,:)); cla s1=errorbar([1],mean(DATtmpB.Motor12(1,:)), std(DATtmpB.Motor12(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(DATtmpB.Motor12(1,:)));hold on set(s1,'FaceColor',[colorB]); s1=errorbar([2],mean(DATtmpB.Motor12(2,:)), std(DATtmpB.Motor12(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(DATtmpB.Motor12(2,:))); set(s2,'FaceColor',[colorB]);% s1=errorbar([3],mean(DATtmpB.Motor34(1,:)), std(DATtmpB.Motor34(1,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(DATtmpB.Motor34(1,:))); set(s3,'FaceColor',[colorB]);% s1=errorbar([4],mean(DATtmpB.Motor34(2,:)), std(DATtmpB.Motor34(2,:)));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(DATtmpB.Motor34(2,:))); set(s4,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -689,19 +689,19 @@ h1=subplot('position',posInfo.statsPos2(16,:)); cla s1=errorbar([1],mean(abs(DATtmpB.debug12(1,:))), std(abs(DATtmpB.debug12(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s1=bar([1],mean(abs(DATtmpB.debug12(1,:))));hold on set(s1,'FaceColor',[colorB]);% s1=errorbar([2],mean(abs(DATtmpB.debug12(2,:))), std(abs(DATtmpB.debug12(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s2=bar([2],mean(abs(DATtmpB.debug12(2,:)))); set(s2,'FaceColor',[colorB]);% s1=errorbar([3],mean(abs(DATtmpB.debug34(1,:))), std(abs(DATtmpB.debug34(1,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s3=bar([3],mean(abs(DATtmpB.debug34(1,:)))); set(s3,'FaceColor',[colorB]);% s1=errorbar([4],mean(abs(DATtmpB.debug34(2,:))), std(abs(DATtmpB.debug34(2,:))));hold on - set(s1,'color','k','linewidth',lineThickness) + set(s1,'color',th.axesFg,'linewidth',lineThickness) s4=bar([4],mean(abs(DATtmpB.debug34(2,:)))); set(s4,'FaceColor',[colorB]);% set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') @@ -1009,6 +1009,8 @@ updateStats=0; +allax = findobj(PSstatsfig, 'Type', 'axes'); +for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end set(PSstatsfig, 'pointer', 'arrow') end diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 5b91b9d..e957438 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -8,6 +8,7 @@ % ---------------------------------------------------------------------------------- PStunefig=figure(4); +th = PStheme(); PStunefig_pos = get(PStunefig, 'Position'); screensz_tmp = get(0,'ScreenSize'); if PStunefig_pos(3) > 10, PStunefig_pos(3:4) = PStunefig_pos(3:4) ./ screensz_tmp(3:4); end @@ -77,7 +78,7 @@ pidvar = [ylab2{p} 'PIDF']; PID = eval([pidvar '{f}']); - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end + if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') set(h,'fontsize',fontsz) @@ -85,34 +86,34 @@ peakresp(p, fcntSR) = nan; peaktime(p, fcntSR) = nan; latencyHalfHeight(p, fcntSR) = nan; - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold'); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') insufficient data']); + if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end + h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') insufficient data']); set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') end - set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); + set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); box off if cnt <= 3, h=ylabel([ylab{p} ' Response '], 'fontweight','bold'); end xlabel('Time (ms)', 'fontweight','bold'); - + if p==1, title('Step Response Functions');end - h=plot([0 500],[1 1],'k--'); + h=plot([0 500],[1 1],'--','Color',th.axesFg); set(h,'linewidth',.5) axis([0 500 0 ymax]) grid on - - - h2=subplot('position',posInfo.TparamsPos(p+6,:)); - h=plot(fcntSR, peakresp(p, fcntSR),'sk'); + + + h2=subplot('position',posInfo.TparamsPos(p+6,:)); + h=plot(fcntSR, peakresp(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) ylabel([ylab{p} ' Peak '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); + xlabel('Test', 'fontweight','bold'); hold on grid on - plot([0 10],[1 1],'--k') + plot([0 10],[1 1],'--','Color',th.axesFg) h3=subplot('position',posInfo.TparamsPos(p+9,:)); h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); @@ -159,27 +160,27 @@ h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); end - set(h,'fontsize',fontsz,'fontweight','bold'); + set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); h=text(505, ypos(p), [pidlabels]); - set(h,'fontsize',fontsz,'fontweight','bold'); + set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); % | Peak = ' num2str(peakresp(fcntSR)) ', Peak Time = ' num2str(peaktime) 'ms, Latency = ' num2str(latencyHalfHeight(fcntSR)) 'ms']);set(h,'fontsize',fontsz); + h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') set(h,'fontsize',fontsz) else peakresp(p, fcntSR) = nan; peaktime(p, fcntSR) = nan; latencyHalfHeight(p, fcntSR) = nan; - if cnt <= 3 + if cnt <= 3 if size(axesOptions,2) < 2 h=text(505, ypos(p)+0.04, [ylab{p}]); else h=text(505, ypos(p)+0.04, [ylab{p} ' (' lnLabels{cnt2} ')']); end - set(h,'fontsize',fontsz,'fontweight','bold'); + set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); h=text(505, ypos(p), [pidlabels]); - set(h,'fontsize',fontsz,'fontweight','bold'); + set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') insufficient data']); set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') @@ -193,7 +194,7 @@ xlabel('Time (ms)', 'fontweight','bold'); title('Step Response Functions'); - h=plot([0 500],[1 1],'k--'); + h=plot([0 500],[1 1],'--','Color',th.axesFg); set(h,'linewidth',.5) axis([0 500 0 ymax]) grid on @@ -204,6 +205,8 @@ warndlg('10 files maximum. Click reset.'); end end + allax = findobj(PStunefig, 'Type', 'axes'); + for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end set(PStunefig, 'pointer', 'arrow') updateStep=0; diff --git a/src/ui/PSdispSetupInfoUIcontrol.m b/src/ui/PSdispSetupInfoUIcontrol.m index 4ec7cc7..c477dbe 100644 --- a/src/ui/PSdispSetupInfoUIcontrol.m +++ b/src/ui/PSdispSetupInfoUIcontrol.m @@ -39,6 +39,7 @@ else warndlg('Please select file(s)'); end +PSstyleControls(PSdisp); % functions function selection(src,event) diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index b00285c..bb5ddb8 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -46,7 +46,8 @@ posInfo.maxStick=[.24 .94 .06 .03]; errCrtlpanel = uipanel('Title','','FontSize',fontsz3,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[.085 .93 .23 .06]); guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh2],... @@ -65,3 +66,4 @@ errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); pause(2); end +PSstyleControls(PSerrfig); diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 939a5fa..ea3c59a 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -76,7 +76,8 @@ end Spec3Crtlpanel = uipanel('Title','select file ','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[cpL .515 cpW .405]); guiHandlesSpec3.computeSpec = uicontrol(PSspecfig3,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec3],... @@ -121,7 +122,7 @@ guiHandlesSpec3.climMax2_input = uicontrol(PSspecfig3,'style','edit','string',[num2str(ClimScale3(2))],'fontsize',fontsz,'TooltipString',['adjusts the color limits'],'units','normalized','Position',[posInfo.clim3Max2_input],... 'callback','@textinput_call2; ClimScale3(2)=str2num(get(guiHandlesSpec3.climMax2_input, ''String''));updateSpec=1;PSfreqTime;'); - guiHandlesSpec3.sub100HzfreqTime = uicontrol(PSspecfig3,'Style','checkbox','String','sub 100Hz','fontsize',fontsz,'ForegroundColor',[.2 .2 .2],'BackgroundColor',bgcolor,... + guiHandlesSpec3.sub100HzfreqTime = uicontrol(PSspecfig3,'Style','checkbox','String','sub 100Hz','fontsize',fontsz,'ForegroundColor',panelFg,'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.sub100HzfreqTime],'callback','@selection2;updateSpec=1; PSfreqTime;'); guiHandlesSpec3.playerBtn = uicontrol(PSspecfig3,'string','Player','fontsize',fontsz,... @@ -142,7 +143,7 @@ try set(guiHandlesSpec3.ColormapSelect, 'Value', defaults.Values(find(strcmp(def else warndlg('Please select file(s)'); end - +PSstyleControls(PSspecfig3); % functions function selection2(src,event) diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index a578f5d..08e3028 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -122,7 +122,8 @@ spec2CrtlpanelPos = [cpL .16+vPosSpec2d cpW .76]; end spec2Crtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',spec2CrtlpanelPos); guiHandlesSpec2.computeSpec = uicontrol(PSspecfig2,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec],... @@ -247,7 +248,7 @@ try set(guiHandlesSpec2.RPYcomboSpec, 'Value', defaults.Values(find(strcmp(defau else warndlg('Please select file(s)'); end - +PSstyleControls(PSspecfig2); % functions function selection2(src,event) diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index b5c509b..d395eca 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -156,7 +156,8 @@ specCrtlpanelPos = [cpL .55 cpW .37]; end specCrtlpanel = uipanel('Title','Params','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',specCrtlpanelPos); %%% PRESET CONFIGURATIONS @@ -267,8 +268,8 @@ else warndlg('Please select file(s)'); end +PSstyleControls(PSspecfig); - % functions function selection2(src,event) val = c.Value; diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index 40614c4..7ba7630 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -76,7 +76,8 @@ posInfo.crossAxesStats_input2 = [.42 .945 .03 .03]; statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[.06 .935 .40 .06]); guiHandlesStats.saveFig5 = uicontrol(PSstatsfig,'string','Save Fig','fontsize',fontsz5,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig5],... @@ -105,7 +106,7 @@ errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); pause(2); end - +PSstyleControls(PSstatsfig); function textinput_call4(src,eventdata) str=get(src,'String'); diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 085c57e..95ee36c 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -67,7 +67,8 @@ posInfo.maxYStepTxt= [cpL+cpW/3+.005 .421 cpW/2 .025]; guiHandlesTune.tuneCrtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[cpL .41 cpW .51]); guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... @@ -126,6 +127,7 @@ try set(guiHandlesTune.maxYStepInput, 'String', num2str(defaults.Values(find(str else warndlg('Please select file(s)'); end +PSstyleControls(PStunefig); % functions function textinput_call3(src,eventdata) diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index c9470d1..c398573 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -45,7 +45,8 @@ checkpanel = uipanel('Title','','FontSize',fontsz,... - 'BackgroundColor',[.95 .95 .95],... + 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... + 'HighlightColor',panelBorder,... 'Position',[.096 .932 .68 .065]); guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... @@ -126,5 +127,6 @@ try set(guiHandles.maxY_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ymax'))))), catch, end try set(guiHandles.nCols_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ncolors'))))), catch, end +PSstyleControls(PSfig); diff --git a/src/util/PSstyleAxes.m b/src/util/PSstyleAxes.m index fad01ea..4e31fbc 100644 --- a/src/util/PSstyleAxes.m +++ b/src/util/PSstyleAxes.m @@ -1,7 +1,13 @@ function PSstyleAxes(ax, th) %% PSstyleAxes - apply dark theme to axes if nargin < 2, th = PStheme(); end +if ~ishandle(ax), return; end +% skip colorbars — findobj('Type','axes') returns them in Octave +try tag = get(ax, 'Tag'); if strcmpi(tag, 'colorbar') || strcmpi(tag, 'Colorbar'), return; end; catch, end set(ax, 'Color', th.axesBg, 'XColor', th.axesFg, 'YColor', th.axesFg, ... 'GridColor', th.gridColor, 'FontWeight', 'bold'); -grid(ax, 'on'); +try set(get(ax, 'Title'), 'Color', th.textPrimary); catch, end +try set(get(ax, 'XLabel'), 'Color', th.textPrimary); catch, end +try set(get(ax, 'YLabel'), 'Color', th.textPrimary); catch, end +try grid(ax, 'on'); catch, end end diff --git a/src/util/PSstyleControls.m b/src/util/PSstyleControls.m new file mode 100644 index 0000000..dde069a --- /dev/null +++ b/src/util/PSstyleControls.m @@ -0,0 +1,20 @@ +function PSstyleControls(fig, th) +%% PSstyleControls - apply dark theme to all uicontrols on figure +if nargin < 2, th = PStheme(); end +controls = findobj(fig, 'Type', 'uicontrol'); +for i = 1:numel(controls) + style = get(controls(i), 'Style'); + if strcmp(style, 'pushbutton'), continue; end + fg = get(controls(i), 'ForegroundColor'); + % fix controls still at default black [0 0 0] + if all(abs(fg) < 0.01) + set(controls(i), 'ForegroundColor', th.textPrimary); + end + if strcmp(style, 'edit') + set(controls(i), 'BackgroundColor', th.inputBg, 'ForegroundColor', th.inputFg); + end + if strcmp(style, 'popupmenu') || strcmp(style, 'listbox') + try set(controls(i), 'BackgroundColor', th.inputBg, 'ForegroundColor', th.inputFg); catch, end + end +end +end From 632f2779eafe18a979c9aeb343f50e490c25e056 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 19:45:53 +0100 Subject: [PATCH 07/80] fix(ui): unify font size via PStheme, remove all hardcoded FontSize values --- PIDscope.m | 19 +- src/compat/PSdatatip_click.m | 4 +- src/compat/cell2table.m | 4 +- src/compat/readtable.m | 2 +- src/compat/smooth.m | 4 +- src/compat/writetable.m | 2 +- src/core/PSarduConvert.m | 8 +- src/core/PSestimateRPM.m | 2 +- src/core/PSload.m | 4 +- src/core/PSquicJson2csv.m | 2 +- src/plot/PSdynSpecPlayer.m | 22 +- src/plot/PSfilterSim.m | 73 +- src/plot/PSfreqTime.m | 13 +- src/plot/PSplotBode.m | 10 +- src/plot/PSplotLogViewer.m | 12 - src/plot/PSplotMotorNoise.m | 7 +- src/plot/PSplotPIDerror.m | 312 ++++--- src/plot/PSplotSpec.m | 43 +- src/plot/PSplotSpec2D.m | 14 - src/plot/PSplotStats.m | 1287 ++++------------------------- src/plot/PSrunChirpAnalysis.m | 2 +- src/plot/PStuningParams.m | 9 - src/ui/PSdispSetupInfoUIcontrol.m | 21 +- src/ui/PSerrUIcontrol.m | 50 +- src/ui/PSfreqTimeUIcontrol.m | 20 +- src/ui/PSspec2DUIcontrol.m | 21 +- src/ui/PSspecUIcontrol.m | 20 +- src/ui/PSstatsUIcontrol.m | 59 +- src/ui/PStuneUIcontrol.m | 21 +- src/ui/PSviewerUIcontrol.m | 36 +- src/util/PSstyleAxes.m | 2 +- src/util/PStheme.m | 4 + 32 files changed, 585 insertions(+), 1524 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index 9b048e3..ae704a6 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -152,9 +152,9 @@ pause(.1)% need to wait for figure to open before extracting screen values -screensz_multiplier = sqrt(screensz(4)^2) * .011; % based on vertical dimension only, to deal with for ultrawide monitors -prop_max_screen = figPos(4) / screensz(4); -fontsz = (screensz_multiplier*prop_max_screen); +th = PStheme(); +fontsz = th.fontsz; +screensz_multiplier = screensz(4) * .011; % Octave font scaling is done below in layout section markerSz = round(screensz_multiplier * 0.75); vPos = 0.92; @@ -186,6 +186,8 @@ posInfo.linewidth = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; posInfo.spectrogramButton = [cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; posInfo.TuningButton = [cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; +posInfo.PIDErrorButton = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; +posInfo.FlightStatsButton = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; posInfo.period2Hz = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; posInfo.DispInfoButton = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; posInfo.saveFig = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; @@ -251,8 +253,9 @@ set(guiHandles.fileA, 'ForegroundColor', colRun); guiHandles.clr = uicontrol(PSfig,'string','Reset','fontsize',fontsz,'TooltipString', ['clear all data'], 'units','normalized','Position',[posInfo.clr],... - 'callback',['clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF filenameA fnameMaster loaded_firmware debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData ampmat freq2d2 amp2d2 specMat; ' ... + 'callback',['clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF filenameA fnameMaster loaded_firmware debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData rpmFilterData ampmat freq2d2 amp2d2 specMat delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02 gyro_phase_shift_deg dterm_phase_shift_deg tuneCrtlpanel_init setupInfoWidgets_init; ' ... 'fcnt=0; filenameA={}; fnameMaster={}; Nfiles=0; expandON=0; ' ... + 'try, delete(checkpanel); clear checkpanel; catch, end; ' ... 'try, delete(subplot(''position'',posInfo.linepos1)); delete(subplot(''position'',posInfo.linepos2)); delete(subplot(''position'',posInfo.linepos3)); delete(subplot(''position'',posInfo.linepos4)); catch, end; ' ... 'figs=findobj(''Type'',''figure''); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end; ' ... 'set(guiHandles.FileNum, ''String'', '' ''); try, set(guiHandles.Epoch1_A_Input, ''String'', '' ''); set(guiHandles.Epoch2_A_Input, ''String'', '' ''); catch, end;']); @@ -290,6 +293,14 @@ 'callback','PStuneUIcontrol'); set(guiHandles.TuningButton, 'ForegroundColor', colorB); +guiHandles.PIDErrorButton = uicontrol(PSfig,'string','PID Error','fontsize',fontsz,'TooltipString', ['PID error distribution analysis'],'units','normalized','Position',[posInfo.PIDErrorButton],... + 'callback','PSerrUIcontrol; PSplotPIDerror;'); +set(guiHandles.PIDErrorButton, 'ForegroundColor', [.8 .4 .1]); + +guiHandles.FlightStatsButton = uicontrol(PSfig,'string','Flight Stats','fontsize',fontsz,'TooltipString', ['Flight statistics and stick analysis'],'units','normalized','Position',[posInfo.FlightStatsButton],... + 'callback','PSstatsUIcontrol; PSplotStats;'); +set(guiHandles.FlightStatsButton, 'ForegroundColor', [.2 .6 .8]); + guiHandles.period2Hz = uicontrol(PSfig,'string','Period','fontsize',fontsz,'TooltipString', ['Calculates peak to peak in Hz similar to the BBE ''Mark'' tool' , newline, 'press button, position mouse over 1st peak, mouse click,' , newline, 'then position over 2nd peak, then mouse click again'], 'units','normalized','Position',[posInfo.period2Hz],... 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.period2Hz, ''Value''), try, [x1 y1] = ginput(1); figure(PSfig); h=plot([x1 x1],[-(maxY*2) maxY],''-r'');set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); [x2 y2] = ginput(1); h=plot([x2 x2],[-(maxY*2) maxY],''-r''); set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); plot([x1 x2],[y1 y2],'':k''); x3=[round(x1*1000) round(x2*1000)]; f = 1000/(x3(2)-x3(1)); text(x2, y2, [num2str(x3(2)-x3(1)) ''ms, '' num2str(f) ''Hz''],''FontSize'',fontsz, ''FontWeight'', ''Bold''); catch, end, end'); diff --git a/src/compat/PSdatatip_click.m b/src/compat/PSdatatip_click.m index f77dacc..c1c5f89 100644 --- a/src/compat/PSdatatip_click.m +++ b/src/compat/PSdatatip_click.m @@ -51,7 +51,7 @@ function PSdatatip_click(ax) x = cp(1,1); y = cp(1,2); - % Check if click is within axes limits + xl = get(ax, 'XLim'); yl = get(ax, 'YLim'); if x < xl(1) || x > xl(2) || y < yl(1) || y > yl(2) @@ -77,7 +77,7 @@ function PSdatatip_click(ax) img_obj = ch; break % image takes priority (heatmap background) elseif strcmp(t, 'line') - % Check if line has visible data + xd = get(ch, 'XData'); yd = get(ch, 'YData'); if isempty(xd) || length(xd) < 2, continue; end diff --git a/src/compat/cell2table.m b/src/compat/cell2table.m index 9e5b765..192898d 100644 --- a/src/compat/cell2table.m +++ b/src/compat/cell2table.m @@ -15,7 +15,7 @@ if ~iscell(var_names) var_names = cellstr(var_names); end - var_names = var_names(:)'; % ensure row vector + var_names = var_names(:)'; % force row vector end end @@ -27,7 +27,7 @@ T = struct(); for j = 1:ncols col_data = C(:, j); - % Check if all entries are numeric + all_numeric = true; for k = 1:length(col_data) if ~isnumeric(col_data{k}) diff --git a/src/compat/readtable.m b/src/compat/readtable.m index 348e331..be73bab 100644 --- a/src/compat/readtable.m +++ b/src/compat/readtable.m @@ -165,7 +165,7 @@ s = regexprep(s, '[\[\]\(\) ]+', '_'); % Replace any remaining non-alphanumeric/underscore chars s = regexprep(s, '[^a-zA-Z0-9_]', '_'); - % Ensure doesn't start with a number + % Force doesn't start with a number if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') s = ['x' s]; end diff --git a/src/compat/smooth.m b/src/compat/smooth.m index 69275da..2be3d55 100644 --- a/src/compat/smooth.m +++ b/src/compat/smooth.m @@ -15,13 +15,13 @@ if nargin < 2, span = 5; end if nargin < 3, method = 'moving'; end - y = y(:); % ensure column vector + y = y(:); % force column vector n = length(y); % Clamp span span = min(round(span), n); span = max(span, 1); - % Ensure span is odd for symmetric window + % Force span is odd for symmetric window if mod(span, 2) == 0 span = span + 1; end diff --git a/src/compat/writetable.m b/src/compat/writetable.m index 68932fd..91a59c8 100644 --- a/src/compat/writetable.m +++ b/src/compat/writetable.m @@ -8,7 +8,7 @@ function writetable(T, filename) % Writes the struct-based table T to a text file. % Appends .txt extension if no extension present. - % Ensure .txt extension + % Force .txt extension if isempty(strfind(filename, '.')) filename = [filename '.txt']; end diff --git a/src/core/PSarduConvert.m b/src/core/PSarduConvert.m index 77045be..13ee566 100644 --- a/src/core/PSarduConvert.m +++ b/src/core/PSarduConvert.m @@ -5,7 +5,7 @@ % parms: struct from PSarduRead (parameter name→value) if ~isfield(data, 'RATE') - error('No RATE messages in log — enable LOG_BITMASK bit 1 (ATTITUDE_FAST)'); + error('No RATE messages in log - enable LOG_BITMASK bit 1 (ATTITUDE_FAST)'); end rate = data.RATE; @@ -36,7 +36,7 @@ T_out.setpoint_3_ = zeros(N, 1); end -% PID terms — interpolate to RATE timestamps +% PID terms - interpolate to RATE timestamps axes_map = {'PIDR', '0'; 'PIDP', '1'; 'PIDY', '2'}; for ax = 1:3 msgName = axes_map{ax, 1}; @@ -98,7 +98,7 @@ end end -% debug fields (empty — ArduPilot doesn't use BF debug channels) +% debug fields (empty - ArduPilot doesn't use BF debug channels) for k = 0:3 T_out.(sprintf('debug_%d_', k)) = zeros(N, 1); end @@ -152,7 +152,7 @@ SetupInfo{n,1} = 'd_min'; SetupInfo{n,2} = sprintf('0,0,0'); n = n+1; SetupInfo{n,1} = 'feedforward_weight'; SetupInfo{n,2} = sprintf('%.0f,%.0f,%.0f', rFF*100, pFF*100, yFF*100); n = n+1; -% debug_mode — ArduPilot doesn't have BF debug modes +% debug_mode - ArduPilot doesn't have BF debug modes SetupInfo{n,1} = 'debug_mode'; SetupInfo{n,2} = '0'; n = n+1; % looptime diff --git a/src/core/PSestimateRPM.m b/src/core/PSestimateRPM.m index eb8c6c4..84e511c 100644 --- a/src/core/PSestimateRPM.m +++ b/src/core/PSestimateRPM.m @@ -51,7 +51,7 @@ if any(f2mask) && max(spec(f2mask)) > noiseFloor * 1.5 fundFreq(t) = f0; else - % might be a harmonic itself — check if f0/2 has a peak + % might be a harmonic itself - check if f0/2 has a peak fhlo = f0 * 0.4; fhhi = f0 * 0.6; fhmask = freqAxis >= fhlo & freqAxis <= fhhi & fMask; diff --git a/src/core/PSload.m b/src/core/PSload.m index 74d1d91..7754024 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -14,7 +14,7 @@ try if ~isempty(filenameA) - %% Detect firmware type change — mixing firmware types causes issues + %% Detect firmware type change - mixing firmware types causes issues current_fw = get(guiHandles.Firmware, 'Value'); if exist('loaded_firmware','var') && loaded_firmware ~= current_fw && exist('fnameMaster','var') && ~isempty(fnameMaster) fw_names = get(guiHandles.Firmware, 'String'); @@ -120,7 +120,7 @@ [~, ~, sfext] = fileparts(subFiles{jj}); if strcmpi(sfext, '.bin') - % ArduPilot DataFlash binary — direct parse + % ArduPilot DataFlash binary - direct parse binpath = fullfile(logfile_directory, subFiles{jj}); [ardu_data, ardu_parms] = PSarduRead(binpath); [T{fcnt}, SetupInfo{fcnt}, A_lograte(fcnt)] = PSarduConvert(ardu_data, ardu_parms); diff --git a/src/core/PSquicJson2csv.m b/src/core/PSquicJson2csv.m index 0ba3901..a3f231e 100644 --- a/src/core/PSquicJson2csv.m +++ b/src/core/PSquicJson2csv.m @@ -46,7 +46,7 @@ end nEntries = numel(entries); - % Initialize output arrays + loopIter = zeros(nEntries, 1); time_us = zeros(nEntries, 1); gyro = zeros(nEntries, 3); % RPY filtered diff --git a/src/plot/PSdynSpecPlayer.m b/src/plot/PSdynSpecPlayer.m index 173fd7d..3e20244 100644 --- a/src/plot/PSdynSpecPlayer.m +++ b/src/plot/PSdynSpecPlayer.m @@ -30,11 +30,15 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) end yPad = (yHi - yLo) * 0.05; +thm = PStheme(); +fontsz = thm.fontsz; screensz = get(0, 'ScreenSize'); figW = round(.6 * screensz(3)); figH = round(.85 * screensz(4)); -thm = PStheme(); -fig = figure('Name', ['Spectrum Player - ' signalName], 'NumberTitle', 'off', ... +figName = ['Spectrum Player - ' signalName]; +fig = findobj('Type', 'figure', 'Name', figName); +if ~isempty(fig), close(fig); end +fig = figure('Name', figName, 'NumberTitle', 'off', ... 'Color', thm.figBg, 'Position', [round(.2*screensz(3)) round(.07*screensz(4)) figW figH]); axColors = {[0 .85 .85], [.85 .85 0], [.85 .3 .85]}; @@ -92,7 +96,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) timeLbl = uicontrol(fig, 'Style', 'text', ... 'String', sprintf('%s t = %.2fs', signalName, Tm(1)), ... 'Units', 'normalized', 'Position', [.07 .27 .40 .025], ... - 'FontSize', 12, 'FontWeight', 'bold', ... + 'FontSize', fontsz, 'FontWeight', 'bold', ... 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textPrimary, ... 'HorizontalAlignment', 'left'); @@ -100,30 +104,30 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) btnW = .07; btnH = .04; btnY = .02; uicontrol(fig, 'Style', 'pushbutton', 'String', 'Play', ... 'Units', 'normalized', 'Position', [.07 btnY btnW btnH], ... - 'FontSize', 12, 'FontWeight', 'bold', 'ForegroundColor', [0 .6 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [0 .6 0], ... 'Callback', @playCallback); uicontrol(fig, 'Style', 'pushbutton', 'String', 'Pause', ... 'Units', 'normalized', 'Position', [.07+btnW+.01 btnY btnW btnH], ... - 'FontSize', 12, 'FontWeight', 'bold', 'ForegroundColor', [.8 .2 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [.8 .2 0], ... 'Callback', @pauseCallback); uicontrol(fig, 'Style', 'pushbutton', 'String', 'Stop', ... 'Units', 'normalized', 'Position', [.07+2*(btnW+.01) btnY btnW btnH], ... - 'FontSize', 12, 'FontWeight', 'bold', 'ForegroundColor', [.7 0 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [.7 0 0], ... 'Callback', @stopCallback); uicontrol(fig, 'Style', 'text', 'String', 'Speed:', ... 'Units', 'normalized', 'Position', [.30 btnY+.005 .045 .030], ... - 'FontSize', 11, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textPrimary); + 'FontSize', fontsz, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textPrimary); speedMenu = uicontrol(fig, 'Style', 'popupmenu', ... 'String', {'0.25x', '0.5x', '1x', '2x', '4x'}, 'Value', 3, ... - 'Units', 'normalized', 'Position', [.35 btnY .06 btnH], 'FontSize', 11); + 'Units', 'normalized', 'Position', [.35 btnY .06 btnH], 'FontSize', fontsz); frameLbl = uicontrol(fig, 'Style', 'text', ... 'String', sprintf('1 / %d', nFrames), ... 'Units', 'normalized', 'Position', [.42 btnY+.005 .08 .030], ... - 'FontSize', 10, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textSecondary, ... + 'FontSize', fontsz, 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textSecondary, ... 'HorizontalAlignment', 'center'); timeSlider = uicontrol(fig, 'Style', 'slider', 'Min', 1, 'Max', nFrames, 'Value', 1, ... diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 26019aa..2064f38 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -5,7 +5,10 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) % setupInfo - cell array {param, value} from header thm = PStheme(); +fontsz = thm.fontsz; screensz = get(0, 'ScreenSize'); +fig = findobj('Type', 'figure', 'Name', 'Filter Simulation'); +if ~isempty(fig), close(fig); end fig = figure('Name', 'Filter Simulation', 'NumberTitle', 'off', ... 'Color', thm.figBg, ... 'Position', round([.1*screensz(3) .08*screensz(4) .75*screensz(3) .8*screensz(4)])); @@ -22,65 +25,65 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) uipanel('Parent', fig, 'Title', 'Filter Settings', 'FontWeight', 'bold', ... 'BackgroundColor', thm.panelBg, 'ForegroundColor', thm.panelFg, ... 'HighlightColor', thm.panelBorder, ... - 'FontSize', 12, 'Position', [cpL .02 cpW .96]); + 'FontSize', fontsz, 'Position', [cpL .02 cpW .96]); row = .92; rh = .032; gap = .005; bgc = thm.panelBg; fgc = thm.panelFg; cb = @(~,~) doUpdate(); % axis selector -mkLabel(fig, 'Axis:', cpL, row, rh, bgc, fgc); +mkLabel(fig, 'Axis:', cpL, row, rh, bgc, fgc, fontsz); h.axis = uicontrol(fig, 'Style', 'popupmenu', 'String', axNames, 'Value', 1, ... - 'Units', 'normalized', 'Position', [cpL+.06 row .08 rh], 'FontSize', 11, 'Callback', cb); + 'Units', 'normalized', 'Position', [cpL+.06 row .08 rh], 'FontSize', fontsz, 'Callback', cb); row = row - rh - gap*3; % Gyro LPF1 -mkSection(fig, '--- Gyro LPF1 ---', cpL, row, cpW, rh, bgc, [.5 .9 1]); +mkSection(fig, '--- Gyro LPF1 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); row = row - rh - gap; -[h.glpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf1_type, cb); -[h.glpf1_hz, h.glpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf1_hz, cb); +[h.glpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf1_type, cb, fontsz); +[h.glpf1_hz, h.glpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf1_hz, cb, fontsz); row = row - gap*2; % Gyro LPF2 -mkSection(fig, '--- Gyro LPF2 ---', cpL, row, cpW, rh, bgc, [.5 .9 1]); +mkSection(fig, '--- Gyro LPF2 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); row = row - rh - gap; -[h.glpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf2_type, cb); -[h.glpf2_hz, h.glpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf2_hz, cb); +[h.glpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf2_type, cb, fontsz); +[h.glpf2_hz, h.glpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf2_hz, cb, fontsz); row = row - gap*2; % Gyro Notch 1 -mkSection(fig, '--- Gyro Notch 1 ---', cpL, row, cpW, rh, bgc, [1 .8 .4]); +mkSection(fig, '--- Gyro Notch 1 ---', cpL, row, cpW, rh, bgc, [1 .8 .4], fontsz); row = row - rh - gap; -[h.gn1_hz, h.gn1_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch1_hz, cb); -[h.gn1_cut, h.gn1_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch1_cut, cb); +[h.gn1_hz, h.gn1_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch1_hz, cb, fontsz); +[h.gn1_cut, h.gn1_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch1_cut, cb, fontsz); row = row - gap*2; % Gyro Notch 2 -mkSection(fig, '--- Gyro Notch 2 ---', cpL, row, cpW, rh, bgc, [1 .8 .4]); +mkSection(fig, '--- Gyro Notch 2 ---', cpL, row, cpW, rh, bgc, [1 .8 .4], fontsz); row = row - rh - gap; -[h.gn2_hz, h.gn2_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch2_hz, cb); -[h.gn2_cut, h.gn2_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch2_cut, cb); +[h.gn2_hz, h.gn2_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch2_hz, cb, fontsz); +[h.gn2_cut, h.gn2_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch2_cut, cb, fontsz); row = row - gap*2; % D-term LPF1 -mkSection(fig, '--- D-term LPF1 ---', cpL, row, cpW, rh, bgc, [.5 1 .5]); +mkSection(fig, '--- D-term LPF1 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); row = row - rh - gap; -[h.dlpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf1_type, cb); -[h.dlpf1_hz, h.dlpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf1_hz, cb); +[h.dlpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf1_type, cb, fontsz); +[h.dlpf1_hz, h.dlpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf1_hz, cb, fontsz); row = row - gap*2; % D-term LPF2 -mkSection(fig, '--- D-term LPF2 ---', cpL, row, cpW, rh, bgc, [.5 1 .5]); +mkSection(fig, '--- D-term LPF2 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); row = row - rh - gap; -[h.dlpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf2_type, cb); -[h.dlpf2_hz, h.dlpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf2_hz, cb); +[h.dlpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf2_type, cb, fontsz); +[h.dlpf2_hz, h.dlpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf2_hz, cb, fontsz); row = row - gap*2; % D-term Notch -mkSection(fig, '--- D-term Notch ---', cpL, row, cpW, rh, bgc, [1 .6 .6]); +mkSection(fig, '--- D-term Notch ---', cpL, row, cpW, rh, bgc, [1 .6 .6], fontsz); row = row - rh - gap; -[h.dn_hz, h.dn_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.dterm_notch_hz, cb); -[h.dn_cut, h.dn_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.dterm_notch_cut, cb); +[h.dn_hz, h.dn_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.dterm_notch_hz, cb, fontsz); +[h.dn_cut, h.dn_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.dterm_notch_cut, cb, fontsz); doUpdate(); @@ -164,7 +167,7 @@ function doUpdate() end -%% helpers (NOT nested — no closure issues) +%% helpers (NOT nested - no closure issues) function y = applyLPF(x, type, hz, Fs) if type == 0 || hz == 0, y = x; return; end types = {'pt1', 'biquad', 'pt2', 'pt3'}; @@ -223,39 +226,39 @@ function doUpdate() end end -function mkLabel(fig, txt, cpL, row, rh, bgc, fgc) +function mkLabel(fig, txt, cpL, row, rh, bgc, fgc, fsz) uicontrol(fig, 'Style', 'text', 'String', txt, ... 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', 11, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); end -function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col) +function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) uicontrol(fig, 'Style', 'text', 'String', txt, ... 'Units', 'normalized', 'Position', [cpL+.01 row cpW-.02 rh], ... - 'FontSize', 11, 'FontWeight', 'bold', 'BackgroundColor', bgc, 'ForegroundColor', col); + 'FontSize', fsz, 'FontWeight', 'bold', 'BackgroundColor', bgc, 'ForegroundColor', col); end -function [hType, rowOut] = mkType(fig, cpL, row, rh, gap, bgc, fgc, initVal, cb) +function [hType, rowOut] = mkType(fig, cpL, row, rh, gap, bgc, fgc, initVal, cb, fsz) uicontrol(fig, 'Style', 'text', 'String', 'Type:', ... 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', 11, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); hType = uicontrol(fig, 'Style', 'popupmenu', ... 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', initVal + 1, ... - 'Units', 'normalized', 'Position', [cpL+.06 row .10 rh], 'FontSize', 11, 'Callback', cb); + 'Units', 'normalized', 'Position', [cpL+.06 row .10 rh], 'FontSize', fsz, 'Callback', cb); rowOut = row - rh - gap; end -function [hSlider, hLbl, rowOut] = mkSlider(fig, label, cpL, row, rh, gap, bgc, fgc, mn, mx, initVal, cb) +function [hSlider, hLbl, rowOut] = mkSlider(fig, label, cpL, row, rh, gap, bgc, fgc, mn, mx, initVal, cb, fsz) initVal = max(mn, min(mx, initVal)); uicontrol(fig, 'Style', 'text', 'String', label, ... 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', 11, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); step1 = 1/max(mx-mn, 1); step10 = 10/max(mx-mn, 1); hSlider = uicontrol(fig, 'Style', 'slider', 'Min', mn, 'Max', mx, 'Value', initVal, ... 'Units', 'normalized', 'Position', [cpL+.06 row .13 rh], ... 'SliderStep', [step1 step10], 'Callback', cb); hLbl = uicontrol(fig, 'Style', 'text', 'String', num2str(round(initVal)), ... 'Units', 'normalized', 'Position', [cpL+.20 row .05 rh], ... - 'FontSize', 11, 'BackgroundColor', bgc, 'ForegroundColor', [1 1 .6], 'HorizontalAlignment', 'left'); + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', [1 1 .6], 'HorizontalAlignment', 'left'); rowOut = row - rh - gap; end diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index b3de8c2..93683ce 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -13,15 +13,6 @@ set(PSspecfig3, 'pointer', 'watch') figure(PSspecfig3) -PSspecfig3_pos = get(PSspecfig3, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSspecfig3_pos(3) > 10, PSspecfig3_pos(3:4) = PSspecfig3_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSspecfig3_pos(3) PSspecfig3_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); - -f = fields(guiHandlesSpec3); -for i = 1 : size(f,1) - try set(guiHandlesSpec3.(f{i}), 'FontSize', fontsz); catch, end -end specSmoothFactors = [1 5 10 20]; timeSmoothFactors = [1 2 5 10]; @@ -92,7 +83,7 @@ tmpFFTft = debugIdx{tmpFileVal3}.FFT_FREQ; end if debugmode(tmpFileVal3) == tmpFFTft && numel(notchData) >= tmpFileVal3 && ~isempty(notchData{tmpFileVal3}) - % Only overlay on the axis matching gyro_debug_axis + % match gyro_debug_axis tmpGdaFt = 0; if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileVal3 tmpGdaFt = gyro_debug_axis(tmpFileVal3); @@ -120,10 +111,8 @@ end updateSpec = 0; -% Apply dark theme to all axes allax = findobj(PSspecfig3, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end -% Set up click-to-show-value datatips + double-click expand on all axes PSdatatipSetup(PSspecfig3); set(PSspecfig3, 'pointer', 'arrow') diff --git a/src/plot/PSplotBode.m b/src/plot/PSplotBode.m index 3f8fb82..9980f1a 100644 --- a/src/plot/PSplotBode.m +++ b/src/plot/PSplotBode.m @@ -8,8 +8,12 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) % titleStr - plot title suffix th = PStheme(); +fontsz = th.fontsz; screensz = get(0, 'ScreenSize'); -fig = figure('Name', ['Chirp Analysis - ' titleStr], 'NumberTitle', 'off', ... +figName = ['Chirp Analysis - ' titleStr]; +fig = findobj('Type', 'figure', 'Name', figName); +if ~isempty(fig), close(fig); end +fig = figure('Name', figName, 'NumberTitle', 'off', ... 'Color', th.figBg, ... 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); @@ -76,7 +80,7 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) [~, pk_idx] = max(stepData.step); plot(ax4, stepData.t_ms(pk_idx), peak, 'ro', 'MarkerSize', 8, 'LineWidth', 2); text(stepData.t_ms(pk_idx)+5, peak, sprintf('%.0f%%', os_pct), ... - 'Color', [1 .3 .3], 'FontSize', 12, 'FontWeight', 'bold', 'Parent', ax4); + 'Color', [1 .3 .3], 'FontSize', fontsz, 'FontWeight', 'bold', 'Parent', ax4); end hold(ax4, 'off'); end @@ -105,7 +109,7 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) end end text(0.05, 0.9, infoLines, 'Parent', ax5, 'Color', th.textAccent, ... - 'FontSize', 12, 'FontWeight', 'bold', 'VerticalAlignment', 'top', ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'VerticalAlignment', 'top', ... 'Units', 'normalized'); PSdatatipSetup(fig); diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index fa1a138..7cc99a7 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -23,17 +23,6 @@ alpha_red=.8; alpha_blue=.8; - % scale fonts according to size of window and/or screen - PSfig_pos = get(PSfig, 'Position'); - screensz_tmp = get(0,'ScreenSize'); if PSfig_pos(3) > 10, PSfig_pos(3:4) = PSfig_pos(3:4) ./ screensz_tmp(3:4); end - prop_max_screen=(max([PSfig_pos(3) PSfig_pos(4)])); - fontsz=(screensz_multiplier*prop_max_screen); - - f = fields(guiHandles); - for i = 1 : size(f,1) - try set(guiHandles.(f{i}), 'FontSize', fontsz); catch, end - end - set(controlpanel, 'FontSize', fontsz); lineSmoothFactors = [1 10 20 40 80]; @@ -286,7 +275,6 @@ 'elseif kk==''o'', try, [xt,~]=ginput(1); epoch2_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... 'end, end']); - % Set up click-to-show-value datatips on all axes PSdatatipSetup(PSfig); set(PSfig, 'pointer', 'arrow') diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index c8a3b39..09f9151 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -6,7 +6,10 @@ function PSplotMotorNoise(T, f, tIND, Fs) % Fs - sample rate (Hz) th = PStheme(); +fontsz = th.fontsz; screensz = get(0, 'ScreenSize'); +fig = findobj('Type', 'figure', 'Name', 'Motor / Prop Noise Analysis'); +if ~isempty(fig), close(fig); end fig = figure('Name', 'Motor / Prop Noise Analysis', 'NumberTitle', 'off', ... 'Color', th.figBg, ... 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); @@ -78,7 +81,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) infoStr = sprintf('Noisiest: %s (%.0f dB) Quietest: %s (%.0f dB) Spread: %.1f dB', ... motorLbl{worst}, rmsAll(worst), motorLbl{best}, rmsAll(best), spread); uicontrol(fig, 'Style', 'text', 'String', infoStr, 'Units', 'normalized', ... - 'Position', [.06 .965 .88 .03], 'FontSize', 13, 'FontWeight', 'bold', ... + 'Position', [.06 .965 .88 .03], 'FontSize', fontsz, 'FontWeight', 'bold', ... 'ForegroundColor', th.textAccent, 'BackgroundColor', th.panelBg, ... 'HorizontalAlignment', 'center'); @@ -119,7 +122,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) % gyro axis dropdown (right of title, inside plot area) uicontrol(fig, 'Style', 'popupmenu', 'String', {'Roll', 'Pitch', 'Yaw'}, ... 'Units', 'normalized', 'Position', [.06 .64 .10 .025], ... - 'FontSize', 12, 'Callback', {@cohAxisCb, fig}); + 'FontSize', fontsz, 'Callback', {@cohAxisCb, fig}); setappdata(fig, 'mndat', dat); % --- MID RIGHT: Noise bar chart per frequency band --- diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index a187f32..eda583d 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -1,4 +1,4 @@ -%% PSplotPIDerror +%% PSplotPIDerror - PID error distribution and error vs stick deflection % ---------------------------------------------------------------------------------- % "THE BEER-WARE LICENSE" (Revision 42): @@ -8,190 +8,188 @@ % ---------------------------------------------------------------------------------- try - -set(PSerrfig, 'pointer', 'watch') + +set(PSerrfig, 'pointer', 'watch'); th = PStheme(); -if ~isempty(filenameA) || ~isempty(filenameB) - %% update fonts - - PSerrfig_pos = get(PSerrfig, 'Position'); - screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end - prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); - fontsz3=round(screensz_multiplier*prop_max_screen); - - set(guiHandlesPIDerr.refresh, 'FontSize', fontsz3); - set(guiHandlesPIDerr.maxSticktext, 'FontSize', fontsz3); - set(guiHandlesPIDerr.maxStick, 'FontSize', fontsz3); - set(guiHandlesPIDerr.saveFig3, 'FontSize', fontsz3); - - - %% PID error distributions - ylab2={'roll';'pitch';'yaw'}; - figure(PSerrfig); - for p=1:3 - delete(subplot('position',posInfo.PIDerrAnalysis(p,:))) - h1=subplot('position',posInfo.PIDerrAnalysis(p,:)); cla - hold on - - if ~isempty(filenameA) - RCRateALL_Thresh_A=abs(DATtmpA.RCRate(1,:)) < maxDegsec & abs(DATtmpA.RCRate(2,:)) < maxDegsec & abs(DATtmpA.RCRate(3,:)) < maxDegsec & abs(DATtmpA.PIDerr(1,:)) < maxDegsec & abs(DATtmpA.PIDerr(2,:)) < maxDegsec & abs(DATtmpA.PIDerr(3,:)) < maxDegsec; - [yA xA]=hist(DATtmpA.PIDerr(p,RCRateALL_Thresh_A),-1000:1:1000); %= 2, fB = 2; end + + axPIDerr = {'piderr_0_', 'piderr_1_', 'piderr_2_'}; + axSP = {'setpoint_0_', 'setpoint_1_', 'setpoint_2_'}; + + %% PID error distributions + ylab2 = {'roll'; 'pitch'; 'yaw'}; + figure(PSerrfig); + for p = 1:3 + delete(subplot('position', posInfo.PIDerrAnalysis(p,:))); + h1 = subplot('position', posInfo.PIDerrAnalysis(p,:)); cla; + hold on; + + piderr_A = T{fA}.(axPIDerr{p})(tIND{fA})'; + mask_A = true(size(piderr_A)); + for q = 1:3 + mask_A = mask_A & abs(T{fA}.(axSP{q})(tIND{fA})') < maxDegsec ... + & abs(T{fA}.(axPIDerr{q})(tIND{fA})') < maxDegsec; end - box off - if p==1, - title('normalized PID error distributions'); + [yA, xA] = hist(piderr_A(mask_A), -1000:1:1000); + yA = yA / max(yA); + h = plot(xA, yA); + set(h, 'color', colorA, 'Linewidth', 2); + if p == 3 + set(h1, 'xtick', -40:10:40, 'ytick', 0:.25:1, 'tickdir', 'out', ... + 'xminortick', 'on', 'yminortick', 'on', 'fontsize', fontsz3); + xlabel('PID error (deg/s)', 'fontweight', 'bold'); + else + set(h1, 'xtick', -40:10:40, 'xticklabel', {}, 'ytick', 0:.25:1, ... + 'tickdir', 'out', 'xminortick', 'on', 'yminortick', 'on', 'fontsize', fontsz3); + end + ylabel('normalized freq', 'fontweight', 'bold'); + h = text(-37, .9, ylab2{p}); + set(h, 'fontsize', fontsz3, 'fontweight', 'bold'); + grid on; + axis([-40 40 0 1]); + h = text(10, .9, ['[1]s.d.=' num2str(round(std(piderr_A(mask_A))*10)/10)]); + set(h, 'fontsize', fontsz3, 'color', colorA, 'fontweight', 'bold'); + + if ~isempty(fB) + piderr_B = T{fB}.(axPIDerr{p})(tIND{fB})'; + mask_B = true(size(piderr_B)); + for q = 1:3 + mask_B = mask_B & abs(T{fB}.(axSP{q})(tIND{fB})') < maxDegsec ... + & abs(T{fB}.(axPIDerr{q})(tIND{fB})') < maxDegsec; + end + [yB, xB] = hist(piderr_B(mask_B), -1000:1:1000); + yB = yB / max(yB); + h = plot(xB, yB); + set(h, 'color', colorB, 'Linewidth', 2); + h = text(10, .8, ['[2]s.d.=' num2str(round(std(piderr_B(mask_B))*10)/10)]); + set(h, 'fontsize', fontsz3, 'color', colorB, 'fontweight', 'bold'); + + try + [~, pval] = kstest2(yA, yB); + if pval <= .05, sigflag = '*'; else, sigflag = ''; end + h = text(10, .7, ['p=' num2str(pval) sigflag]); + set(h, 'fontsize', fontsz3, 'fontweight', 'bold'); + catch + end end + + box off; + if p == 1, title('normalized PID error distributions'); end end - - - %% compute PID error and latency x % stick deflection + + + %% PID error x stick deflection if ~updateErr - t=[.1 .2 .3 .4 .5 .6 .7 .8 .9 1]; - - % cutoff=100; % ignore less frequent error at the extremes, outliers - if ~isempty(filenameA) - for i=1:length(t) - clear RCRateALL_Thresh_A - m=max(max(abs(DATtmpA.RCRate))) * (t(i)); - RCRateALL_Thresh_A=abs(DATtmpA.RCRate(1,:)) < m & abs(DATtmpA.RCRate(2,:)) < m & abs(DATtmpA.RCRate(3,:)) < m & abs(DATtmpA.PIDerr(1,:)) < m & abs(DATtmpA.PIDerr(2,:)) < m & abs(DATtmpA.PIDerr(3,:)) < m; - for j=1:3 - perr_a=[]; - perr_a=DATtmpA.PIDerr(j, RCRateALL_Thresh_A); - Perr_a_m(j,i)=nanmean(abs(perr_a)); - Perr_a_se(j,i)=nanstd(abs(perr_a)) / sqrt(length(perr_a)); - end + t = [.1 .2 .3 .4 .5 .6 .7 .8 .9 1]; + + sp_A = zeros(3, sum(tIND{fA})); + err_A = zeros(3, sum(tIND{fA})); + for q = 1:3 + sp_A(q,:) = T{fA}.(axSP{q})(tIND{fA})'; + err_A(q,:) = T{fA}.(axPIDerr{q})(tIND{fA})'; + end + maxSP_A = max(abs(sp_A(:))); + + Perr_a_m = zeros(3, length(t)); + Perr_a_se = zeros(3, length(t)); + for i = 1:length(t) + m = maxSP_A * t(i); + msk = abs(sp_A(1,:)) < m & abs(sp_A(2,:)) < m & abs(sp_A(3,:)) < m ... + & abs(err_A(1,:)) < m & abs(err_A(2,:)) < m & abs(err_A(3,:)) < m; + for j = 1:3 + pe = abs(err_A(j, msk)); + Perr_a_m(j,i) = mean(pe); + Perr_a_se(j,i) = std(pe) / sqrt(numel(pe)); end end - t=[.1 .2 .3 .4 .5 .6 .7 .8 .9 1]; - if ~isempty(filenameB) - for i=1:length(t) - clear RCRateALL_Thresh_B - m=max(max(abs(DATtmpB.RCRate))) * (t(i)); - RCRateALL_Thresh_B=abs(DATtmpB.RCRate(1,:)) < m & abs(DATtmpB.RCRate(2,:)) < m & abs(DATtmpB.RCRate(3,:)) < m & abs(DATtmpB.PIDerr(1,:)) < m & abs(DATtmpB.PIDerr(2,:)) < m & abs(DATtmpB.PIDerr(3,:)) < m; - for j=1:3 - perr_b=[]; - perr_b=DATtmpB.PIDerr(j, RCRateALL_Thresh_B); - Perr_b_m(j,i)=nanmean(abs(perr_b)); - Perr_b_se(j,i)=nanstd(abs(perr_b)) / sqrt(length(perr_b)); + if ~isempty(fB) + sp_B = zeros(3, sum(tIND{fB})); + err_B = zeros(3, sum(tIND{fB})); + for q = 1:3 + sp_B(q,:) = T{fB}.(axSP{q})(tIND{fB})'; + err_B(q,:) = T{fB}.(axPIDerr{q})(tIND{fB})'; + end + maxSP_B = max(abs(sp_B(:))); + + Perr_b_m = zeros(3, length(t)); + Perr_b_se = zeros(3, length(t)); + for i = 1:length(t) + m = maxSP_B * t(i); + msk = abs(sp_B(1,:)) < m & abs(sp_B(2,:)) < m & abs(sp_B(3,:)) < m ... + & abs(err_B(1,:)) < m & abs(err_B(2,:)) < m & abs(err_B(3,:)) < m; + for j = 1:3 + pe = abs(err_B(j, msk)); + Perr_b_m(j,i) = mean(pe); + Perr_b_se(j,i) = std(pe) / sqrt(numel(pe)); end end end - updateErr=0; + updateErr = 0; end - %% PID error x stick - ylab=['R';'P';'Y']; - for p=1:3 - delete(subplot('position',posInfo.PIDerrAnalysis(p+3,:))) - h1=subplot('position',posInfo.PIDerrAnalysis(p+3,:)); cla - posA=.8:1:9.8; - posB=1.2:1:10.2; - if ~isempty(filenameA) - minyA=min(Perr_a_m(p,:))-.5;if minyA<0, minyA=0;end - maxyA=max(Perr_a_m(p,:))+.5; - h=errorbar([posA],[Perr_a_m(p,:) ], [Perr_a_se(p,:) ] );hold on - set(h, 'color',th.axesFg, 'LineStyle','none'); - h=bar([posA], (Perr_a_m(p,:) )); - set(h, 'facecolor',[colorA],'facealpha',.8,'BarWidth',.4) - set(h1,'tickdir','out','xminortick','off','yminortick','on'); - set(h1,'fontsize',fontsz3); - ylabel(['mean |' ylab(p) ' error| ^o/s'], 'fontweight','bold') - set(h1,'xtick',[0:2:10], 'xticklabel',{''},'ygrid','on'); - axis([0 11 minyA maxyA]) - box off - if p==3 - set(h1,'xtick',[0:1:10], 'xticklabel',{'0', '', '20','','40','', '60','', '80','', '100'}); - xlabel('stick deflection (% of max)', 'fontweight','bold') - else - set(h1,'xtick',[0:1:10], 'xticklabel',{'', '', '','','','', '','', '','', ''}); + %% plot error x stick + ylab = ['R'; 'P'; 'Y']; + for p = 1:3 + delete(subplot('position', posInfo.PIDerrAnalysis(p+3,:))); + h1 = subplot('position', posInfo.PIDerrAnalysis(p+3,:)); cla; + posAx = .8:1:9.8; + posBx = 1.2:1:10.2; + + minyA = min(Perr_a_m(p,:)) - .5; if minyA < 0, minyA = 0; end + maxyA = max(Perr_a_m(p,:)) + .5; + h = errorbar(posAx, Perr_a_m(p,:), Perr_a_se(p,:)); hold on; + set(h, 'color', th.axesFg, 'LineStyle', 'none'); + h = bar(posAx, Perr_a_m(p,:)); + set(h, 'facecolor', colorA, 'BarWidth', .4); + set(h1, 'tickdir', 'out', 'xminortick', 'off', 'yminortick', 'on', 'fontsize', fontsz3); + ylabel(['mean |' ylab(p) ' error| ^o/s'], 'fontweight', 'bold'); + set(h1, 'xtick', 0:2:10, 'xticklabel', {''}, 'ygrid', 'on'); + axis([0 11 minyA maxyA]); + box off; + if p == 3 + set(h1, 'xtick', 0:1:10, 'xticklabel', ... + {'0','','20','','40','','60','','80','','100'}); + xlabel('stick deflection (% of max)', 'fontweight', 'bold'); + else + set(h1, 'xtick', 0:1:10, 'xticklabel', ... + {'','','','','','','','','','',''}); end - end - if ~isempty(filenameB) - minyB=min(Perr_b_m(p,:))-.5;if minyB<0, minyB=0;end - maxyB=max(Perr_b_m(p,:))+.5; - h=errorbar([posB],[ Perr_b_m(p,:)], [ Perr_b_se(p,:)] ); - set(h, 'color',th.axesFg, 'LineStyle','none'); - h=bar([posB], (Perr_b_m(p,:))); - set(h, 'facecolor',[colorB],'facealpha',.8,'BarWidth',.4) - set(h1,'tickdir','out','xminortick','off','yminortick','on'); - set(h1,'fontsize',fontsz3); - ylabel(['mean |' ylab(p) ' error| ^o/s'], 'fontweight','bold') - set(h1,'xtick',[0:2:10], 'xticklabel',{''},'ygrid','on'); - axis([0 11 min([minyA minyB]) max([maxyA maxyB])]) - box off - if p==3 - set(h1,'xtick',[0:1:10], 'xticklabel',{'0', '', '20','','40','', '60','', '80','', '100'}); - xlabel('stick deflection (% of max)', 'fontweight','bold') + if ~isempty(fB) + minyB = min(Perr_b_m(p,:)) - .5; if minyB < 0, minyB = 0; end + maxyB = max(Perr_b_m(p,:)) + .5; + h = errorbar(posBx, Perr_b_m(p,:), Perr_b_se(p,:)); + set(h, 'color', th.axesFg, 'LineStyle', 'none'); + h = bar(posBx, Perr_b_m(p,:)); + set(h, 'facecolor', colorB, 'BarWidth', .4); + axis([0 11 min([minyA minyB]) max([maxyA maxyB])]); + box off; + if p == 3 + set(h1, 'xtick', 0:1:10, 'xticklabel', ... + {'0','','20','','40','','60','','80','','100'}); + xlabel('stick deflection (% of max)', 'fontweight', 'bold'); else - set(h1,'xtick',[0:1:10], 'xticklabel',{'', '', '','','','', '','', '','', ''}); + set(h1, 'xtick', 0:1:10, 'xticklabel', ... + {'','','','','','','','','','',''}); end end - if p==1, - title('mean abs PID error X stick deflection'); - end + if p == 1, title('mean abs PID error X stick deflection'); end end - -% end allax = findobj(PSerrfig, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end -set(PSerrfig, 'pointer', 'arrow') - +set(PSerrfig, 'pointer', 'arrow'); catch err - msgPSplotPIDerror=PSerrorMessages('PSplotPIDerror', err); + msgPSplotPIDerror = PSerrorMessages('PSplotPIDerror', err); end - - \ No newline at end of file diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 166280e..781b38f 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -11,24 +11,6 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) th = PStheme(); -PSspecfig_pos = get(PSspecfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSspecfig_pos(3) > 10, PSspecfig_pos(3:4) = PSspecfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSspecfig_pos(3) PSspecfig_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); -%% update fonts - -f = fields(guiHandlesSpec); -for i = 1 : size(f,1) - try - h = guiHandlesSpec.(f{i}); - if iscell(h) - for ci = 1:numel(h), set(h{ci}, 'FontSize', fontsz); end - else - set(h, 'FontSize', fontsz); - end - catch - end -end guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); @@ -195,7 +177,7 @@ tmpFFTk = debugIdx{tmpFileK}.FFT_FREQ; end if debugmode(tmpFileK) == tmpFFTk && numel(notchData) >= tmpFileK && ~isempty(notchData{tmpFileK}) - % Only overlay on the axis matching gyro_debug_axis + % match gyro_debug_axis tmpGdaK = 0; if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileK tmpGdaK = gyro_debug_axis(tmpFileK); @@ -286,18 +268,20 @@ set(hCbar4,'Position', [posInfo.hCbar4pos]) end - % color maps - use set() to avoid stale colorbar listener errors in colormap() + % color maps try - tmpCmapVal = get(guiHandlesSpec.ColormapSelect, 'Value'); - if tmpCmapVal <= 7 - tmpCmapStr = get(guiHandlesSpec.ColormapSelect, 'String'); - cm = feval(char(tmpCmapStr(tmpCmapVal)), 64); - elseif tmpCmapVal == 8 - cm = linearREDcmap; - else - cm = linearGREYcmap; + if ishandle(PSspecfig) + tmpCmapVal = get(guiHandlesSpec.ColormapSelect, 'Value'); + if tmpCmapVal <= 7 + tmpCmapStr = get(guiHandlesSpec.ColormapSelect, 'String'); + cm = feval(char(tmpCmapStr(tmpCmapVal)), 64); + elseif tmpCmapVal == 8 + cm = linearREDcmap; + else + cm = linearGREYcmap; + end + colormap(PSspecfig, cm); end - set(PSspecfig, 'Colormap', cm); catch, end end @@ -359,7 +343,6 @@ end end end -% Set up click-to-show-value datatips + double-click expand on all axes PSdatatipSetup(PSspecfig); set(PSspecfig, 'pointer', 'arrow') diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 8b048bc..bce564a 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -10,18 +10,6 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) th = PStheme(); -%% update fonts -PSspecfig2_pos = get(PSspecfig2, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSspecfig2_pos(3) > 10, PSspecfig2_pos(3:4) = PSspecfig2_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSspecfig2_pos(3) PSspecfig2_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); - -f = fields(guiHandlesSpec2); -for i = 1 : size(f,1) - try set(guiHandlesSpec2.(f{i}), 'FontSize', fontsz); catch, end -end - -set(spec2Crtlpanel, 'FontSize', fontsz); guiHandlesSpec2.climMax1_text = uicontrol(PSspecfig2,'style','text','string','Y min','fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax1_text]); guiHandlesSpec2.climMax1_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','Position',[posInfo.climMax1_input],... @@ -373,10 +361,8 @@ end -% Apply dark theme to all axes allax = findobj(PSspecfig2, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end -% Set up click-to-show-value datatips + double-click expand on all axes PSdatatipSetup(PSspecfig2); set(PSspecfig2, 'pointer', 'arrow') diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index bb2b186..23e5b43 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -1,4 +1,4 @@ -%% PSplotStats - script to plot flight statistics +%% PSplotStats - flight statistics (histograms, mean+SD) % ---------------------------------------------------------------------------------- % "THE BEER-WARE LICENSE" (Revision 42): @@ -6,1148 +6,221 @@ % can do whatever you want with this stuff. If we meet some day, and you think % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- - -if ~isempty(filenameA) || ~isempty(filenameB) - th = PStheme(); - set(PSstatsfig, 'pointer', 'watch') - %% update fonts -PSstatsfig_pos = get(PSstatsfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); -fontsz5=round(screensz_multiplier*prop_max_screen); +if ~exist('fnameMaster','var') || isempty(fnameMaster), return; end -set(guiHandlesStats.saveFig5, 'FontSize', fontsz5); -set(guiHandlesStats.refresh, 'FontSize', fontsz5); -set(guiHandlesStats.degsecStick, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats, 'FontSize', fontsz5); +try -set(guiHandlesStats.crossAxesStats_text, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_input, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_text2, 'FontSize', fontsz5); -set(guiHandlesStats.crossAxesStats_input2, 'FontSize', fontsz5); +th = PStheme(); +set(PSstatsfig, 'pointer', 'watch'); -%% Histograms -if get(guiHandlesStats.crossAxesStats, 'Value')==1 - if ~isempty(filenameA) - if ~updateStats - - rcRates=dataA.rates(1,:); - rcExpo=dataA.rates(2,:); - Srates=dataA.rates(3,:); - const=200; - if FirmwareCode_A==INAV, const=1000; end - - RateCurveRoll_A=PSrc2deg([0:5:500],dataA.rates(1,1), dataA.rates(2,1), dataA.rates(3,1), const); - RateCurvePitch_A=PSrc2deg([0:5:500],dataA.rates(1,2), dataA.rates(2,2), dataA.rates(3,2), const); - RateCurveYaw_A=PSrc2deg([0:5:500],dataA.rates(1,3), dataA.rates(2,3), dataA.rates(3,3), const); - - Yscale=round((max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])) / 50) * 50; - - if get(guiHandlesStats.degsecStick, 'Value')==1, - RateCurveRoll_A=(diff(RateCurveRoll_A)); - RateCurvePitch_A=(diff(RateCurvePitch_A)); - RateCurveYaw_A=(diff(RateCurveYaw_A)); - - Yscale=round(max([max(RateCurveRoll_A) max(RateCurvePitch_A) max(RateCurveYaw_A)])); - end - - Rpercent_A=PSPercent(DATtmpA.RCcommand(1,:)); - Ppercent_A=PSPercent(DATtmpA.RCcommand(2,:)); - Ypercent_A=PSPercent(DATtmpA.RCcommand(3,:)); - Tpercent_A=DATtmpA.RCRate(4,:); % already computed for throttle - end +fA = 1; +fB = []; if Nfiles >= 2, fB = 2; end - hhist=subplot('position',posInfo.statsPos(1,:)); - cla - h=histogram(Rpercent_A,'Normalization','probability','BinWidth',1); - y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold') - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) +plotMode = get(guiHandlesStats.crossAxesStats, 'Value'); - hold on +%% Histograms +if plotMode == 1 - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_A)-1],RateCurveRoll_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + clear posInfo.statsPos + cols = [0.06 0.54]; + rows = [0.75 0.52 0.29 0.06]; + k = 0; + for c = 1:2 + for r = 1:4 + k = k + 1; + posInfo.statsPos(k,:) = [cols(c) rows(r) 0.39 0.18]; end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_A(20) RateCurveRoll_A(40) RateCurveRoll_A(60) RateCurveRoll_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(1,:)]); - axis([1 100 0 .1]) + end - text(21, RateCurveRoll_A(20),[int2str(RateCurveRoll_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveRoll_A(40),[int2str(RateCurveRoll_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveRoll_A(60),[int2str(RateCurveRoll_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveRoll_A(80),[int2str(RateCurveRoll_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on + axLbl = {'% roll', '% pitch', '% yaw', '% throttle'}; + rcFields = {'rcCommand_0_', 'rcCommand_1_', 'rcCommand_2_'}; - hhist=subplot('position',posInfo.statsPos(2,:)); - cla - h=histogram(Ppercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) - y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold') - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_A)-1],RateCurvePitch_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + % File 1 + if ~updateStats + for q = 1:3 + rcRaw = T{fA}.(rcFields{q})(tIND{fA}); + Rpct_A{q} = PSPercent(rcRaw); end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_A(20) RateCurvePitch_A(40) RateCurvePitch_A(60) RateCurvePitch_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(2,:)]); - axis([1 100 0 .1]) - - text(21, RateCurvePitch_A(20),[int2str(RateCurvePitch_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurvePitch_A(40),[int2str(RateCurvePitch_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurvePitch_A(60),[int2str(RateCurvePitch_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurvePitch_A(80),[int2str(RateCurvePitch_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(3,:)); - cla - h=histogram(Ypercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7) - y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold') + Tpct_A = T{fA}.setpoint_3_(tIND{fA}) / 10; + end - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_A)-1],RateCurveYaw_A(1,2:end)); - set(ax(1),'Ycolor',[colorA]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); + for sp = 1:4 + hhist = subplot('position', posInfo.statsPos(sp,:)); cla; + if sp <= 3 + pctData = Rpct_A{sp}; + else + pctData = Tpct_A; end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_A(20) RateCurveYaw_A(40) RateCurveYaw_A(60) RateCurveYaw_A(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(3,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveYaw_A(20),[int2str(RateCurveYaw_A(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveYaw_A(40),[int2str(RateCurveYaw_A(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveYaw_A(60),[int2str(RateCurveYaw_A(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveYaw_A(80),[int2str(RateCurveYaw_A(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(4,:)); - cla - h=histogram(Tpercent_A,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorA],'FaceAlpha',.9, 'edgecolor',[colorA],'EdgeAlpha',.7); - grid on - y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight', 'color',[colorA],'fontweight','bold') - set(hhist,'ycolor',[colorA],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(4,:)]); - axis([1 100 0 .1]) + [nn, xx] = hist(pctData, 0:1:100); + nn = nn / sum(nn); + hb = bar(xx, nn, 1); + set(hb, 'FaceColor', colorA, 'EdgeColor', colorA); + y = xlabel(axLbl{sp}, 'fontweight', 'bold'); + set(y, 'Units', 'normalized', 'position', [.5 -.1 1], 'color', th.textPrimary); + ylabel('% of flight', 'fontweight', 'bold'); + set(hhist, 'tickdir', 'in', 'xlim', [0 100], 'xtick', [0 20 40 60 80 100], ... + 'ylim', [0 .1], 'ytick', [0 .05 .1], ... + 'xticklabel', {'0','20','40','60','80','100'}, ... + 'yticklabel', {'0','5','10'}, 'fontsize', fontsz5); + axis([0 100 0 .1]); + grid on; end - if ~isempty(filenameB) + % File 2 + if ~isempty(fB) if ~updateStats - rcRates=dataB.rates(1,:); - rcExpo=dataB.rates(2,:); - Srates=dataB.rates(3,:); - const=200; - if FirmwareCode_B==INAV, const=1000; end - - RateCurveRoll_B=PSrc2deg([0:5:500],dataB.rates(1,1), dataB.rates(2,1), dataB.rates(3,1), const); - RateCurvePitch_B=PSrc2deg([0:5:500],dataB.rates(1,2), dataB.rates(2,2), dataB.rates(3,2), const); - RateCurveYaw_B=PSrc2deg([0:5:500],dataB.rates(1,3), dataB.rates(2,3), dataB.rates(3,3), const); - - Yscale=round((max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])) / 50) * 50; - - if get(guiHandlesStats.degsecStick, 'Value')==1, - RateCurveRoll_B=(diff(RateCurveRoll_B)); - RateCurvePitch_B=(diff(RateCurvePitch_B)); - RateCurveYaw_B=(diff(RateCurveYaw_B)); - - Yscale=round(max([max(RateCurveRoll_B) max(RateCurvePitch_B) max(RateCurveYaw_B)])); - end - - Rpercent_B=PSPercent(DATtmpB.RCcommand(1,:)); - Ppercent_B=PSPercent(DATtmpB.RCcommand(2,:)); - Ypercent_B=PSPercent(DATtmpB.RCcommand(3,:)); - Tpercent_B=DATtmpB.RCRate(4,:); % already computed for throttle + for q = 1:3 + rcRaw = T{fB}.(rcFields{q})(tIND{fB}); + Rpct_B{q} = PSPercent(rcRaw); + end + Tpct_B = T{fB}.setpoint_3_(tIND{fB}) / 10; end - hhist=subplot('position',posInfo.statsPos(5,:)); - cla - h=histogram(Rpercent_B,'Normalization','probability','BinWidth',1); - y=xlabel('% roll','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold') - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - - hold on - - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveRoll_B)-1],RateCurveRoll_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveRoll_B(20) RateCurveRoll_B(40) RateCurveRoll_B(60) RateCurveRoll_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(5,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveRoll_B(20),[int2str(RateCurveRoll_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveRoll_B(40),[int2str(RateCurveRoll_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveRoll_B(60),[int2str(RateCurveRoll_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveRoll_B(80),[int2str(RateCurveRoll_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(1))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(1))],'Parent', ax(1),'fontsize',fontsz5); - - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(6,:)); - cla - h=histogram(Ppercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - y=xlabel('% pitch','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold'); - - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurvePitch_B)-1],RateCurvePitch_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurvePitch_B(20) RateCurvePitch_B(40) RateCurvePitch_B(60) RateCurvePitch_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(6,:)]); - axis([1 100 0 .1]) - - text(21, RateCurvePitch_B(20),[int2str(RateCurvePitch_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurvePitch_B(40),[int2str(RateCurvePitch_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurvePitch_B(60),[int2str(RateCurvePitch_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurvePitch_B(80),[int2str(RateCurvePitch_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - - text(2, .095,['rates: ' num2str(rcRates(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(2))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(2))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(7,:)); - cla - h=histogram(Ypercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - y=xlabel('% yaw','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight','fontweight','bold') - - hold on - [ax,h1,h2]=plotyy(0,0,[1:length(RateCurveYaw_B)-1],RateCurveYaw_B(1,2:end)); - set(ax(1),'Ycolor',[colorB]) - set(ax(2),'Xlim',[1 100],'YLim',[0 Yscale] ,'ytick',[0 round(Yscale/2) Yscale], 'Ycolor',th.axesFg,'fontsize',fontsz5) - set(get(ax(2), 'YLabel'), 'String', 'deg/s'); - if get(guiHandlesStats.degsecStick, 'Value')==1, - set(get(ax(2), 'YLabel'), 'String', 'deg/s/stick travel units'); - end - set(h2,'color',[.5 .5 .5],'LineWidth',1.5) - hold(ax(2),'on'); h=plot([20 40 60 80],[RateCurveYaw_B(20) RateCurveYaw_B(40) RateCurveYaw_B(60) RateCurveYaw_B(80)],'ko','Parent', ax(2)); - set(h,'markerfacecolor',th.axesFg) - - set(hhist,'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(7,:)]); - axis([1 100 0 .1]) - - text(21, RateCurveYaw_B(20),[int2str(RateCurveYaw_B(20)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(41, RateCurveYaw_B(40),[int2str(RateCurveYaw_B(40)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(61, RateCurveYaw_B(60),[int2str(RateCurveYaw_B(60)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(81, RateCurveYaw_B(80),[int2str(RateCurveYaw_B(80)) 'deg/s'],'Parent', ax(2),'fontsize',fontsz5); - text(2, .095,['rates: ' num2str(rcRates(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .085,['expo: ' num2str(rcExpo(3))],'Parent', ax(1),'fontsize',fontsz5); - text(2, .075,['super: ' num2str(Srates(3))],'Parent', ax(1),'fontsize',fontsz5); - axis([1 100 0 .1]) - grid on - - hhist=subplot('position',posInfo.statsPos(8,:)); - cla - h=histogram(Tpercent_B,'Normalization','probability','BinWidth',1); - set(h,'FaceColor',[colorB],'FaceAlpha',.9, 'edgecolor',[colorB],'EdgeAlpha',.7) - grid on - y=xlabel('% throttle','fontweight','bold'); - set(y,'Units','normalized', 'position', [.5 -.1 1],'color',th.textPrimary); - ylabel('% of flight', 'color',[colorB],'fontweight','bold') - set(hhist,'ycolor',[colorB],'tickdir','in','xlim',[1 100],'xtick',[1 20 40 60 80 100],'ylim',[0 .1],'ytick',[0 .05 .1],'xticklabels',{0 20 40 60 80 100},'yticklabels',{0 5 10},'fontsize',fontsz5, 'Position',[posInfo.statsPos(8,:)]); - axis([1 100 0 .1]) - end -end - -%% means/standard deviations - -if get(guiHandlesStats.crossAxesStats, 'Value')==2 - - cols=[0.06 0.3 0.54 0.78]; - rows=[0.76 0.53 0.3 0.08]; - k=0; - for c=1:length(cols) - for r=1:length(rows) - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.18 0.16]; + for sp = 1:4 + hhist = subplot('position', posInfo.statsPos(sp+4,:)); cla; + if sp <= 3 + pctData = Rpct_B{sp}; + else + pctData = Tpct_B; + end + [nn, xx] = hist(pctData, 0:1:100); + nn = nn / sum(nn); + hb = bar(xx, nn, 1); + set(hb, 'FaceColor', colorB, 'EdgeColor', colorB); + y = xlabel(axLbl{sp}, 'fontweight', 'bold'); + set(y, 'Units', 'normalized', 'position', [.5 -.1 1], 'color', th.textPrimary); + ylabel('% of flight', 'fontweight', 'bold'); + set(hhist, 'tickdir', 'in', 'xlim', [0 100], 'xtick', [0 20 40 60 80 100], ... + 'ylim', [0 .1], 'ytick', [0 .05 .1], ... + 'xticklabel', {'0','20','40','60','80','100'}, ... + 'yticklabel', {'0','5','10'}, 'fontsize', fontsz5); + axis([0 100 0 .1]); + grid on; end end - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - - N=length(DATtmpA.GyroFilt(1,:)); - - % gyro - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'FaceColor',[colorA]);%[ColorSet(11,:)]) - s1=errorbar([2],mean(abs(DATtmpA.GyroFilt(2,:))), std(abs(DATtmpA.GyroFilt(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.GyroFilt(2,:)))); - set(s2,'FaceColor',[colorA]);%,[ColorSet(12,:)]) - s1=errorbar([3],mean(abs(DATtmpA.GyroFilt(1,:))), std(abs(DATtmpA.GyroFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.GyroFilt(1,:)))); - set(s3,'FaceColor',[colorA]);%,[ColorSet(13,:)]) - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Gyro| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.GyroFilt),2))+max((std(abs(DATtmpA.GyroFilt)')'))); - axis([.5 3.5 0 ymax]) - box off - - % RCRate - h1=subplot('position',posInfo.statsPos2(5,:)); cla - s1=errorbar([1],mean(abs(Rpercent_A)), std(abs(Rpercent_A)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(Rpercent_A)));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(Ppercent_A)), std(abs(Ppercent_A)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(Ppercent_A))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(Ypercent_A)), std(abs(Ypercent_A)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(Ypercent_A))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(abs(Tpercent_A)), std(abs(Tpercent_A)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(Tpercent_A)); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('% RPYT [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - axis([0.5 4.5 0 100]) - box off - - % pterm - h1=subplot('position',posInfo.statsPos2(2,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Pterm(1,:))), std(abs(DATtmpA.Pterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Pterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Pterm(2,:))), std(abs(DATtmpA.Pterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Pterm(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.Pterm(3,:))), std(abs(DATtmpA.Pterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Pterm(3,:)))); - set(s3,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Pterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Pterm),2))+max((std(abs(DATtmpA.Pterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % fterm - h1=subplot('position',posInfo.statsPos2(6,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Fterm(1,:))), std(abs(DATtmpA.Fterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Fterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Fterm(2,:))), std(abs(DATtmpA.Fterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Fterm(2,:)))); - set(s2,'FaceColor',[colorA]);% % - s1=errorbar([3],mean(abs(DATtmpA.Fterm(3,:))), std(abs(DATtmpA.Fterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Fterm(3,:)))); - set(s3,'FaceColor',[colorA]); - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Fterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Fterm),2))+max((std(abs(DATtmpA.Fterm)')'))); - if ymax<1, ymax=10, end - axis([.5 3.5 0 ymax]) - box off - - % Iterm - h1=subplot('position',posInfo.statsPos2(3,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.Iterm(1,:))), std(abs(DATtmpA.Iterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.Iterm(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.Iterm(2,:))), std(abs(DATtmpA.Iterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.Iterm(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.Iterm(3,:))), std(abs(DATtmpA.Iterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.Iterm(3,:)))); - set(s3,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Iterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.Iterm),2))+max((std(abs(DATtmpA.Iterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % dterm - h1=subplot('position',posInfo.statsPos2(7,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.DtermFilt(1,:))), std(abs(DATtmpA.DtermFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.DtermFilt(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.DtermFilt(2,:))), std(abs(DATtmpA.DtermFilt(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.DtermFilt(2,:)))); - set(s2,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Dterm| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max(mean(abs(DATtmpA.DtermFilt),2))+max((std(abs(DATtmpA.DtermFilt)')'))); - axis([.5 2.5 0 ymax]) - box off - - h1=subplot('position',posInfo.statsPos2(4,:)); cla - s1=errorbar([1],mean(DATtmpA.Motor12(1,:)), std(DATtmpA.Motor12(1,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(DATtmpA.Motor12(1,:)));hold on - set(s1,'FaceColor',[colorA]); - s1=errorbar([2],mean(DATtmpA.Motor12(2,:)), std(DATtmpA.Motor12(2,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(DATtmpA.Motor12(2,:))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(DATtmpA.Motor34(1,:)), std(DATtmpA.Motor34(1,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(DATtmpA.Motor34(1,:))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(DATtmpA.Motor34(2,:)), std(DATtmpA.Motor34(2,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(DATtmpA.Motor34(2,:))); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('Motors [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - axis([0.5 4.5 0 100]) - box off - - - h1=subplot('position',posInfo.statsPos2(8,:)); cla - s1=errorbar([1],mean(abs(DATtmpA.debug12(1,:))), std(abs(DATtmpA.debug12(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpA.debug12(1,:))));hold on - set(s1,'FaceColor',[colorA]);% - s1=errorbar([2],mean(abs(DATtmpA.debug12(2,:))), std(abs(DATtmpA.debug12(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpA.debug12(2,:)))); - set(s2,'FaceColor',[colorA]);% - s1=errorbar([3],mean(abs(DATtmpA.debug34(1,:))), std(abs(DATtmpA.debug34(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpA.debug34(1,:)))); - set(s3,'FaceColor',[colorA]);% - s1=errorbar([4],mean(abs(DATtmpA.debug34(2,:))), std(abs(DATtmpA.debug34(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(abs(DATtmpA.debug34(2,:)))); - set(s4,'FaceColor',[colorA]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorA],'ycolor',[colorA],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Debug| [A]','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorA]); - ymax=ceil(max([max(mean(abs(DATtmpA.debug12),2)) max(mean(abs(DATtmpA.debug34),2))]) + max([max((std(abs(DATtmpA.debug12)')')) max((std(abs(DATtmpA.debug34)')'))])); - axis([.5 4.5 0 ymax]) - box off - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - - N=length(DATtmpB.GyroFilt(1,:)); - - % gyro - h1=subplot('position',posInfo.statsPos2(9,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'FaceColor',[colorB]);%[ColorSet(11,:)]) - s1=errorbar([2],mean(abs(DATtmpB.GyroFilt(2,:))), std(abs(DATtmpB.GyroFilt(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.GyroFilt(2,:)))); - set(s2,'FaceColor',[colorB]);%,[ColorSet(12,:)]) - s1=errorbar([3],mean(abs(DATtmpB.GyroFilt(1,:))), std(abs(DATtmpB.GyroFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.GyroFilt(1,:)))); - set(s3,'FaceColor',[colorB]);%,[ColorSet(13,:)]) - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Gyro| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.GyroFilt),2))+max((std(abs(DATtmpB.GyroFilt)')'))); - axis([.5 3.5 0 ymax]) - box off - - % RCRate - h1=subplot('position',posInfo.statsPos2(13,:)); cla - s1=errorbar([1],mean(abs(Rpercent_B)), std(abs(Rpercent_B)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(Rpercent_B)));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(Ppercent_B)), std(abs(Ppercent_B)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(Ppercent_B))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(Ypercent_B)), std(abs(Ypercent_B)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(Ypercent_B))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(abs(Tpercent_B)), std(abs(Tpercent_B)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(Tpercent_B)); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xticklabel',{'R';'P';'Y';'T'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('% RPYT [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean % +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - axis([0.5 4.5 0 100]) - box off - - % pterm - h1=subplot('position',posInfo.statsPos2(10,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Pterm(1,:))), std(abs(DATtmpB.Pterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Pterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Pterm(2,:))), std(abs(DATtmpB.Pterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Pterm(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.Pterm(3,:))), std(abs(DATtmpB.Pterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Pterm(3,:)))); - set(s3,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Pterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Pterm),2))+max((std(abs(DATtmpB.Pterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % fterm - h1=subplot('position',posInfo.statsPos2(14,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Fterm(1,:))), std(abs(DATtmpB.Fterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Fterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Fterm(2,:))), std(abs(DATtmpB.Fterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Fterm(2,:)))); - set(s2,'FaceColor',[colorB]);% % - s1=errorbar([3],mean(abs(DATtmpB.Fterm(3,:))), std(abs(DATtmpB.Fterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Fterm(3,:)))); - set(s3,'FaceColor',[colorB]); - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Fterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Fterm),2))+max((std(abs(DATtmpB.Fterm)')'))); - if ymax<1, ymax=10, end - axis([.5 3.5 0 ymax]) - box off - - % Iterm - h1=subplot('position',posInfo.statsPos2(11,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.Iterm(1,:))), std(abs(DATtmpB.Iterm(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.Iterm(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.Iterm(2,:))), std(abs(DATtmpB.Iterm(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.Iterm(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.Iterm(3,:))), std(abs(DATtmpB.Iterm(3,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.Iterm(3,:)))); - set(s3,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3],'xticklabel',{'R';'P';'Y'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Iterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.Iterm),2))+max((std(abs(DATtmpB.Iterm)')'))); - axis([.5 3.5 0 ymax]) - box off - - % dterm - h1=subplot('position',posInfo.statsPos2(15,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.DtermFilt(1,:))), std(abs(DATtmpB.DtermFilt(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.DtermFilt(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.DtermFilt(2,:))), std(abs(DATtmpB.DtermFilt(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.DtermFilt(2,:)))); - set(s2,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2],'xticklabel',{'R';'P'},'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Dterm| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max(mean(abs(DATtmpB.DtermFilt),2))+max((std(abs(DATtmpB.DtermFilt)')'))); - axis([.5 2.5 0 ymax]) - box off - - h1=subplot('position',posInfo.statsPos2(12,:)); cla - s1=errorbar([1],mean(DATtmpB.Motor12(1,:)), std(DATtmpB.Motor12(1,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(DATtmpB.Motor12(1,:)));hold on - set(s1,'FaceColor',[colorB]); - s1=errorbar([2],mean(DATtmpB.Motor12(2,:)), std(DATtmpB.Motor12(2,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(DATtmpB.Motor12(2,:))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(DATtmpB.Motor34(1,:)), std(DATtmpB.Motor34(1,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(DATtmpB.Motor34(1,:))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(DATtmpB.Motor34(2,:)), std(DATtmpB.Motor34(2,:)));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(DATtmpB.Motor34(2,:))); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('Motors [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - axis([0.5 4.5 0 100]) - box off - - - h1=subplot('position',posInfo.statsPos2(16,:)); cla - s1=errorbar([1],mean(abs(DATtmpB.debug12(1,:))), std(abs(DATtmpB.debug12(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s1=bar([1],mean(abs(DATtmpB.debug12(1,:))));hold on - set(s1,'FaceColor',[colorB]);% - s1=errorbar([2],mean(abs(DATtmpB.debug12(2,:))), std(abs(DATtmpB.debug12(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s2=bar([2],mean(abs(DATtmpB.debug12(2,:)))); - set(s2,'FaceColor',[colorB]);% - s1=errorbar([3],mean(abs(DATtmpB.debug34(1,:))), std(abs(DATtmpB.debug34(1,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s3=bar([3],mean(abs(DATtmpB.debug34(1,:)))); - set(s3,'FaceColor',[colorB]);% - s1=errorbar([4],mean(abs(DATtmpB.debug34(2,:))), std(abs(DATtmpB.debug34(2,:))));hold on - set(s1,'color',th.axesFg,'linewidth',lineThickness) - s4=bar([4],mean(abs(DATtmpB.debug34(2,:)))); - set(s4,'FaceColor',[colorB]);% - set(gca,'Xtick',[1 2 3 4],'xcolor',[colorB],'ycolor',[colorB],'YMinorGrid','on') - set(h1,'fontsize',fontsz5); - xlabel('|Debug| [B]','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ylabel('Mean +SD ','fontsize',fontsz5,'fontweight','bold','color',[colorB]); - ymax=ceil(max([max(mean(abs(DATtmpB.debug12),2)) max(mean(abs(DATtmpB.debug34),2))]) + max([max((std(abs(DATtmpB.debug12)')')) max((std(abs(DATtmpB.debug34)')'))])); - axis([.5 4.5 0 ymax]) - box off - end end -%% Mode 1 topography -if get(guiHandlesStats.crossAxesStats, 'Value')==3 - - cols=[0.06 0.52]; - rows=[0.55 0.08]; - k=0; - for c=1:2 - for r=1:2 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; - end - end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Ypercent_A;Ypercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_B;Ypercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - -end +%% Mean +/- SD +if plotMode == 2 -%% Mode 2 topography -if get(guiHandlesStats.crossAxesStats, 'Value')==4 - - cols=[0.06 0.52]; - rows=[0.55 0.08]; - k=0; - for c=1:2 - for r=1:2 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.38 0.4]; + cols = [0.06 0.30 0.54 0.78]; + rows = [0.76 0.53 0.30 0.08]; + k = 0; + for c = 1:length(cols) + for r = 1:length(rows) + k = k + 1; + posInfo.statsPos2(k,:) = [cols(c) rows(r) 0.18 0.16]; end end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Rpercent_A;Rpercent_A],[Ppercent_A;Ppercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - end - - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Ppercent_B;Ppercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[-100:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 -100 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[-100 100],':k'); - plot([-100 100],[0 0],':k'); - end - -end -%% each against throttle - -if get(guiHandlesStats.crossAxesStats, 'Value')==5 - - cols=[0.06 0.54]; - rows=[0.69 0.385 0.08]; - k=0; - for c=1:2 - for r=1:3 - k=k+1; - posInfo.statsPos2(k,:)=[cols(c) rows(r) 0.39 0.24]; + lineThickness = 2; + + % field groups: {field_prefix, nAxes, xlabel, useAbs} + groups = { + {'gyroADC_', 3, '|Gyro|', true}; + {'axisP_', 3, '|Pterm|', true}; + {'axisI_', 3, '|Iterm|', true}; + {'axisD_', 2, '|Dterm|', true}; + {'setpoint_',3, '% RPYT', true}; + {'axisF_', 3, '|Fterm|', true}; + {'motor_', 4, 'Motors', false}; + {'debug_', 4, '|Debug|', true}; + }; + % subplot indices: col1=[1,2,3,4] col2=[5,6,7,8] col3=[9..] col4=[13..] + % File A: slots 1-8, File B: slots 9-16 + slotA = [1 2 3 7 5 6 4 8]; + slotB = [9 10 11 15 13 14 12 16]; + axLabelsRPY = {'R','P','Y'}; + axLabelsRPYT = {'R','P','Y','T'}; + axLabelsM = {'1','2','3','4'}; + axLabelsD4 = {'0','1','2','3'}; + + for fi = 1:2 + if fi == 1, f = fA; clr = colorA; slots = slotA; tag = '[1]'; + else + if isempty(fB), continue; end + f = fB; clr = colorB; slots = slotB; tag = '[2]'; end - end - - - lineThickness=2; - - if ~isempty(filenameA) - - Rpercent_A=DATtmpA.RCcommand(1,:)/5; - Ppercent_A=DATtmpA.RCcommand(2,:)/5; - Ypercent_A=DATtmpA.RCcommand(3,:)/5; - Tpercent_A=DATtmpA.RCRate(4,:); - Tacceleration_A=[0 (smooth( diff((Tpercent_A)*10)*A_lograte, 50))']; - col=Tacceleration_A; - - cmap_crossaxisStat=b2r(-1, 1); - - h1=subplot('position',posInfo.statsPos2(1,:)); cla - s1=surface([Rpercent_A;Rpercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h1,'fontsize',fontsz5); - xlabel('% Roll [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s1, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h2=subplot('position',posInfo.statsPos2(2,:)); cla - s2=surface([Ppercent_A;Ppercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h2,'fontsize',fontsz5); - xlabel('% Pitch [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s2, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h3=subplot('position',posInfo.statsPos2(3,:)); cla - s3=surface([Ypercent_A;Ypercent_A],[Tpercent_A;Tpercent_A],[Tacceleration_A;Tacceleration_A],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h3,'fontsize',fontsz5); - xlabel('% Yaw [A]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [A]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s3, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - end - - if ~isempty(filenameB) - - Rpercent_B=DATtmpB.RCcommand(1,:)/5; - Ppercent_B=DATtmpB.RCcommand(2,:)/5; - Ypercent_B=DATtmpB.RCcommand(3,:)/5; - Tpercent_B=DATtmpB.RCRate(4,:); - Tacceleration_B=[0 (smooth( diff((Tpercent_B)*10)*B_lograte, 50))']; - col=Tacceleration_B; - - h4=subplot('position',posInfo.statsPos2(4,:)); cla - s4=surface([Rpercent_B;Rpercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h4,'fontsize',fontsz5); - xlabel('% Roll [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s4, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h5=subplot('position',posInfo.statsPos2(5,:)); cla - s5=surface([Ppercent_B;Ppercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h5,'fontsize',fontsz5); - xlabel('% Pitch [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s5, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); - - h6=subplot('position',posInfo.statsPos2(6,:)); cla - s6=surface([Ypercent_B;Ypercent_B],[Tpercent_B;Tpercent_B],[Tacceleration_B;Tacceleration_B],[col;col],'facecol','no','edgecol','interp','linew',lineThickness); - colormap(cmap_crossaxisStat); - set(gca,'CLim',[-zScale zScale],'xtick',[-100:20:100],'ytick',[0:20:100]) - set(h6,'fontsize',fontsz5); - xlabel('% Yaw [B]','fontsize',fontsz5,'fontweight','bold'); - ylabel('% Throttle [B]','fontsize',fontsz5,'fontweight','bold'); - axis([-100 100 0 100]) - set(s6, 'EdgeAlpha',zTransparency); - hold on; plot([0 0],[0 100],':k'); - plot([-100 100],[50 50],':k'); + for g = 1:length(groups) + grp = groups{g}; + prefix = grp{1}; + nAx = grp{2}; + xlbl = grp{3}; + useAbs = grp{4}; + + h1 = subplot('position', posInfo.statsPos2(slots(g),:)); cla; + + vals = zeros(nAx, 1); + sds = zeros(nAx, 1); + for q = 1:nAx + fld = [prefix int2str(q-1) '_']; + if ~isfield(T{f}, fld), continue; end + d = T{f}.(fld)(tIND{f}); + if useAbs, d = abs(d); end + vals(q) = mean(d); + sds(q) = std(d); + end + + % special: setpoint group adds throttle as 4th bar + if strcmp(prefix, 'setpoint_') + nAx = 4; + d = T{f}.setpoint_3_(tIND{f}) / 10; + vals(4) = mean(d); + sds(4) = std(d); + end + + for q = 1:nAx + s = errorbar(q, vals(q), sds(q)); hold on; + set(s, 'color', th.axesFg, 'linewidth', lineThickness); + s = bar(q, vals(q)); + set(s, 'FaceColor', clr); + end + + if nAx == 2 + set(gca, 'Xtick', 1:nAx, 'xticklabel', axLabelsRPY(1:2)); + axis([.5 2.5 0 max(vals+sds)*1.2+1]); + elseif nAx == 3 + set(gca, 'Xtick', 1:nAx, 'xticklabel', axLabelsRPY); + axis([.5 3.5 0 max(vals+sds)*1.2+1]); + elseif nAx == 4 && strcmp(prefix, 'setpoint_') + set(gca, 'Xtick', 1:4, 'xticklabel', axLabelsRPYT); + axis([.5 4.5 0 100]); + elseif nAx == 4 && strcmp(prefix, 'motor_') + set(gca, 'Xtick', 1:4, 'xticklabel', axLabelsM); + axis([.5 4.5 0 100]); + elseif nAx == 4 + set(gca, 'Xtick', 1:4, 'xticklabel', axLabelsD4); + ymax = max(vals+sds)*1.2+1; if ymax < 1, ymax = 10; end + axis([.5 4.5 0 ymax]); + end + + set(gca, 'xcolor', clr, 'ycolor', clr, 'YMinorGrid', 'on'); + set(h1, 'fontsize', fontsz5); + xlabel([xlbl ' ' tag], 'fontsize', fontsz5, 'fontweight', 'bold', 'color', clr); + ylabel('Mean +SD', 'fontsize', fontsz5, 'fontweight', 'bold', 'color', clr); + box off; + end end end +%% Topography and Axes x Throttle (modes 3-5) +if plotMode >= 3 + ax_msg = axes('Parent', PSstatsfig, 'Position', [.1 .3 .8 .4]); + set(ax_msg, 'Visible', 'off'); + text(0.5, 0.5, {'Topography and Axes x Throttle modes', 'not yet ported to new data model.'}, ... + 'HorizontalAlignment', 'center', 'FontSize', fontsz, 'FontWeight', 'bold', ... + 'Color', th.textPrimary, 'Parent', ax_msg, 'Units', 'normalized'); +end -updateStats=0; allax = findobj(PSstatsfig, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end -set(PSstatsfig, 'pointer', 'arrow') -end - - -function newmap = b2r(cmin_input,cmax_input) -%BLUEWHITERED Blue, white, and red color map. -% this matlab file is designed to draw anomaly figures. the color of -% the colorbar is from blue to white and then to red, corresponding to -% the anomaly values from negative to zero to positive, respectively. -% The color white always correspondes to value zero. -% -% You should input two values like caxis in matlab, that is the min and -% the max value of color values designed. e.g. colormap(b2r(-3,5)) -% -% the brightness of blue and red will change according to your setting, -% so that the brightness of the color corresponded to the color of his -% opposite number -% e.g. colormap(b2r(-3,6)) is from light blue to deep red -% e.g. colormap(b2r(-3,3)) is from deep blue to deep red -% -% I'd advise you to use colorbar first to make sure the caxis' cmax and cmin. -% Besides, there is also another similar colorbar named 'darkb2r', in which the -% color is darker. -% -% by Cunjie Zhang, 2011-3-14 -% find bugs ====> email : daisy19880411@126.com -% updated: Robert Beckman help to fix the bug when start point is zero, 2015-04-08 -% -% Examples: -% ------------------------------ -% figure -% peaks; -% colormap(b2r(-6,8)), colorbar, title('b2r') -% - - -%% check the input -if nargin ~= 2 ; - disp('input error'); - disp('input two variables, the range of caxis , for example : colormap(b2r(-3,3))'); -end - -if cmin_input >= cmax_input - disp('input error'); - disp('the color range must be from a smaller one to a larger one'); -end - -%% control the figure caxis -lims = get(gca, 'CLim'); % get figure caxis formation -caxis([cmin_input cmax_input]); - -%% color configuration : from blue to to white then to red - -red_top = [1 0 0]; -white_middle= [1 1 1]; -blue_bottom = [0 0 1]; - -%% color interpolation - -color_num = 251; -color_input = [blue_bottom; white_middle; red_top]; -oldsteps = linspace(-1, 1, size(color_input,1)); -newsteps = linspace(-1, 1, color_num); - -%% Category Discussion according to the cmin and cmax input - -% the color data will be remaped to color range from -max(abs(cmin_input),cmax_input) -% to max(abs(cmin_input),cmax_input) , and then squeeze the color data -% in order to make sure the blue and red color selected corresponded -% to their math values +set(PSstatsfig, 'pointer', 'arrow'); -% for example : -% if b2r(-3,6) ,the color range is from light blue to deep red , so that -% the light blue valued at -3 correspondes to light red valued at 3 - - -%% Category Discussion according to the cmin and cmax input -% first : from negative to positive -% then : from positive to positive -% last : from negative to negative - -for j=1:3 - newmap_all(:,j) = min(max(transpose(interp1(oldsteps, color_input(:,j), newsteps)), 0), 1); +catch err + msgPSplotStats = PSerrorMessages('PSplotStats', err); end - -if (cmin_input < 0) && (cmax_input > 0) ; - - - if abs(cmin_input) < cmax_input - - % |--------|---------|--------------------| - % -cmax cmin 0 cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - - elseif abs(cmin_input) >= cmax_input - - % |------------------|------|--------------| - % cmin 0 cmax -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); - end - - -elseif cmin_input >= 0 - - if lims(1) < 0 - disp('caution:') - disp('there are still values smaller than 0, but cmin is larger than 0.') - disp('some area will be in red color while it should be in blue color') - end - - % |-----------------|-------|-------------| - % -cmax 0 cmin cmax [cmin,cmax] - - start_point = max(round((cmin_input+cmax_input)/2/cmax_input*color_num),1); - newmap = squeeze(newmap_all(start_point:color_num,:)); - -elseif cmax_input <= 0 - - if lims(2) > 0 - disp('caution:') - disp('there are still values larger than 0, but cmax is smaller than 0.') - disp('some area will be in blue color while it should be in red color') - end - - % |------------|------|--------------------| - % cmin cmax 0 -cmin [cmin,cmax] - - end_point = max(round((cmax_input-cmin_input)/2/abs(cmin_input)*color_num),1); - newmap = squeeze(newmap_all(1:end_point,:)); -end - - - -end - - diff --git a/src/plot/PSrunChirpAnalysis.m b/src/plot/PSrunChirpAnalysis.m index 1b88f54..c260025 100644 --- a/src/plot/PSrunChirpAnalysis.m +++ b/src/plot/PSrunChirpAnalysis.m @@ -13,7 +13,7 @@ function PSrunChirpAnalysis(T, setupInfo, debugIdx, Fs, tIND, axisIdx) % check CHIRP debug mode if ~isfield(T, 'debug_0_') - warndlg('No debug data in log — chirp analysis requires debug_mode = CHIRP'); + warndlg('No debug data in log - chirp analysis requires debug_mode = CHIRP'); return end diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index e957438..e28719a 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -10,15 +10,6 @@ PStunefig=figure(4); th = PStheme(); -PStunefig_pos = get(PStunefig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PStunefig_pos(3) > 10, PStunefig_pos(3:4) = PStunefig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PStunefig_pos(3) PStunefig_pos(4)])); -fontsz=(screensz_multiplier*prop_max_screen); - -f = fields(guiHandlesTune); -for i = 1 : size(f,1) - try set(guiHandlesTune.(f{i}), 'FontSize', fontsz); catch, end -end %% step resp computed directly from set point and gyro diff --git a/src/ui/PSdispSetupInfoUIcontrol.m b/src/ui/PSdispSetupInfoUIcontrol.m index c477dbe..1a70cfb 100644 --- a/src/ui/PSdispSetupInfoUIcontrol.m +++ b/src/ui/PSdispSetupInfoUIcontrol.m @@ -10,20 +10,25 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) -PSdisp=figure(5); -screensz = get(0,'ScreenSize'); -set(PSdisp, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSdisp, 'NumberTitle', 'on'); -set(PSdisp, 'Name', ['PIDscope (' PsVersion ') - Setup Info']); -set(PSdisp,'color',bgcolor) +if exist('PSdisp','var') && ishandle(PSdisp) + figure(PSdisp); +else + PSdisp=figure(5); + screensz = get(0,'ScreenSize'); + set(PSdisp, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSdisp, 'NumberTitle', 'on'); + set(PSdisp, 'Name', ['PIDscope (' PsVersion ') - Setup Info']); + set(PSdisp,'color',bgcolor); +end -columnWidth=55*round(screensz_multiplier*prop_max_screen); +columnWidth = 55 * fontsz; TooltipString_FileNumDispA=['List of files available. Click to view setup info for each']; posInfo.FileNumDispA=[.22 .95 .1 .04]; posInfo.FileNumDispB=[.72 .95 .1 .04]; posInfo.checkboxDIFF=[.04 .96 .1 .04]; +if ~exist('setupInfoWidgets_init','var') || ~ishandle(guiHandlesInfo.FileNumDispA) guiHandlesInfo.FileNumDispA = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... 'fontsize',fontsz, 'units','normalized','Position', [posInfo.FileNumDispA],'callback','@selection; PSdispSetupInfo;'); set(guiHandlesInfo.FileNumDispA, 'Value', 1); @@ -35,6 +40,8 @@ guiHandlesInfo.checkboxDIFF =uicontrol(PSdisp,'Style','checkbox','String','Show Differences Only','fontsize',fontsz,'TooltipString', [''],... 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxDIFF],'callback', 'PSdispSetupInfo;'); +setupInfoWidgets_init = true; +end % ishandle widgets else warndlg('Please select file(s)'); diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index bb5ddb8..129b0cd 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -8,19 +8,20 @@ % ---------------------------------------------------------------------------------- -if ~isempty(filenameA) || ~isempty(filenameB) - -PSerrfig=figure(3); -set(PSerrfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSerrfig, 'NumberTitle', 'off'); -set(PSerrfig, 'Name', ['PIDscope (' PsVersion ') - PID Error Tool']); -set(PSerrfig, 'InvertHardcopy', 'off'); -set(PSerrfig,'color',bgcolor) +if exist('fnameMaster','var') && ~isempty(fnameMaster) -PSerrfig_pos = get(PSerrfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSerrfig_pos(3) > 10, PSerrfig_pos(3:4) = PSerrfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSerrfig_pos(3) PSerrfig_pos(4)])); -fontsz3=round(screensz_multiplier*prop_max_screen); +if exist('PSerrfig','var') && ishandle(PSerrfig) + figure(PSerrfig); +else + PSerrfig=figure(7); + set(PSerrfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSerrfig, 'NumberTitle', 'off'); + set(PSerrfig, 'Name', ['PIDscope (' PsVersion ') - PID Error Tool']); + set(PSerrfig, 'InvertHardcopy', 'off'); + set(PSerrfig,'color',bgcolor); +end + +fontsz3 = fontsz; maxDegsec=100; updateErr=0; @@ -42,28 +43,31 @@ posInfo.refresh2=[.09 .94 .06 .04]; posInfo.saveFig3=[.16 .94 .06 .04]; -posInfo.maxSticktext=[.22 .966 .12 .03]; -posInfo.maxStick=[.24 .94 .06 .03]; +posInfo.maxSticktext=[.23 .966 .12 .03]; +posInfo.maxStick=[.26 .94 .06 .03]; +if ~exist('errCrtlpanel','var') || ~ishandle(errCrtlpanel) errCrtlpanel = uipanel('Title','','FontSize',fontsz3,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[.085 .93 .23 .06]); - -guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh2],... + 'Position',[.085 .93 .28 .06]); + +guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString','Refresh plots','units','normalized','Position',[posInfo.refresh2],... 'callback','updateErr=1;PSplotPIDerror;'); -set(guiHandlesPIDerr.refresh, 'BackgroundColor', [1 1 .2]); +set(guiHandlesPIDerr.refresh, 'ForegroundColor', colRun); guiHandlesPIDerr.maxSticktext = uicontrol(PSerrfig,'style','text','string','max stick deg/s','fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxSticktext]); guiHandlesPIDerr.maxStick = uicontrol(PSerrfig,'style','edit','string',[int2str(maxDegsec)],'fontsize',fontsz3,'TooltipString',[TooltipString_degsec],'units','normalized','Position',[posInfo.maxStick],... - 'callback','@textinput_call; maxDegsec=str2num(get(guiHandlesPIDerr.maxStick, ''String'')); updateErr=1;PSplotPIDerror; '); + 'callback','maxDegsec=str2double(get(guiHandlesPIDerr.maxStick, ''String'')); updateErr=1;PSplotPIDerror; '); guiHandlesPIDerr.saveFig3 = uicontrol(PSerrfig,'string','Save Fig','fontsize',fontsz3,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig3],... - 'callback','set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesPIDerr.saveFig3, ''FontWeight'', ''normal'');'); -set(guiHandlesPIDerr.saveFig3, 'BackgroundColor', [.8 .8 .8]); - + 'callback','PSsaveFig;'); +set(guiHandlesPIDerr.saveFig3, 'ForegroundColor', saveCol); +end % ishandle(errCrtlpanel) + +PSstyleControls(PSerrfig); + else errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); pause(2); end -PSstyleControls(PSerrfig); diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index ea3c59a..1d4e2c7 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -62,12 +62,16 @@ posInfo.sub100HzfreqTime = [cpL+.003 .564 cpW-.006 .025]; posInfo.playerBtn3 = [cpL+.003 .530 cpW-.006 rh]; -PSspecfig3=figure(31); -set(PSspecfig3, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSspecfig3, 'NumberTitle', 'off'); -set(PSspecfig3, 'Name', ['PIDscope (' PsVersion ') - Frequency x Time Spectrogram']); -set(PSspecfig3, 'InvertHardcopy', 'off'); -set(PSspecfig3,'color',bgcolor); +if exist('PSspecfig3','var') && ishandle(PSspecfig3) + figure(PSspecfig3); +else + PSspecfig3=figure(31); + set(PSspecfig3, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig3, 'NumberTitle', 'off'); + set(PSspecfig3, 'Name', ['PIDscope (' PsVersion ') - Frequency x Time Spectrogram']); + set(PSspecfig3, 'InvertHardcopy', 'off'); + set(PSspecfig3,'color',bgcolor); +end try % datacursormode not available in Octave @@ -75,11 +79,12 @@ set(dcm_obj2,'UpdateFcn',@PSdatatip); end +if ~exist('Spec3Crtlpanel','var') || ~ishandle(Spec3Crtlpanel) Spec3Crtlpanel = uipanel('Title','select file ','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... 'Position',[cpL .515 cpW .405]); - + guiHandlesSpec3.computeSpec = uicontrol(PSspecfig3,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec3],... 'callback','updateSpec = 0; clear specMat; PSfreqTime;'); set(guiHandlesSpec3.computeSpec, 'ForegroundColor', colRun); @@ -133,6 +138,7 @@ 'PSdynSpecPlayer(specMat,Tm,F,{''Roll'',''Pitch'',''Yaw''},tmpSA3{get(guiHandlesSpec3.SpecList,''Value'')});' ... 'else,warndlg(''Run spectrogram first''),end']); set(guiHandlesSpec3.playerBtn, 'ForegroundColor', [0 .4 .8]); +end % ishandle(Spec3Crtlpanel) try set(guiHandlesSpec3.SpecList, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-Preset')))), catch, set(guiHandlesSpec3.SpecList, 'Value', 1), end try set(guiHandlesSpec3.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-FreqSmoothing')))), catch, set(guiHandlesSpec3.smoothFactor_select, 'Value', 2), end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 08e3028..df91497 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -104,12 +104,16 @@ climScale1=[0 ; -50 ]; climScale2=[0.5 ; 20]; -PSspecfig2=figure(3); -set(PSspecfig2, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSspecfig2, 'NumberTitle', 'off'); -set(PSspecfig2, 'Name', ['PIDscope (' PsVersion ') - Spectral Analyzer']); -set(PSspecfig2, 'InvertHardcopy', 'off'); -set(PSspecfig2,'color',bgcolor); +if exist('PSspecfig2','var') && ishandle(PSspecfig2) + figure(PSspecfig2); +else + PSspecfig2=figure(3); + set(PSspecfig2, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig2, 'NumberTitle', 'off'); + set(PSspecfig2, 'Name', ['PIDscope (' PsVersion ') - Spectral Analyzer']); + set(PSspecfig2, 'InvertHardcopy', 'off'); + set(PSspecfig2,'color',bgcolor); +end try % datacursormode not available in Octave @@ -121,11 +125,12 @@ if exist('isOctave','var') && isOctave spec2CrtlpanelPos = [cpL .16+vPosSpec2d cpW .76]; end +if ~exist('spec2Crtlpanel','var') || ~ishandle(spec2Crtlpanel) spec2Crtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... 'Position',spec2CrtlpanelPos); - + guiHandlesSpec2.computeSpec = uicontrol(PSspecfig2,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec],... 'callback','PSplotSpec2D;'); set(guiHandlesSpec2.computeSpec, 'ForegroundColor', colRun); @@ -221,7 +226,7 @@ guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); - +end % ishandle(spec2Crtlpanel) try set(guiHandlesSpec2.SpecList, 'Value', [defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term1'))) defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term2')))]), catch, set(guiHandlesSpec2.SpecList, 'Value', [1 2]), end try set(guiHandlesSpec2.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-smoothing')))), catch, set(guiHandlesSpec2.smoothFactor_select, 'Value', 3), end diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index d395eca..8d0fbbb 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -138,12 +138,16 @@ Flim1=20; % 3.3333Hz steps Flim2=60; -PSspecfig=figure(2); -set(PSspecfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSspecfig, 'NumberTitle', 'off'); -set(PSspecfig, 'Name', ['PIDscope (' PsVersion ') - Frequency x Throttle Spectrogram']); -set(PSspecfig, 'InvertHardcopy', 'off'); -set(PSspecfig,'color',bgcolor); +if exist('PSspecfig','var') && ishandle(PSspecfig) + figure(PSspecfig); +else + PSspecfig=figure(2); + set(PSspecfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig, 'NumberTitle', 'off'); + set(PSspecfig, 'Name', ['PIDscope (' PsVersion ') - Frequency x Throttle Spectrogram']); + set(PSspecfig, 'InvertHardcopy', 'off'); + set(PSspecfig,'color',bgcolor); +end try % datacursormode not available in Octave @@ -155,11 +159,12 @@ if exist('isOctave','var') && isOctave specCrtlpanelPos = [cpL .55 cpW .37]; end +if ~exist('specCrtlpanel','var') || ~ishandle(specCrtlpanel) specCrtlpanel = uipanel('Title','Params','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... 'Position',specCrtlpanelPos); - + %%% PRESET CONFIGURATIONS % guiHandles.FileNum = uicontrol(PSspecfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... @@ -255,6 +260,7 @@ guiHandlesSpec.ColormapSelect = uicontrol(PSspecfig,'Style','popupmenu','string',{'viridis','jet','hot','cool','gray','bone','copper','linear-RED','linear-GREY'},... 'fontsize',fontsz,'TooltipString', [TooltipString_cmap], 'units','normalized','Position',[posInfo.ColormapSelect],'callback','@selection2;updateSpec=1; PSplotSpec;'); set(guiHandlesSpec.ColormapSelect, 'Value', 3);% jet 2 hot 3 viridis 8 +end % ishandle(specCrtlpanel) try set(guiHandlesSpec.SpecSelect{1}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column1')))), catch, set(guiHandlesSpec.SpecSelect{1}, 'Value', 3); end try set(guiHandlesSpec.SpecSelect{2}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column2')))), catch, set(guiHandlesSpec.SpecSelect{2}, 'Value', 2); end diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index 7ba7630..71a8d47 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -7,14 +7,18 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- -if ~isempty(filenameA) || ~isempty(filenameB) +if exist('fnameMaster','var') && ~isempty(fnameMaster) -PSstatsfig=figure(6); -set(PSstatsfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PSstatsfig, 'NumberTitle', 'off'); -set(PSstatsfig, 'Name', ['PIDscope (' PsVersion ') - Flight stats']); -set(PSstatsfig, 'InvertHardcopy', 'off'); -set(PSstatsfig,'color',bgcolor) +if exist('PSstatsfig','var') && ishandle(PSstatsfig) + figure(PSstatsfig); +else + PSstatsfig=figure(6); + set(PSstatsfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSstatsfig, 'NumberTitle', 'off'); + set(PSstatsfig, 'Name', ['PIDscope (' PsVersion ') - Flight stats']); + set(PSstatsfig, 'InvertHardcopy', 'off'); + set(PSstatsfig,'color',bgcolor); +end TooltipString_degsecStick=['Plots rate curve (Histograms Figs) in terms of degs per sec per stick-travel units, or how fast one''s rates change across stick travel ']; TooltipString_crossAxesStats=['Selects from several plotting options, from basic histograms of stick use per flight, to means',... @@ -49,10 +53,7 @@ zScale=1; zTransparency=1; -PSstatsfig_pos = get(PSstatsfig, 'Position'); -screensz_tmp = get(0,'ScreenSize'); if PSstatsfig_pos(3) > 10, PSstatsfig_pos(3:4) = PSstatsfig_pos(3:4) ./ screensz_tmp(3:4); end -prop_max_screen=(max([PSstatsfig_pos(3) PSstatsfig_pos(4)])); -fontsz5=round(screensz_multiplier*prop_max_screen); +fontsz5 = fontsz; clear posInfo.statsPos cols=[0.06 0.54]; @@ -75,50 +76,38 @@ posInfo.crossAxesStats_text2 = [.42 .965 .03 .03]; posInfo.crossAxesStats_input2 = [.42 .945 .03 .03]; +if ~exist('statsCrtlpanel','var') || ~ishandle(statsCrtlpanel) statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... 'Position',[.06 .935 .40 .06]); guiHandlesStats.saveFig5 = uicontrol(PSstatsfig,'string','Save Fig','fontsize',fontsz5,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig5],... - 'callback','set(guiHandlesStats.saveFig5, ''FontWeight'', ''bold'');PSsaveFig; set(guiHandlesStats.saveFig5, ''FontWeight'', ''normal'');'); -set(guiHandlesStats.saveFig5, 'BackgroundColor', [.8 .8 .8]); + 'callback','PSsaveFig;'); +set(guiHandlesStats.saveFig5, 'ForegroundColor', saveCol); -guiHandlesStats.refresh = uicontrol(PSstatsfig,'string','Refresh','fontsize',fontsz5,'TooltipString',[TooltipString_refresh],'units','normalized','Position',[posInfo.refresh3],... +guiHandlesStats.refresh = uicontrol(PSstatsfig,'string','Refresh','fontsize',fontsz5,'TooltipString','Refresh plots','units','normalized','Position',[posInfo.refresh3],... 'callback','updateStats=1;PSplotStats;'); -set(guiHandlesStats.refresh, 'BackgroundColor', [1 1 .2]); +set(guiHandlesStats.refresh, 'ForegroundColor', colRun); guiHandlesStats.degsecStick =uicontrol(PSstatsfig,'Style','checkbox','String','rate of change','fontsize',fontsz5,'TooltipString',[TooltipString_degsecStick],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.degsecStick],'callback','if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.degsecStick],'callback','PSplotStats;'); guiHandlesStats.crossAxesStats =uicontrol(PSstatsfig,'Style','popupmenu','String',{'Histograms'; 'Mean & Standard Deviation'; 'Mode 1 topography'; 'Mode 2 topography'; 'Axes X Throttle'},'fontsize',fontsz5,'TooltipString',[TooltipString_crossAxesStats],... - 'units','normalized','BackgroundColor',[1 1 1 ],'Position',[posInfo.crossAxesStats],'callback','@selection; if (~isempty(filenameA) | ~isempty(filenameB)), end; PSplotStats;'); + 'units','normalized','BackgroundColor',[1 1 1 ],'Position',[posInfo.crossAxesStats],'callback','PSplotStats;'); %guiHandlesStats.crossAxesStats.Value=0; guiHandlesStats.crossAxesStats_text = uicontrol(PSstatsfig,'style','text','string','scale','fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text]); guiHandlesStats.crossAxesStats_input = uicontrol(PSstatsfig,'style','edit','string',[num2str(zScale)],'fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','Position',[posInfo.crossAxesStats_input],... - 'callback','@textinput_call4; zScale=str2num(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); + 'callback','zScale=str2num(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); guiHandlesStats.crossAxesStats_text2 = uicontrol(PSstatsfig,'style','text','string','alpha','fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text2]); guiHandlesStats.crossAxesStats_input2 = uicontrol(PSstatsfig,'style','edit','string',[num2str(zTransparency)],'fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','Position',[posInfo.crossAxesStats_input2],... - 'callback','@textinput_call4; zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); + 'callback','zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); +end % ishandle(statsCrtlpanel) + +PSstyleControls(PSstatsfig); else errordlg('Please select file(s) then click ''load+run''', 'Error, no data'); pause(2); -end -PSstyleControls(PSstatsfig); - -function textinput_call4(src,eventdata) -str=get(src,'String'); - if isempty(str2num(str)) - set(src,'string','0'); - warndlg('Input must be numerical'); - end -end - -function selection(src,event) - val = c.Value; - str = c.String; - str{val}; - % disp(['Selection: ' str{val}]); end \ No newline at end of file diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 95ee36c..c28bfa8 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -9,12 +9,16 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) -PStunefig=figure(4); -set(PStunefig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); -set(PStunefig, 'NumberTitle', 'on'); -set(PStunefig, 'Name', ['PIDscope (' PsVersion ') - Step Response Tool']); -set(PStunefig, 'InvertHardcopy', 'off'); -set(PStunefig,'color',bgcolor) +if exist('PStunefig','var') && ishandle(PStunefig) + figure(PStunefig); +else + PStunefig=figure(4); + set(PStunefig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PStunefig, 'NumberTitle', 'on'); + set(PStunefig, 'Name', ['PIDscope (' PsVersion ') - Step Response Tool']); + set(PStunefig, 'InvertHardcopy', 'off'); + set(PStunefig,'color',bgcolor); +end updateStep=0; @@ -66,11 +70,12 @@ posInfo.maxYStepInput= [cpL+.005 .421 cpW/3 .025]; posInfo.maxYStepTxt= [cpL+cpW/3+.005 .421 cpW/2 .025]; +if ~exist('tuneCrtlpanel_init','var') || ~ishandle(guiHandlesTune.tuneCrtlpanel) guiHandlesTune.tuneCrtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... 'Position',[cpL .41 cpW .51]); - + guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... 'callback','PStuningParams;'); set(guiHandlesTune.run4, 'ForegroundColor', colRun); @@ -117,6 +122,8 @@ guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothing off' 'smoothing low' 'smoothing medium' 'smoothing high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... 'callback','@selection2;'); set(guiHandlesTune.smoothFactor_select, 'Value', 1); +tuneCrtlpanel_init = true; +end % ishandle(tuneCrtlpanel) try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end try set(guiHandlesTune.plotP, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotP')))), catch, set(guiHandlesTune.plotP, 'Value', 1), end diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index c398573..82926a6 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -44,10 +44,11 @@ fullszPlot = [0.095 0.255 0.77 0.63]; +if ~exist('checkpanel','var') || ~ishandle(checkpanel) checkpanel = uipanel('Title','','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[.096 .932 .68 .065]); + 'Position',[.096 .932 .68 .065]); guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox0],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); @@ -94,8 +95,23 @@ TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; guiHandles.FileNum = uicontrol(PSfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameAText],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); +maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; +guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); +guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... + 'callback','PSplotLogViewer; '); + +guiHandles.nCols_text = uicontrol(PSfig,'style','text','string','N colors','fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.nCols_text]); +guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... + 'callback','if str2num(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2num(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2num(get(guiHandles.nCols_input, ''String''))); '); + +subplot('position',[posInfo.YTstick]); +set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); +box on +subplot('position',[posInfo.RPstick]) +set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); +box on +end % ishandle(checkpanel) - fileIdx = get(guiHandles.FileNum, 'Value'); if exist('tta','var') && iscell(tta) && numel(tta) >= fileIdx if numel(epoch1_A) < fileIdx || numel(epoch2_A) < fileIdx @@ -109,22 +125,6 @@ tIND{f} = tta{f} > (epoch1_A(f)*us2sec) & tta{f} < (epoch2_A(f)*us2sec); end -maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; -guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); -guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... - 'callback','PSplotLogViewer; '); - -guiHandles.nCols_text = uicontrol(PSfig,'style','text','string','N colors','fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.nCols_text]); -guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... - 'callback','if str2num(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2num(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2num(get(guiHandles.nCols_input, ''String''))); '); - -subplot('position',[posInfo.YTstick]); -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on -subplot('position',[posInfo.RPstick]) -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on - try set(guiHandles.maxY_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ymax'))))), catch, end try set(guiHandles.nCols_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ncolors'))))), catch, end PSstyleControls(PSfig); diff --git a/src/util/PSstyleAxes.m b/src/util/PSstyleAxes.m index 4e31fbc..5b3c25b 100644 --- a/src/util/PSstyleAxes.m +++ b/src/util/PSstyleAxes.m @@ -2,7 +2,7 @@ function PSstyleAxes(ax, th) %% PSstyleAxes - apply dark theme to axes if nargin < 2, th = PStheme(); end if ~ishandle(ax), return; end -% skip colorbars — findobj('Type','axes') returns them in Octave +% skip colorbars - findobj('Type','axes') returns them in Octave try tag = get(ax, 'Tag'); if strcmpi(tag, 'colorbar') || strcmpi(tag, 'Colorbar'), return; end; catch, end set(ax, 'Color', th.axesBg, 'XColor', th.axesFg, 'YColor', th.axesFg, ... 'GridColor', th.gridColor, 'FontWeight', 'bold'); diff --git a/src/util/PStheme.m b/src/util/PStheme.m index 6ec9ff8..deb0762 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -37,6 +37,10 @@ th.btnDash5 = [.20 .80 .80]; % Chirp Analysis th.btnLink = [.85 .55 .15]; % Support PIDscope +% font size - single source of truth +screensz = get(0, 'ScreenSize'); +th.fontsz = round(screensz(4) * .011); + % checkbox / input bg th.checkBg = [.18 .18 .20]; th.inputBg = [.14 .14 .16]; From 1456afff2fcfbab2b987e52e03010562ed68c3a2 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 14:25:37 +0100 Subject: [PATCH 08/80] fix(ui): pixel-based CP resize, remove duplicate widgets, fix topbar padding --- PIDscope.m | 141 ++++++++++++++----------- src/core/PSload.m | 13 ++- src/core/PSthrSpec.m | 9 +- src/plot/PSdispSetupInfo.m | 62 ++++++----- src/plot/PSdynSpecPlayer.m | 3 +- src/plot/PSfilterSim.m | 3 +- src/plot/PSplotBode.m | 3 +- src/plot/PSplotLogViewer.m | 27 +++-- src/plot/PSplotMotorNoise.m | 3 +- src/plot/PSplotPIDerror.m | 8 +- src/plot/PSplotSpec.m | 28 ++--- src/plot/PSplotSpec2D.m | 9 +- src/plot/PSplotStats.m | 4 +- src/ui/PSdispSetupInfoUIcontrol.m | 25 ++++- src/ui/PSerrUIcontrol.m | 35 +++++-- src/ui/PSfreqTimeUIcontrol.m | 69 +++++++----- src/ui/PSspec2DUIcontrol.m | 134 +++++++++++------------ src/ui/PSspecUIcontrol.m | 169 +++++++++++++++--------------- src/ui/PSstatsUIcontrol.m | 49 ++++++--- src/ui/PStuneUIcontrol.m | 61 +++++++---- src/ui/PSviewerUIcontrol.m | 79 ++++++++------ src/util/PSregisterResize.m | 14 +++ src/util/PSresetData.m | 40 +++++++ src/util/PSresizeCP.m | 153 +++++++++++++++++++++++++++ src/util/PSsaveSettings.m | 8 +- src/util/PSstyleControls.m | 5 +- src/util/PStheme.m | 14 ++- 27 files changed, 765 insertions(+), 403 deletions(-) create mode 100644 src/util/PSregisterResize.m create mode 100644 src/util/PSresetData.m create mode 100644 src/util/PSresizeCP.m diff --git a/PIDscope.m b/PIDscope.m index ae704a6..96a62fe 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -140,72 +140,77 @@ %use_phsCorrErr=0; flightSpec=0; screensz = get(0,'ScreenSize'); -screensz(3) = round(1.78 * screensz(4)); % force 16:9 - - % Octave Qt bug: setting figure units to 'normalized' permanently breaks uipanel % Calculate pixel position manually instead -figPos = round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)]); -set(PSfig, 'Position', figPos); +set(PSfig, 'Position', round([0 0 screensz(3) screensz(4)])); +try set(PSfig, 'WindowState', 'maximized'); catch, end set(PSfig, 'NumberTitle', 'off'); set(PSfig, 'Name', ['PIDscope (' PsVersion ') - Log Viewer']); - -pause(.1)% need to wait for figure to open before extracting screen values +drawnow; pause(0.2); +% Use ACTUAL figure size (accounts for dock/panel/taskbar) +figPos = get(PSfig, 'Position'); +screensz(3) = figPos(3); screensz(4) = figPos(4); th = PStheme(); fontsz = th.fontsz; screensz_multiplier = screensz(4) * .011; % Octave font scaling is done below in layout section markerSz = round(screensz_multiplier * 0.75); -vPos = 0.92; -cpL = .875; % control panel left edge -cpW = .12; % control panel width -rs = 0.025; % row step (vertical spacing between elements) -rh = 0.026; % row height + +% CP dimensions — all derived from pixel sizes, then normalized +cpW_px = 200; rh_px = 22; rs_px = 24; cpM_px = 5; cpTitle_px = 28; +ddh_px = 28; % dropdown height (taller than button) +cbW_px = 40; % checkbox column width +rhs_px = 16; % small text row height +infoH_px = 100; % info table height if isOctave - fontsz = fontsz * 0.85; - rs = 0.034; rh = 0.030; + rh_px = 26; rs_px = 30; ddh_px = 32; rhs_px = 18; end +cpW = cpW_px / screensz(3); +cpL = 1 - cpW - cpM_px/screensz(3); +rh = rh_px / screensz(4); +rs = rs_px / screensz(4); +cpM = cpM_px / screensz(3); % horizontal margin +cpMv = cpM_px / screensz(4); % vertical margin +cpTitleH = cpTitle_px / screensz(4); +cbW = cbW_px / screensz(3); +ddh = ddh_px / screensz(4); +rhs = rhs_px / screensz(4); +tbOff = 40 / screensz(4); % figure toolbar offset +vPos = 1 - tbOff - cpTitleH - cpMv; % top of first row (below toolbar + title bar) set(0,'defaultUicontrolFontSize', fontsz) set(0,'defaultUicontrolForegroundColor', th.textPrimary) set(0,'defaultUicontrolBackgroundColor', th.panelBg) row = 1; -posInfo.firmware =[cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; -posInfo.fileA=[cpL+.006 vPos-rs*row cpW/2-.006 rh]; -posInfo.clr=[cpL+cpW/2 vPos-rs*row cpW/2-.006 rh]; row=row+1; -posInfo.fnameAText = [cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; -posInfo.startEndButton=[cpL+.005 vPos-rs*row cpW/2-.005 rh]; -posInfo.RPYcomboLV = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; -LogStDefault = 2;% default ignore first 2 seconds of logfile -LogNdDefault = 1;% default ignore last 1 second of logfile -posInfo.plotR_LV = [cpL+.005 vPos-rs*row .035 rh]; -posInfo.plotP_LV = [cpL+.04 vPos-rs*row .035 rh]; -posInfo.plotY_LV = [cpL+.075 vPos-rs*row .035 rh]; row=row+1; -posInfo.lineSmooth = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; -posInfo.linewidth = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; -posInfo.spectrogramButton = [cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; -posInfo.TuningButton = [cpL+.003 vPos-rs*row cpW-.006 rh]; row=row+1; -posInfo.PIDErrorButton = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; -posInfo.FlightStatsButton = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; -posInfo.period2Hz = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; -posInfo.DispInfoButton = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; -posInfo.saveFig = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; -posInfo.saveSettings = [cpL+cpW/2 vPos-rs*row cpW/2-.003 rh]; row=row+1; -%posInfo.wiki = [cpL+.003 vPos-rs*row cpW/2-.003 rh]; -posInfo.PIDtuningService = [cpL+.003 vPos-rs*row cpW-.006 rh]; -cpH = rs*row + 0.04; % control panel height = rows + title margin +posInfo.firmware = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.fileA= [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.clr= [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +posInfo.fnameAText = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.startEndButton= [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.RPYcomboLV = [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +LogStDefault = 2; +LogNdDefault = 1; +posInfo.plotR_LV = [cpL+cpM vPos-rs*row cbW rh]; +posInfo.plotP_LV = [cpL+cpM+cbW vPos-rs*row cbW rh]; +posInfo.plotY_LV = [cpL+cpM+2*cbW vPos-rs*row cbW rh]; row=row+1; +posInfo.lineSmooth = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.linewidth = [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +posInfo.spectrogramButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.TuningButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.PIDErrorButton = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.FlightStatsButton=[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +posInfo.period2Hz = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.DispInfoButton =[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +posInfo.saveFig = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.saveSettings = [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; +posInfo.PIDtuningService = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; +cpH = rs*row + cpTitleH + cpMv; % small padding below last button controlpanel = uipanel('Title','Control Panel','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[cpL vPos-cpH+0.02 cpW cpH]); + 'Position',[cpL vPos-cpH+cpTitleH cpW cpH]); -% Position info table just below control panel -cpBottom = vPos - cpH + 0.02; -infoTableH = 0.30; -infoTableY = cpBottom - infoTableH - 0.01; -infoTablePos = [cpL infoTableY cpW infoTableH]; -posInfo.resetMain = [cpL+.003 infoTableY - rh - 0.005 cpW-.006 rh]; fnameMaster = {}; @@ -253,12 +258,7 @@ set(guiHandles.fileA, 'ForegroundColor', colRun); guiHandles.clr = uicontrol(PSfig,'string','Reset','fontsize',fontsz,'TooltipString', ['clear all data'], 'units','normalized','Position',[posInfo.clr],... - 'callback',['clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF filenameA fnameMaster loaded_firmware debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData rpmFilterData ampmat freq2d2 amp2d2 specMat delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02 gyro_phase_shift_deg dterm_phase_shift_deg tuneCrtlpanel_init setupInfoWidgets_init; ' ... - 'fcnt=0; filenameA={}; fnameMaster={}; Nfiles=0; expandON=0; ' ... - 'try, delete(checkpanel); clear checkpanel; catch, end; ' ... - 'try, delete(subplot(''position'',posInfo.linepos1)); delete(subplot(''position'',posInfo.linepos2)); delete(subplot(''position'',posInfo.linepos3)); delete(subplot(''position'',posInfo.linepos4)); catch, end; ' ... - 'figs=findobj(''Type'',''figure''); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end; ' ... - 'set(guiHandles.FileNum, ''String'', '' ''); try, set(guiHandles.Epoch1_A_Input, ''String'', '' ''); set(guiHandles.Epoch2_A_Input, ''String'', '' ''); catch, end;']); + 'callback','PSresetData;'); set(guiHandles.clr, 'ForegroundColor', cautionCol); guiHandles.startEndButton = uicontrol(PSfig,'style','checkbox', 'string','Trim ','fontsize',fontsz,'TooltipString', [TooltipString_selectButton], 'units','normalized','Position',[posInfo.startEndButton],... @@ -325,9 +325,6 @@ set(guiHandles.PIDtuningService, 'ForegroundColor', cautionCol); -guiHandles.resetMain = uicontrol(PSfig,'string','Reset main directory','fontsize',fontsz ,'FontName','arial','FontAngle','normal','TooltipString', ['Donate to the PIDscope project'],'units','normalized','Position',[posInfo.resetMain],... - 'callback','uiwait(helpdlg(resetupStr)), cd(configDir), main_directory = uigetdir(''Navigate to Main folder''); fid = fopen([''mainDir-PS'' PsVersion ''.txt''],''w''); fprintf(fid,''%s\n'',main_directory); fclose(fid); PIDscope'); -set(guiHandles.resetMain, 'ForegroundColor', cautionCol); @@ -360,16 +357,8 @@ drawnow; pause(0.2); try defaults = readtable('PSdefaults.txt'); - a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']); - try set(t, 'BackgroundColor', th.panelBg, 'ForegroundColor', th.textSecondary); catch, end catch defaults = ' '; - a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']); - try set(t, 'BackgroundColor', th.panelBg, 'ForegroundColor', th.textSecondary); catch, end end @@ -391,3 +380,31 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para drawnow; end PSstyleControls(PSfig, th); + +% Register CP elements for resize — keeps fixed pixel sizes when window changes +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', infoH_px); +nrows = 12; % number of rows in CP +cpItems = {}; +cpItems{end+1} = struct('h', controlpanel, 'type','panel', 'row',0, 'col',0, 'nrows',nrows); +cpItems{end+1} = struct('h', guiHandles.Firmware, 'type','full', 'row',1, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.fileA, 'type','left', 'row',2, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.clr, 'type','right', 'row',2, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.FileNum, 'type','full', 'row',3, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.startEndButton, 'type','left', 'row',4, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.RPYcomboLV, 'type','right', 'row',4, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.plotR, 'type','cb', 'row',5, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.plotP, 'type','cb', 'row',5, 'col',1, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.plotY, 'type','cb', 'row',5, 'col',2, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.lineSmooth, 'type','left', 'row',6, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.linewidth, 'type','right', 'row',6, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.spectrogramButton, 'type','full', 'row',7, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.TuningButton, 'type','full', 'row',8, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',9, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',9, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.DispInfoButton, 'type','right', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveFig, 'type','left', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveSettings, 'type','right', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDtuningService, 'type','full', 'row',12, 'col',0, 'nrows',0); +PSregisterResize(PSfig, cpPx, cpItems, 'rows'); diff --git a/src/core/PSload.m b/src/core/PSload.m index 7754024..f0967bb 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -25,12 +25,15 @@ 'Reset data before loading?'], ... 'Firmware type changed', 'Reset & Load', 'Cancel', 'Reset & Load'); if strcmp(choice, 'Reset & Load') - clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData; - fcnt = 0; fnameMaster = {}; + clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData rpmFilterData ampmat freq2d2 amp2d2 specMat delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02 gyro_phase_shift_deg dterm_phase_shift_deg tuneCrtlpanel_init setupInfoWidgets_init; + fcnt = 0; fnameMaster = {}; Nfiles = 0; + try, delete(checkpanel); clear checkpanel; catch, end try, delete(subplot('position',posInfo.linepos1)); catch, end try, delete(subplot('position',posInfo.linepos2)); catch, end try, delete(subplot('position',posInfo.linepos3)); catch, end try, delete(subplot('position',posInfo.linepos4)); catch, end + figs=findobj('Type','figure'); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end + clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; set(guiHandles.FileNum, 'String', ' '); try, set(guiHandles.Epoch1_A_Input, 'String', ' '); set(guiHandles.Epoch2_A_Input, 'String', ' '); catch, end else @@ -64,13 +67,9 @@ try defaults = readtable('PSdefaults.txt'); a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) catch - defaults = ' '; + defaults = ' '; a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig, 'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) end fnameMaster = [fnameMaster filenameA]; diff --git a/src/core/PSthrSpec.m b/src/core/PSthrSpec.m index e26ad75..3a22380 100644 --- a/src/core/PSthrSpec.m +++ b/src/core/PSthrSpec.m @@ -31,14 +31,17 @@ if subsampleFactor < 1, subsampleFactor = 1; end segment_vector = 1 : segment_length / subsampleFactor : length(Y); + Tm = []; Yseg = []; for i = 1 : length(segment_vector) - subsampleFactor - 1 - Tm(i) = nanmean(X(segment_vector(i) : segment_vector(i) + segment_length)); - Yseg(i,:) = Y(segment_vector(i) : segment_vector(i) + segment_length-1); + if segment_vector(i) + segment_length > length(Y), break; end + Tm(i) = nanmean(X(segment_vector(i) : segment_vector(i) + segment_length)); + Yseg(i,:) = Y(segment_vector(i) : segment_vector(i) + segment_length-1); end - + ampMat = zeros(Tr,(segment_length / 2)); freq = zeros(Tr,(segment_length / 2)); + if isempty(Tm), return; end Tm(find(Tm < 0)) = 0; [Thr_sort Thr_sortInd] = sort(Tm'); Yseg_sort = Yseg(Thr_sortInd,:);% sorted Y according to X (throttle or motor output) diff --git a/src/plot/PSdispSetupInfo.m b/src/plot/PSdispSetupInfo.m index c781ab2..ee722cf 100644 --- a/src/plot/PSdispSetupInfo.m +++ b/src/plot/PSdispSetupInfo.m @@ -23,42 +23,54 @@ end th = PStheme(); - BGCol = []; + diffCol = th.diffBg; + nA = size(setupA,1); + BGCol = repmat(th.panelBg, nA, 1); + u = false(nA, 1); try - for i = 1 : size(setupA,1) - if strcmp(setupA{i}, setupB{i}) - BGCol(i,:) = th.panelBg; - else - BGCol(i,:) = [.6 .2 .2]; + for i = 1 : min(nA, size(setupB,1)) + if ~strcmp(setupA{i}, setupB{i}) + BGCol(i,:) = diffCol; + u(i) = true; end end + for i = size(setupB,1)+1 : nA + BGCol(i,:) = diffCol; + u(i) = true; + end catch - BGCol=th.panelBg; end - u=[]; - u = (sum(BGCol,2)/3) < 1; + delete(findobj(PSdisp, 'Type', 'uitable')); + + tbH = 0.88; if get(guiHandlesInfo.checkboxDIFF, 'Value') == 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); - set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) - set(t,'BackgroundColor', [.6 .2 .2]) - try set(t,'ForegroundColor', th.textPrimary); catch, end + nDiff = sum(u); + diffBG = repmat(diffCol, max(nDiff,1), 1); + st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); + set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]); + try set(st,'BackgroundColor', diffBG); catch, end + try set(st,'ForegroundColor', th.textPrimary); catch, end + try set(st,'RowStriping', 'off'); catch, end if Nfiles > 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); - set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) - set(t,'BackgroundColor', [.6 .2 .2]) - try set(t,'ForegroundColor', th.textPrimary); catch, end + st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); + set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}); + try set(st,'BackgroundColor', diffBG); catch, end + try set(st,'ForegroundColor', th.textPrimary); catch, end + try set(st,'RowStriping', 'off'); catch, end end else - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); - set(t,'units','normalized','Position',[.02 .05 .45 .9],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]) - set(t,'BackgroundColor', [BGCol]) - try set(t,'ForegroundColor', th.textPrimary); catch, end + st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); + set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]); + try set(st,'BackgroundColor', BGCol); catch, end + try set(st,'ForegroundColor', th.textPrimary); catch, end + try set(st,'RowStriping', 'off'); catch, end if Nfiles > 1 - t = uitable('ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); - set(t,'units','normalized','Position',[.52 .05 .45 .9],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}) - set(t,'BackgroundColor', [BGCol]) - try set(t,'ForegroundColor', th.textPrimary); catch, end + st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); + set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}); + try set(st,'BackgroundColor', BGCol); catch, end + try set(st,'ForegroundColor', th.textPrimary); catch, end + try set(st,'RowStriping', 'off'); catch, end end end end diff --git a/src/plot/PSdynSpecPlayer.m b/src/plot/PSdynSpecPlayer.m index 3e20244..ac1bca2 100644 --- a/src/plot/PSdynSpecPlayer.m +++ b/src/plot/PSdynSpecPlayer.m @@ -39,7 +39,8 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) fig = findobj('Type', 'figure', 'Name', figName); if ~isempty(fig), close(fig); end fig = figure('Name', figName, 'NumberTitle', 'off', ... - 'Color', thm.figBg, 'Position', [round(.2*screensz(3)) round(.07*screensz(4)) figW figH]); + 'Color', thm.figBg, 'Position', round([0 0 screensz(3) screensz(4)])); +try set(fig, 'WindowState', 'maximized'); catch, end axColors = {[0 .85 .85], [.85 .85 0], [.85 .3 .85]}; diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 2064f38..249d450 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -11,7 +11,8 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) if ~isempty(fig), close(fig); end fig = figure('Name', 'Filter Simulation', 'NumberTitle', 'off', ... 'Color', thm.figBg, ... - 'Position', round([.1*screensz(3) .08*screensz(4) .75*screensz(3) .8*screensz(4)])); + 'Position', round([0 0 screensz(3) screensz(4)])); +try set(fig, 'WindowState', 'maximized'); catch, end fp = parseFilterParams(setupInfo); diff --git a/src/plot/PSplotBode.m b/src/plot/PSplotBode.m index 9980f1a..af7d3bd 100644 --- a/src/plot/PSplotBode.m +++ b/src/plot/PSplotBode.m @@ -15,7 +15,8 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) if ~isempty(fig), close(fig); end fig = figure('Name', figName, 'NumberTitle', 'off', ... 'Color', th.figBg, ... - 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); + 'Position', round([0 0 screensz(3) screensz(4)])); +try set(fig, 'WindowState', 'maximized'); catch, end freq = freq(:); fPlot = freq(freq > 0); % skip DC for log plot diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 7cc99a7..4acebd6 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -46,7 +46,7 @@ end plotall_flag=-1; - expand_sz=[0.05 0.06 0.815 0.835]; + expand_sz=[0.05 0.06 0.815 posInfo.slider(2)-0.07]; %% where you want full range of data @@ -63,15 +63,24 @@ set(guiHandles.checkbox0, 'String', 'Debug'); end - % if start or end > length of file, or start > end - if (epoch1_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch2_A(fileIdx) > (tta{fileIdx}(end) / us2sec)) || (epoch1_A(fileIdx) > epoch2_A(fileIdx)) - epoch1_A(fileIdx) = 2; - epoch2_A(fileIdx) = floor(tta{fileIdx}(end) / us2sec) - 1; + % clamp epochs to valid data range + if ~exist('tta','var') || ~iscell(tta) || numel(tta) < fileIdx + set(PSfig, 'pointer', 'arrow'); return; + end + tStart = tta{fileIdx}(1) / us2sec; + tEnd = tta{fileIdx}(end) / us2sec; + if epoch1_A(fileIdx) >= tEnd || epoch2_A(fileIdx) <= tStart || epoch1_A(fileIdx) >= epoch2_A(fileIdx) + epoch1_A(fileIdx) = tStart; + epoch2_A(fileIdx) = tEnd; end - y=[epoch1_A(fileIdx)*us2sec epoch2_A(fileIdx)*us2sec];%%% used for fill in unused data range - t1=(tta{fileIdx}(find(tta{fileIdx}>y(1),1))) / us2sec; - t2=(tta{fileIdx}(find(tta{fileIdx}>y(2),1))) / us2sec; + y=[epoch1_A(fileIdx)*us2sec epoch2_A(fileIdx)*us2sec]; + idx1 = find(tta{fileIdx} >= y(1), 1); + idx2 = find(tta{fileIdx} >= y(2), 1); + if isempty(idx1), idx1 = 1; end + if isempty(idx2), idx2 = length(tta{fileIdx}); end + t1 = tta{fileIdx}(idx1) / us2sec; + t2 = tta{fileIdx}(idx2) / us2sec; tIND{fileIdx} = (tta{fileIdx} > (t1*us2sec)) & (tta{fileIdx} < (t2*us2sec)); @@ -80,7 +89,7 @@ % set(jRangeSlider, 'MajorTickSpacing',50, 'PaintTicks',true, 'PaintLabels',true, 'Background',java.awt.Color.white) % jRangeSlider.LowValue = 20;, jRangeSlider.HighValue = 180; - guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',[0.0826 0.905 0.787 0.02],... + guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',posInfo.slider,... 'min',0,'max',1, 'callback','PSslider1Actions;'); diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index 09f9151..800ad87 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -12,7 +12,8 @@ function PSplotMotorNoise(T, f, tIND, Fs) if ~isempty(fig), close(fig); end fig = figure('Name', 'Motor / Prop Noise Analysis', 'NumberTitle', 'off', ... 'Color', th.figBg, ... - 'Position', round([.08*screensz(3) .06*screensz(4) .78*screensz(3) .82*screensz(4)])); + 'Position', round([0 0 screensz(3) screensz(4)])); +try set(fig, 'WindowState', 'maximized'); catch, end F_kHz = Fs / 1000; motorCol = {'motor_0_', 'motor_1_', 'motor_2_', 'motor_3_'}; diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index eda583d..812f7f0 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -142,8 +142,8 @@ posAx = .8:1:9.8; posBx = 1.2:1:10.2; - minyA = min(Perr_a_m(p,:)) - .5; if minyA < 0, minyA = 0; end - maxyA = max(Perr_a_m(p,:)) + .5; + minyA = min(Perr_a_m(p,:)) - .5; if isnan(minyA) || minyA < 0, minyA = 0; end + maxyA = max(Perr_a_m(p,:)) + .5; if isnan(maxyA), maxyA = 1; end h = errorbar(posAx, Perr_a_m(p,:), Perr_a_se(p,:)); hold on; set(h, 'color', th.axesFg, 'LineStyle', 'none'); h = bar(posAx, Perr_a_m(p,:)); @@ -163,8 +163,8 @@ end if ~isempty(fB) - minyB = min(Perr_b_m(p,:)) - .5; if minyB < 0, minyB = 0; end - maxyB = max(Perr_b_m(p,:)) + .5; + minyB = min(Perr_b_m(p,:)) - .5; if isnan(minyB) || minyB < 0, minyB = 0; end + maxyB = max(Perr_b_m(p,:)) + .5; if isnan(maxyB), maxyB = 1; end h = errorbar(posBx, Perr_b_m(p,:), Perr_b_se(p,:)); set(h, 'color', th.axesFg, 'LineStyle', 'none'); h = bar(posBx, Perr_b_m(p,:)); diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 781b38f..41ae720 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -12,17 +12,11 @@ th = PStheme(); -guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); - -guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); +psdIdx = get(guiHandlesSpec.checkboxPSD, 'Value') + 1; +set(guiHandlesSpec.climMax_input, 'String', num2str(climScale(psdIdx, 1))); +set(guiHandlesSpec.climMax_input2, 'String', num2str(climScale(psdIdx, 2))); +set(guiHandlesSpec.climMax_input3, 'String', num2str(climScale(psdIdx, 3))); +set(guiHandlesSpec.climMax_input4, 'String', num2str(climScale(psdIdx, 4))); %% @@ -110,8 +104,8 @@ hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim1/3.33) size(ampmat{p},2)-round(Flim1/3.33)],'y--');set(h,'linewidth',2) hold on;h=plot([0 100],[size(ampmat{p},2)-round(Flim2/3.33) size(ampmat{p},2)-round(Flim2/3.33)],'y--');set(h,'linewidth',2) % sub100Hz scaling - xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; - yticks=[(size(ampmat{p},2)-30):6:size(ampmat{p},2)]; + xticks=round([1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]); + yticks=round([(size(ampmat{p},2)-30):6:size(ampmat{p},2)]); set(h1,'PlotBoxAspectRatioMode','auto','ylim',[size(ampmat{p},2)-30 size(ampmat{p},2)]) set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',{'100';'80';'60';'40';'20';'0'},'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); @@ -132,16 +126,16 @@ h=text(xticks(1)+1,(size(ampmat{p},2)-30)+1,axLabel{c2(p)}); set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') else % full scaling - xticks=[1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]; - yticks=[1:(size(ampmat{p},2))/10:size(ampmat{p},2) size(ampmat{p},2)]; + xticks=round([1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]); + yticks=round([1:(size(ampmat{p},2))/10:size(ampmat{p},2) size(ampmat{p},2)]); maxHz = max(round(yticks * 3.333)); ytlbl = {num2str(maxHz), '', num2str(round(maxHz*4/5)), '', num2str(round(maxHz*3/5)), '', num2str(round(maxHz*2/5)), '', num2str(round(maxHz*1/5)), '', '0'}; set(h1,'fontsize',fontsz,'CLim',[baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1) climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, c1(p))],'YTick',yticks,'yticklabel',ytlbl,'XTick',xticks,'xticklabel',{'0';'20';'40';'60';'80';'100'},'tickdir','out','xminortick','on','yminortick','on'); set(h1,'PlotBoxAspectRatioMode','auto','ylim',[1 size(ampmat{p},2)]) a=[];a2=[];a=filter2(ftr, ampmat{p}) + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); - a2 = a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)); + a2 = a(:,round(size(ampmat{p},2)/10):size(ampmat{p},2)); meanspec=nanmean(a2(:)); - peakspec=max(max(a(:,(size(ampmat{p},2)/10):size(ampmat{p},2)))); + peakspec=max(max(a(:,round(size(ampmat{p},2)/10):size(ampmat{p},2)))); if get(guiHandlesSpec.ColormapSelect, 'Value')==8 || get(guiHandlesSpec.ColormapSelect, 'Value')==9 h=text(64,size(ampmat{p},2)*.04,['mean=' num2str(meanspec,3)]); set(h,'Color','k','fontsize',fontsz,'fontweight','bold'); diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index bce564a..179fd1f 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -11,13 +11,8 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) th = PStheme(); -guiHandlesSpec2.climMax1_text = uicontrol(PSspecfig2,'style','text','string','Y min','fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax1_text]); -guiHandlesSpec2.climMax1_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','Position',[posInfo.climMax1_input],... - 'callback','@textinput_call2; climScale1(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax1_input, ''String''));updateSpec=1;PSplotSpec2D;'); - - guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); -guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... - 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));updateSpec=1;PSplotSpec2D;'); +set(guiHandlesSpec2.climMax1_input, 'String', num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))); +set(guiHandlesSpec2.climMax2_input, 'String', num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))); %% diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index 23e5b43..a14bfe8 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -25,7 +25,7 @@ clear posInfo.statsPos cols = [0.06 0.54]; - rows = [0.75 0.52 0.29 0.06]; + rows = [0.69 0.48 0.27 0.06]; k = 0; for c = 1:2 for r = 1:4 @@ -107,7 +107,7 @@ if plotMode == 2 cols = [0.06 0.30 0.54 0.78]; - rows = [0.76 0.53 0.30 0.08]; + rows = [0.69 0.48 0.27 0.06]; k = 0; for c = 1:length(cols) for r = 1:length(rows) diff --git a/src/ui/PSdispSetupInfoUIcontrol.m b/src/ui/PSdispSetupInfoUIcontrol.m index 1a70cfb..6067cc4 100644 --- a/src/ui/PSdispSetupInfoUIcontrol.m +++ b/src/ui/PSdispSetupInfoUIcontrol.m @@ -15,7 +15,8 @@ else PSdisp=figure(5); screensz = get(0,'ScreenSize'); - set(PSdisp, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSdisp, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSdisp, 'WindowState', 'maximized'); catch, end set(PSdisp, 'NumberTitle', 'on'); set(PSdisp, 'Name', ['PIDscope (' PsVersion ') - Setup Info']); set(PSdisp,'color',bgcolor); @@ -23,10 +24,13 @@ columnWidth = 55 * fontsz; -TooltipString_FileNumDispA=['List of files available. Click to view setup info for each']; -posInfo.FileNumDispA=[.22 .95 .1 .04]; -posInfo.FileNumDispB=[.72 .95 .1 .04]; -posInfo.checkboxDIFF=[.04 .96 .1 .04]; +TooltipString_FileNumDispA=['List of files available. Click to view setup info for each']; +topDdW = 160/screensz(3); topCbW = 200/screensz(3); +tbOff = 40/screensz(4); % toolbar offset +topBtnY = 1 - tbOff - rh - cpMv; +posInfo.FileNumDispA=[.22 topBtnY topDdW ddh]; +posInfo.FileNumDispB=[.72 topBtnY topDdW ddh]; +posInfo.checkboxDIFF=[.04 topBtnY topCbW rh]; if ~exist('setupInfoWidgets_init','var') || ~ishandle(guiHandlesInfo.FileNumDispA) guiHandlesInfo.FileNumDispA = uicontrol(PSdisp,'Style','popupmenu','string',[fnameMaster],... @@ -43,6 +47,17 @@ setupInfoWidgets_init = true; end % ishandle widgets +% Register top bar for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', guiHandlesInfo.checkboxDIFF, 'type','cb', 'row',0, 'col',0, 'hpx',0, 'wpx',200); +cpI{end+1} = struct('h', guiHandlesInfo.FileNumDispA, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +if Nfiles > 1 && isfield(guiHandlesInfo, 'FileNumDispB') && ishandle(guiHandlesInfo.FileNumDispB) + cpI{end+1} = struct('h', guiHandlesInfo.FileNumDispB, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +end +PSregisterResize(PSdisp, cpPx, cpI, 'topbar', 0.04); + else warndlg('Please select file(s)'); end diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index 129b0cd..04754e0 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -14,7 +14,8 @@ figure(PSerrfig); else PSerrfig=figure(7); - set(PSerrfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSerrfig, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSerrfig, 'WindowState', 'maximized'); catch, end set(PSerrfig, 'NumberTitle', 'off'); set(PSerrfig, 'Name', ['PIDscope (' PsVersion ') - PID Error Tool']); set(PSerrfig, 'InvertHardcopy', 'off'); @@ -31,7 +32,7 @@ clear posInfo.PIDerrAnalysis cols=[0.1 0.55]; -rows=[0.66 0.38 0.1]; +rows=[0.63 0.36 0.09]; k=0; for c=1:2 for r=1:3 @@ -40,17 +41,24 @@ end end -posInfo.refresh2=[.09 .94 .06 .04]; -posInfo.saveFig3=[.16 .94 .06 .04]; - -posInfo.maxSticktext=[.23 .966 .12 .03]; -posInfo.maxStick=[.26 .94 .06 .03]; +% Top bar layout — pixel-based sizes +topBtnW = 100/screensz(3); topBtnH = rh; topEdtW = 80/screensz(3); +topTxtW = 120/screensz(3); topBarL = 0.09; +tbOff = 40/screensz(4); % toolbar offset +topBtnY = 1 - tbOff - rh - cpMv; +topX = topBarL + cpM; +posInfo.refresh2= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; +posInfo.saveFig3= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; +posInfo.maxStick= [topX topBtnY topEdtW topBtnH]; topX=topX+topEdtW+cpM; +posInfo.maxSticktext=[topX topBtnY topTxtW topBtnH]; +topPanelW = topX + topTxtW + cpM - topBarL; +topPanelH = 1 - tbOff - topBtnY + cpMv; if ~exist('errCrtlpanel','var') || ~ishandle(errCrtlpanel) errCrtlpanel = uipanel('Title','','FontSize',fontsz3,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[.085 .93 .28 .06]); + 'Position',[topBarL topBtnY-cpMv topPanelW topPanelH]); guiHandlesPIDerr.refresh = uicontrol(PSerrfig,'string','Refresh','fontsize',fontsz3,'TooltipString','Refresh plots','units','normalized','Position',[posInfo.refresh2],... 'callback','updateErr=1;PSplotPIDerror;'); @@ -65,6 +73,17 @@ set(guiHandlesPIDerr.saveFig3, 'ForegroundColor', saveCol); end % ishandle(errCrtlpanel) +% Register top bar for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', guiHandlesPIDerr.refresh, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',100); +cpI{end+1} = struct('h', guiHandlesPIDerr.saveFig3, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',100); +cpI{end+1} = struct('h', guiHandlesPIDerr.maxStick, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',80); +cpI{end+1} = struct('h', guiHandlesPIDerr.maxSticktext, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',120); +cpI{end+1} = struct('h', errCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); +PSregisterResize(PSerrfig, cpPx, cpI, 'topbar', topBarL); + PSstyleControls(PSerrfig); else diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 1d4e2c7..f48e76f 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -38,35 +38,34 @@ updateSpec = 0; clear specMat -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; -rh = .030; rs = .034; - -posInfo.fileListWindowSpec= [cpL+.003 .870 cpW-.006 rh]; -posInfo.TermListWindowSpec= [cpL+.003 .836 cpW-.006 rh]; - -posInfo.computeSpec3= [cpL+.006 .802 cpW/2-.006 rh]; -posInfo.resetSpec3= [cpL+cpW/2 .802 cpW/2-.006 rh]; -posInfo.saveFig3= [cpL+.006 .768 cpW/2-.006 rh]; -posInfo.saveSettings3= [cpL+cpW/2 .768 cpW/2-.006 rh]; -posInfo.smooth_select3 = [cpL+.003 .734 cpW-.006 rh]; -posInfo.subsampling_select3= [cpL+.003 .700 cpW-.006 rh]; -posInfo.ColormapSelect2 = [cpL+.003 .666 cpW-.006 rh]; - -posInfo.clim3Max1_text = [cpL+.003 .632 cpW/4 .024]; -posInfo.clim3Max1_input = [cpL+cpW/4 .598 cpW/4 .024]; -posInfo.clim3Max2_text = [cpL+cpW/2 .632 cpW/4 .024]; -posInfo.clim3Max2_input = [cpL+3*cpW/4 .598 cpW/4 .024]; +% Control panel layout — cpL/cpW/rh/rs/ddh/cpM inherited from PIDscope.m (pixel-based) +% yTop tracks where TOP of next element goes; Position Y = yTop - height +gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; +tbOff_s3 = 40/screensz(4); +yTop = 1 - tbOff_s3 - cpTitleH - cpMv; +posInfo.fileListWindowSpec= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.TermListWindowSpec= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.computeSpec3= [cpL+cpM yTop-rh hw rh]; +posInfo.resetSpec3= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.saveFig3= [cpL+cpM yTop-rh hw rh]; +posInfo.saveSettings3= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.smooth_select3 = [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.subsampling_select3= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.ColormapSelect2 = [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.clim3Max1_text = [cpL+cpM yTop-rhs cpW/4 rhs]; +posInfo.clim3Max2_text = [cpL+cpW/2 yTop-rhs cpW/4 rhs]; yTop=yTop-rhs-gap; +posInfo.clim3Max1_input = [cpL+cpM yTop-rh cpW/4 rh]; +posInfo.clim3Max2_input = [cpL+cpW/2 yTop-rh cpW/4 rh]; yTop=yTop-rh-gap; ClimScale3 = [-30 10]; - -posInfo.sub100HzfreqTime = [cpL+.003 .564 cpW-.006 .025]; -posInfo.playerBtn3 = [cpL+.003 .530 cpW-.006 rh]; +posInfo.sub100HzfreqTime = [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.playerBtn3 = [cpL+cpM yTop-rh fw rh]; if exist('PSspecfig3','var') && ishandle(PSspecfig3) figure(PSspecfig3); else PSspecfig3=figure(31); - set(PSspecfig3, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig3, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSspecfig3, 'WindowState', 'maximized'); catch, end set(PSspecfig3, 'NumberTitle', 'off'); set(PSspecfig3, 'Name', ['PIDscope (' PsVersion ') - Frequency x Time Spectrogram']); set(PSspecfig3, 'InvertHardcopy', 'off'); @@ -83,7 +82,7 @@ Spec3Crtlpanel = uipanel('Title','select file ','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[cpL .515 cpW .405]); + 'Position',[cpL yTop-rh-gap cpW vPos-(yTop-rh-gap)+cpTitleH]); guiHandlesSpec3.computeSpec = uicontrol(PSspecfig3,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec3],... 'callback','updateSpec = 0; clear specMat; PSfreqTime;'); @@ -140,6 +139,28 @@ set(guiHandlesSpec3.playerBtn, 'ForegroundColor', [0 .4 .8]); end % ishandle(Spec3Crtlpanel) +% Register CP for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', Spec3Crtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0); +cpI{end+1} = struct('h', guiHandlesSpec3.FileSelect, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.SpecList, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.computeSpec, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.resetSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.saveFig3, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.saveSettings3, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.smoothFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.subsampleFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.ColormapSelect, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.climMax1_text, 'type','text_left', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec3.climMax2_text, 'type','text_right', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec3.climMax1_input, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.sub100HzfreqTime, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.playerBtn, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +PSregisterResize(PSspecfig3, cpPx, cpI, 'seq'); + try set(guiHandlesSpec3.SpecList, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-Preset')))), catch, set(guiHandlesSpec3.SpecList, 'Value', 1), end try set(guiHandlesSpec3.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-FreqSmoothing')))), catch, set(guiHandlesSpec3.smoothFactor_select, 'Value', 2), end try set(guiHandlesSpec3.subsampleFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-TimeSmoothing')))), catch, set(guiHandlesSpec3.subsampleFactor_select, 'Value', 2), end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index df91497..37955a4 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -38,68 +38,38 @@ end end -vPosSpec2d = .037; -sp2_rh = .026; sp2_ddh = .04; -if exist('isOctave','var') && isOctave - sp2_rh = .030; sp2_ddh = .035; -end - -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; - -posInfo.fileListWindowSpec= [cpL+.003 .7+vPosSpec2d cpW-.006 .20]; -posInfo.TermListWindowSpec= [cpL+.003 .55+vPosSpec2d cpW-.006 .14]; - -posInfo.computeSpec= [cpL+.006 .52+vPosSpec2d cpW/2-.006 sp2_rh]; -posInfo.resetSpec= [cpL+cpW/2 .52+vPosSpec2d cpW/2-.006 sp2_rh]; -posInfo.spectrogramButton2= [cpL+.003 .49+vPosSpec2d cpW-.006 sp2_rh]; -posInfo.spectrogramButton3= [cpL+.003 .46+vPosSpec2d cpW-.006 sp2_rh]; -posInfo.filterSimButton= [cpL+.003 .43+vPosSpec2d cpW-.006 sp2_rh]; -posInfo.motorNoiseButton= [cpL+.003 .40+vPosSpec2d cpW-.006 sp2_rh]; -posInfo.chirpButton= [cpL+.003 .37+vPosSpec2d cpW-.006 sp2_rh]; -posInfo.saveFig2= [cpL+.006 .34+vPosSpec2d cpW/2-.006 sp2_rh]; -posInfo.saveSettings2= [cpL+cpW/2 .34+vPosSpec2d cpW/2-.006 sp2_rh]; - -posInfo.smooth_select = [cpL+.003 .305+vPosSpec2d cpW-.006 sp2_ddh]; -posInfo.Delay = [cpL+.003 .27+vPosSpec2d cpW-.006 sp2_ddh]; - -posInfo.plotRspec = [cpL+.005 .245+vPosSpec2d .035 .025]; -posInfo.plotPspec = [cpL+.04 .245+vPosSpec2d .035 .025]; -posInfo.plotYspec = [cpL+.075 .245+vPosSpec2d .035 .025]; - -posInfo.checkboxPSD = [cpL+.005 .225+vPosSpec2d .04 .02]; -posInfo.RPYcomboSpec = [cpL+cpW/2-.01 .225+vPosSpec2d cpW/2+.004 .02]; - -posInfo.climMax1_text = [cpL+.003 .202+vPosSpec2d cpW/4 .022]; -posInfo.climMax1_input = [cpL+cpW/4 .180+vPosSpec2d cpW/4 .022]; -posInfo.climMax2_text = [cpL+cpW/2 .202+vPosSpec2d cpW/4 .022]; -posInfo.climMax2_input = [cpL+3*cpW/4 .180+vPosSpec2d cpW/4 .022]; - -if exist('isOctave','var') && isOctave - % Octave Qt widgets need more vertical space - vPosSpec2d = .037; - rr = .030; dd = .035; - posInfo.computeSpec= [cpL+.006 .52+vPosSpec2d cpW/2-.006 rr]; - posInfo.resetSpec= [cpL+cpW/2 .52+vPosSpec2d cpW/2-.006 rr]; - posInfo.spectrogramButton2= [cpL+.003 .485+vPosSpec2d cpW-.006 rr]; - posInfo.spectrogramButton3= [cpL+.003 .450+vPosSpec2d cpW-.006 rr]; - posInfo.filterSimButton= [cpL+.003 .415+vPosSpec2d cpW-.006 rr]; - posInfo.motorNoiseButton= [cpL+.003 .380+vPosSpec2d cpW-.006 rr]; - posInfo.chirpButton= [cpL+.003 .345+vPosSpec2d cpW-.006 rr]; - posInfo.saveFig2= [cpL+.006 .310+vPosSpec2d cpW/2-.006 rr]; - posInfo.saveSettings2= [cpL+cpW/2 .310+vPosSpec2d cpW/2-.006 rr]; - posInfo.smooth_select= [cpL+.003 .270+vPosSpec2d cpW-.006 dd]; - posInfo.Delay= [cpL+.003 .233+vPosSpec2d cpW-.006 dd]; - posInfo.plotRspec= [cpL+.005 .208+vPosSpec2d .035 .025]; - posInfo.plotPspec= [cpL+.04 .208+vPosSpec2d .035 .025]; - posInfo.plotYspec= [cpL+.075 .208+vPosSpec2d .035 .025]; - posInfo.checkboxPSD= [cpL+.005 .185+vPosSpec2d .04 .025]; - posInfo.RPYcomboSpec= [cpL+cpW/2-.01 .185+vPosSpec2d cpW/2+.004 .025]; - posInfo.climMax1_text= [cpL+.003 .162+vPosSpec2d cpW/4 .024]; - posInfo.climMax1_input= [cpL+cpW/4 .140+vPosSpec2d cpW/4 .024]; - posInfo.climMax2_text= [cpL+cpW/2 .162+vPosSpec2d cpW/4 .024]; - posInfo.climMax2_input= [cpL+3*cpW/4 .140+vPosSpec2d cpW/4 .024]; -end +% Control panel layout — cpL/cpW/rh/rs/cpM/ddh inherited from PIDscope.m +% yTop tracks where the TOP of the next element goes; Position Y = yTop - height +listH = 5*rs; termH = 4*rs; gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; +tbOff_s2 = 40/screensz(4); +yTop = 1 - tbOff_s2 - cpTitleH - cpMv; +posInfo.fileListWindowSpec= [cpL+cpM yTop-listH fw listH]; yTop=yTop-listH-gap; +posInfo.TermListWindowSpec= [cpL+cpM yTop-termH fw termH]; yTop=yTop-termH-gap; + +posInfo.computeSpec= [cpL+cpM yTop-rh hw rh]; +posInfo.resetSpec= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.spectrogramButton2= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.spectrogramButton3= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.filterSimButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.motorNoiseButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.chirpButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.saveFig2= [cpL+cpM yTop-rh hw rh]; +posInfo.saveSettings2= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; + +posInfo.smooth_select = [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.Delay = [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; + +posInfo.plotRspec = [cpL+cpM yTop-rh cbW rh]; +posInfo.plotPspec = [cpL+cpM+cbW yTop-rh cbW rh]; +posInfo.plotYspec = [cpL+cpM+2*cbW yTop-rh cbW rh]; yTop=yTop-rh-gap; + +posInfo.checkboxPSD = [cpL+cpM yTop-rh cbW rh]; +posInfo.RPYcomboSpec = [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; + +posInfo.climMax1_text = [cpL+cpM yTop-rhs cpW/4 rhs]; +posInfo.climMax2_text = [cpL+cpW/2 yTop-rhs cpW/4 rhs]; yTop=yTop-rhs-gap; +posInfo.climMax1_input = [cpL+cpM yTop-rh cpW/4 rh]; +posInfo.climMax2_input = [cpL+cpW/2 yTop-rh cpW/4 rh]; climScale1=[0 ; -50 ]; climScale2=[0.5 ; 20]; @@ -108,7 +78,8 @@ figure(PSspecfig2); else PSspecfig2=figure(3); - set(PSspecfig2, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig2, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSspecfig2, 'WindowState', 'maximized'); catch, end set(PSspecfig2, 'NumberTitle', 'off'); set(PSspecfig2, 'Name', ['PIDscope (' PsVersion ') - Spectral Analyzer']); set(PSspecfig2, 'InvertHardcopy', 'off'); @@ -121,10 +92,9 @@ set(dcm_obj2,'UpdateFcn',@PSdatatip); end -spec2CrtlpanelPos = [cpL .21+vPosSpec2d cpW .71]; -if exist('isOctave','var') && isOctave - spec2CrtlpanelPos = [cpL .16+vPosSpec2d cpW .76]; -end +sp2PanelBot = yTop - rh - gap; +sp2PanelH = vPos - sp2PanelBot + cpTitleH; +spec2CrtlpanelPos = [cpL sp2PanelBot cpW sp2PanelH]; if ~exist('spec2Crtlpanel','var') || ~ishandle(spec2Crtlpanel) spec2Crtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... @@ -228,6 +198,36 @@ 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); end % ishandle(spec2Crtlpanel) +% Register CP for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', spec2Crtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0); +listH_px = 5*rs_px; termH_px = 4*rs_px; +cpI{end+1} = struct('h', guiHandlesSpec2.FileSelect, 'type','full', 'row',0, 'col',0, 'hpx',listH_px); +cpI{end+1} = struct('h', guiHandlesSpec2.SpecList, 'type','full', 'row',0, 'col',0, 'hpx',termH_px); +cpI{end+1} = struct('h', guiHandlesSpec2.computeSpec, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.resetSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton2, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton3, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.filterSimButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.motorNoiseButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.chirpButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.saveFig2, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.saveSettings2, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.smoothFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.Delay, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.plotR, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.plotP, 'type','cb', 'row',0, 'col',1, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.plotY, 'type','cb_end', 'row',0, 'col',2, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.checkboxPSD, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.RPYcomboSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.climMax1_text, 'type','text_left', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_text, 'type','text_right', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec2.climMax1_input, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); +PSregisterResize(PSspecfig2, cpPx, cpI, 'seq'); + try set(guiHandlesSpec2.SpecList, 'Value', [defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term1'))) defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term2')))]), catch, set(guiHandlesSpec2.SpecList, 'Value', [1 2]), end try set(guiHandlesSpec2.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-smoothing')))), catch, set(guiHandlesSpec2.smoothFactor_select, 'Value', 3), end try set(guiHandlesSpec2.Delay, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-delay')))), catch, set(guiHandlesSpec2.Delay, 'Value', 1), end diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 8d0fbbb..52a1020 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -46,9 +46,10 @@ SpecLineCols(:,:,3) = [colorA; colorB; colorC; colorD]; +tbOff_spec = 40/screensz(4); clear posInfo.SpecPos cols=[0.04 0.25 0.46 0.67]; -rows=[0.64 0.35 0.06]; +rows=[0.64-tbOff_spec 0.35-tbOff_spec 0.06-tbOff_spec]; k=0; for c=1:4 for r=1:3 @@ -58,82 +59,63 @@ end -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; - -posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .026]; -posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .026]; -posInfo.saveFig1= [cpL+.006 .845 cpW/2-.006 .026]; -posInfo.saveSettings1= [cpL+cpW/2 .845 cpW/2-.006 .026]; - -posInfo.specPresets= [cpL+.003 .805 cpW-.006 .035]; -posInfo.ColormapSelect= [cpL+.003 .775 cpW-.006 .035]; -posInfo.smooth_select = [cpL+.003 .745 cpW-.006 .035]; -posInfo.controlFreqCutoff_text =[cpL+.003 .725 cpW-.006 .025]; -posInfo.controlFreq1Cutoff = [cpL+.005 .700 cpW/2-.005 .025]; -posInfo.controlFreq2Cutoff = [cpL+cpW/2 .700 cpW/2-.005 .025]; -posInfo.checkbox2d= [cpL+.005 .675 cpW/2-.005 .025]; -posInfo.checkboxPSD= [cpL+cpW/2 .675 cpW/2-.005 .025]; -posInfo.checkboxEstRPM= [cpL+.005 .650 cpW-.005 .025]; -posInfo.rpmLegend1= [cpL+.005 .632 cpW-.005 .018]; -posInfo.rpmLegend2= [cpL+.005 .616 cpW-.005 .018]; -posInfo.rpmLegend3= [cpL+.005 .600 cpW-.005 .018]; - -if exist('isOctave','var') && isOctave - % Octave Qt widgets need more vertical space - posInfo.computeSpec= [cpL+.006 .87 cpW/2-.006 .030]; - posInfo.resetSpec= [cpL+cpW/2 .87 cpW/2-.006 .030]; - posInfo.saveFig1= [cpL+.006 .838 cpW/2-.006 .030]; - posInfo.saveSettings1= [cpL+cpW/2 .838 cpW/2-.006 .030]; - posInfo.specPresets= [cpL+.003 .795 cpW-.006 .035]; - posInfo.ColormapSelect= [cpL+.003 .758 cpW-.006 .035]; - posInfo.smooth_select= [cpL+.003 .721 cpW-.006 .035]; - posInfo.controlFreqCutoff_text= [cpL+.003 .700 cpW-.006 .024]; - posInfo.controlFreq1Cutoff= [cpL+.005 .675 cpW/2-.005 .024]; - posInfo.controlFreq2Cutoff= [cpL+cpW/2 .675 cpW/2-.005 .024]; - posInfo.checkbox2d= [cpL+.005 .650 cpW/2-.005 .025]; - posInfo.checkboxPSD= [cpL+cpW/2 .650 cpW/2-.005 .025]; - posInfo.checkboxEstRPM= [cpL+.005 .625 cpW-.005 .025]; - posInfo.rpmLegend1= [cpL+.005 .605 cpW-.005 .020]; - posInfo.rpmLegend2= [cpL+.005 .585 cpW-.005 .020]; - posInfo.rpmLegend3= [cpL+.005 .565 cpW-.005 .020]; -end - -posInfo.AphasedelayText1=[.06 .984 .14 .02]; -posInfo.AphasedelayText2=[.27 .984 .14 .02]; -posInfo.AphasedelayText3=[.48 .984 .14 .02]; -posInfo.AphasedelayText4=[.69 .984 .14 .02]; - -posInfo.hCbar1pos=[0.04 0.89 0.175 0.02]; -posInfo.hCbar2pos=[0.25 0.89 0.175 0.02]; -posInfo.hCbar3pos=[0.46 0.89 0.175 0.02]; -posInfo.hCbar4pos=[0.67 0.89 0.175 0.02]; +% Control panel layout — cpL/cpW/rh/rs/ddh/cpM inherited from PIDscope.m (pixel-based) +% yTop tracks where TOP of next element goes; Position Y = yTop - height +gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; +tbOff_s1 = 40/screensz(4); +yTop = 1 - tbOff_s1 - cpTitleH - cpMv; +posInfo.computeSpec= [cpL+cpM yTop-rh hw rh]; +posInfo.resetSpec= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.saveFig1= [cpL+cpM yTop-rh hw rh]; +posInfo.saveSettings1= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.specPresets= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.ColormapSelect= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.smooth_select = [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.controlFreqCutoff_text =[cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.controlFreq1Cutoff = [cpL+cpM yTop-rh hw rh]; +posInfo.controlFreq2Cutoff = [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.checkbox2d= [cpL+cpM yTop-rh hw rh]; +posInfo.checkboxPSD= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.checkboxEstRPM= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.rpmLegend1= [cpL+cpM yTop-rhs fw rhs]; yTop=yTop-rhs-cpMv; +posInfo.rpmLegend2= [cpL+cpM yTop-rhs fw rhs]; yTop=yTop-rhs-cpMv; +posInfo.rpmLegend3= [cpL+cpM yTop-rhs fw rhs]; + +posInfo.AphasedelayText1=[.06 .984-tbOff_spec .14 .02]; +posInfo.AphasedelayText2=[.27 .984-tbOff_spec .14 .02]; +posInfo.AphasedelayText3=[.48 .984-tbOff_spec .14 .02]; +posInfo.AphasedelayText4=[.69 .984-tbOff_spec .14 .02]; + +posInfo.hCbar1pos=[0.04 0.89-tbOff_spec 0.175 0.02]; +posInfo.hCbar2pos=[0.25 0.89-tbOff_spec 0.175 0.02]; +posInfo.hCbar3pos=[0.46 0.89-tbOff_spec 0.175 0.02]; +posInfo.hCbar4pos=[0.67 0.89-tbOff_spec 0.175 0.02]; ddh = 0.01; % dropdown height if exist('isOctave','var') && isOctave, ddh = 0.025; end -posInfo.hDropdn1pos=[0.08 0.97 0.095 ddh]; -posInfo.hDropdn2pos=[0.29 0.97 0.095 ddh]; -posInfo.hDropdn3pos=[0.50 0.97 0.095 ddh]; -posInfo.hDropdn4pos=[0.71 0.97 0.095 ddh]; - -posInfo.fDropdn1pos=[0.08 0.942 0.095 ddh]; -posInfo.fDropdn2pos=[0.29 0.942 0.095 ddh]; -posInfo.fDropdn3pos=[0.50 0.942 0.095 ddh]; -posInfo.fDropdn4pos=[0.71 0.942 0.095 ddh]; - -posInfo.Sub100HzCheck1=[0.175 0.942 .06 .025]; -posInfo.Sub100HzCheck2=[.385 .942 .06 .025]; -posInfo.Sub100HzCheck3=[.595 .942 .06 .025]; -posInfo.Sub100HzCheck4=[.805 .942 .06 .025]; - -posInfo.climMax_text = [.01 .913 .025 .024]; -posInfo.climMax_input = [.01 .888 .025 .024]; -posInfo.climMax_text2 = [.22 .913 .025 .024]; -posInfo.climMax_input2 = [.22 .888 .025 .024]; -posInfo.climMax_text3 = [.43 .913 .025 .024]; -posInfo.climMax_input3 = [.43 .888 .025 .024]; -posInfo.climMax_text4 = [.64 .913 .025 .024]; -posInfo.climMax_input4 = [.64 .888 .025 .024]; +posInfo.hDropdn1pos=[0.08 0.97-tbOff_spec 0.095 ddh]; +posInfo.hDropdn2pos=[0.29 0.97-tbOff_spec 0.095 ddh]; +posInfo.hDropdn3pos=[0.50 0.97-tbOff_spec 0.095 ddh]; +posInfo.hDropdn4pos=[0.71 0.97-tbOff_spec 0.095 ddh]; + +posInfo.fDropdn1pos=[0.08 0.942-tbOff_spec 0.095 ddh]; +posInfo.fDropdn2pos=[0.29 0.942-tbOff_spec 0.095 ddh]; +posInfo.fDropdn3pos=[0.50 0.942-tbOff_spec 0.095 ddh]; +posInfo.fDropdn4pos=[0.71 0.942-tbOff_spec 0.095 ddh]; + +posInfo.Sub100HzCheck1=[0.175 0.942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck2=[.385 .942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck3=[.595 .942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck4=[.805 .942-tbOff_spec .06 .025]; + +posInfo.climMax_text = [.01 .913-tbOff_spec .025 .024]; +posInfo.climMax_input = [.01 .888-tbOff_spec .025 .024]; +posInfo.climMax_text2 = [.22 .913-tbOff_spec .025 .024]; +posInfo.climMax_input2 = [.22 .888-tbOff_spec .025 .024]; +posInfo.climMax_text3 = [.43 .913-tbOff_spec .025 .024]; +posInfo.climMax_input3 = [.43 .888-tbOff_spec .025 .024]; +posInfo.climMax_text4 = [.64 .913-tbOff_spec .025 .024]; +posInfo.climMax_input4 = [.64 .888-tbOff_spec .025 .024]; climScale=[0.5 0.5 0.5 0.5; 10 10 10 10]; Flim1=20; % 3.3333Hz steps Flim2=60; @@ -142,7 +124,8 @@ figure(PSspecfig); else PSspecfig=figure(2); - set(PSspecfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSspecfig, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSspecfig, 'WindowState', 'maximized'); catch, end set(PSspecfig, 'NumberTitle', 'off'); set(PSspecfig, 'Name', ['PIDscope (' PsVersion ') - Frequency x Throttle Spectrogram']); set(PSspecfig, 'InvertHardcopy', 'off'); @@ -155,15 +138,12 @@ set(dcm_obj2,'UpdateFcn',@PSdatatip); end -specCrtlpanelPos = [cpL .59 cpW .33]; -if exist('isOctave','var') && isOctave - specCrtlpanelPos = [cpL .55 cpW .37]; -end +spPanelBot = yTop - rhs - cpMv; if ~exist('specCrtlpanel','var') || ~ishandle(specCrtlpanel) specCrtlpanel = uipanel('Title','Params','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',specCrtlpanelPos); + 'Position',[cpL spPanelBot cpW vPos-spPanelBot+cpTitleH]); %%% PRESET CONFIGURATIONS @@ -203,9 +183,9 @@ 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxEstRPM],'callback','updateSpec=1;PSplotSpec;'); set(guiHandlesSpec.checkboxEstRPM, 'Value', 0); -uicontrol(PSspecfig,'style','text','string','--- 1st (fund)','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[0 .7 .15],'FontWeight','bold','Position',[posInfo.rpmLegend1]); -uicontrol(PSspecfig,'style','text','string','-- 2nd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .7 0],'FontWeight','bold','Position',[posInfo.rpmLegend2]); -uicontrol(PSspecfig,'style','text','string','... 3rd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .2 0],'FontWeight','bold','Position',[posInfo.rpmLegend3]); +guiHandlesSpec.rpmLegend1 = uicontrol(PSspecfig,'style','text','string','--- 1st (fund)','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[0 .7 .15],'FontWeight','bold','Position',[posInfo.rpmLegend1]); +guiHandlesSpec.rpmLegend2 = uicontrol(PSspecfig,'style','text','string','-- 2nd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .7 0],'FontWeight','bold','Position',[posInfo.rpmLegend2]); +guiHandlesSpec.rpmLegend3 = uicontrol(PSspecfig,'style','text','string','... 3rd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .2 0],'FontWeight','bold','Position',[posInfo.rpmLegend3]); guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); guiHandlesSpec.controlFreq1Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim1))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq1Cutoff],... @@ -262,6 +242,29 @@ set(guiHandlesSpec.ColormapSelect, 'Value', 3);% jet 2 hot 3 viridis 8 end % ishandle(specCrtlpanel) +% Register CP for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', specCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0); +cpI{end+1} = struct('h', guiHandlesSpec.computeSpec, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.resetSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.saveFig1, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.saveSettings1, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.specPresets, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec.ColormapSelect, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec.smoothFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec.controlFreqCutoff_text, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.controlFreq1Cutoff, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.controlFreq2Cutoff, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.checkbox2d, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.checkboxPSD, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.checkboxEstRPM, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend1, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend2, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend3, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); +PSregisterResize(PSspecfig, cpPx, cpI, 'seq'); + try set(guiHandlesSpec.SpecSelect{1}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column1')))), catch, set(guiHandlesSpec.SpecSelect{1}, 'Value', 3); end try set(guiHandlesSpec.SpecSelect{2}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column2')))), catch, set(guiHandlesSpec.SpecSelect{2}, 'Value', 2); end try set(guiHandlesSpec.SpecSelect{3}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column3')))), catch, set(guiHandlesSpec.SpecSelect{3}, 'Value', 8); end diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index 71a8d47..b1605ce 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -13,7 +13,8 @@ figure(PSstatsfig); else PSstatsfig=figure(6); - set(PSstatsfig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PSstatsfig, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PSstatsfig, 'WindowState', 'maximized'); catch, end set(PSstatsfig, 'NumberTitle', 'off'); set(PSstatsfig, 'Name', ['PIDscope (' PsVersion ') - Flight stats']); set(PSstatsfig, 'InvertHardcopy', 'off'); @@ -57,7 +58,7 @@ clear posInfo.statsPos cols=[0.06 0.54]; -rows=[0.75 0.52 0.29 0.06]; +rows=[0.69 0.48 0.27 0.06]; k=0; for c=1:2 for r=1:4 @@ -66,21 +67,28 @@ end end -posInfo.saveFig5=[.065 .945 .06 .04]; -posInfo.refresh3=[.135 .945 .06 .04]; -posInfo.degsecStick=[.20 .945 .09 .04]; -posInfo.crossAxesStats=[.29 .945 .08 .04]; - -posInfo.crossAxesStats_text = [.385 .965 .03 .03]; -posInfo.crossAxesStats_input = [.385 .945 .03 .03]; -posInfo.crossAxesStats_text2 = [.42 .965 .03 .03]; -posInfo.crossAxesStats_input2 = [.42 .945 .03 .03]; +% Top bar layout — pixel-based sizes +topBtnW = 100/screensz(3); topBtnH = rh; topCbW = 150/screensz(3); +topDdW = 140/screensz(3); topEdtW = 50/screensz(3); topTxtW = 50/screensz(3); +topBarL = 0.065; +tbOff = 40/screensz(4); % toolbar offset +topLblY = 1 - tbOff - rhs - cpMv; topBtnY = topLblY - rhs - cpMv; +topX = topBarL + cpM; +posInfo.saveFig5= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; +posInfo.refresh3= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; +posInfo.degsecStick= [topX topBtnY topCbW topBtnH]; topX=topX+topCbW+cpM; +posInfo.crossAxesStats=[topX topBtnY topDdW topBtnH]; topX=topX+topDdW+cpM; +posInfo.crossAxesStats_text = [topX topLblY topTxtW rhs]; +posInfo.crossAxesStats_input = [topX topBtnY topEdtW topBtnH]; topX=topX+topEdtW+cpM; +posInfo.crossAxesStats_text2 = [topX topLblY topTxtW rhs]; +posInfo.crossAxesStats_input2 = [topX topBtnY topEdtW topBtnH]; +topPanelW = topX + topEdtW + cpM - topBarL; if ~exist('statsCrtlpanel','var') || ~ishandle(statsCrtlpanel) statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[.06 .935 .40 .06]); + 'Position',[topBarL topBtnY-cpMv topPanelW 1-tbOff-topBtnY+cpMv]); guiHandlesStats.saveFig5 = uicontrol(PSstatsfig,'string','Save Fig','fontsize',fontsz5,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig5],... 'callback','PSsaveFig;'); @@ -93,7 +101,7 @@ guiHandlesStats.degsecStick =uicontrol(PSstatsfig,'Style','checkbox','String','rate of change','fontsize',fontsz5,'TooltipString',[TooltipString_degsecStick],... 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.degsecStick],'callback','PSplotStats;'); guiHandlesStats.crossAxesStats =uicontrol(PSstatsfig,'Style','popupmenu','String',{'Histograms'; 'Mean & Standard Deviation'; 'Mode 1 topography'; 'Mode 2 topography'; 'Axes X Throttle'},'fontsize',fontsz5,'TooltipString',[TooltipString_crossAxesStats],... - 'units','normalized','BackgroundColor',[1 1 1 ],'Position',[posInfo.crossAxesStats],'callback','PSplotStats;'); + 'units','normalized','Position',[posInfo.crossAxesStats],'callback','PSplotStats;'); %guiHandlesStats.crossAxesStats.Value=0; guiHandlesStats.crossAxesStats_text = uicontrol(PSstatsfig,'style','text','string','scale','fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text]); @@ -105,6 +113,21 @@ 'callback','zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); end % ishandle(statsCrtlpanel) +% Register top bar for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +cpI{end+1} = struct('h', guiHandlesStats.saveFig5, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',100); +cpI{end+1} = struct('h', guiHandlesStats.refresh, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',100); +cpI{end+1} = struct('h', guiHandlesStats.degsecStick, 'type','cb', 'row',0, 'col',0, 'hpx',0, 'wpx',150); +cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',140); +cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_text, 'type','lbl', 'row',0, 'col',0, 'hpx',0, 'wpx',50); +cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_input, 'type','input', 'row',0, 'col',0, 'hpx',0, 'wpx',50); +cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_text2, 'type','lbl', 'row',0, 'col',0, 'hpx',0, 'wpx',50); +cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_input2, 'type','input', 'row',0, 'col',0, 'hpx',0, 'wpx',50); +cpI{end+1} = struct('h', statsCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); +PSregisterResize(PSstatsfig, cpPx, cpI, 'topbar', topBarL); + PSstyleControls(PSstatsfig); else diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index c28bfa8..ba8585d 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -13,7 +13,8 @@ figure(PStunefig); else PStunefig=figure(4); - set(PStunefig, 'Position', round([.1*screensz(3) .1*screensz(4) .75*screensz(3) .8*screensz(4)])); + set(PStunefig, 'Position', round([0 0 screensz(3) screensz(4)])); + try set(PStunefig, 'WindowState', 'maximized'); catch, end set(PStunefig, 'NumberTitle', 'on'); set(PStunefig, 'Name', ['PIDscope (' PsVersion ') - Step Response Tool']); set(PStunefig, 'InvertHardcopy', 'off'); @@ -52,29 +53,30 @@ end end -% Control panel layout (consistent with Log Viewer cpL/cpW) -cpL = .875; cpW = .12; -rh = .030; rs = .034; - -posInfo.fileListWindowStep= [cpL+.003 .660 cpW-.006 .24]; -posInfo.run4= [cpL+.006 .625 cpW/2-.006 rh]; -posInfo.clearPlots= [cpL+cpW/2 .625 cpW/2-.006 rh]; -posInfo.saveFig4= [cpL+.006 .591 cpW/2-.006 rh]; -posInfo.saveSettings4= [cpL+cpW/2 .591 cpW/2-.006 rh]; -posInfo.smooth_tuning= [cpL+.003 .557 cpW-.006 rh]; -posInfo.plotR= [cpL+.005 .523 .035 .025]; -posInfo.plotP= [cpL+.04 .523 .035 .025]; -posInfo.plotY= [cpL+.075 .523 .035 .025]; -posInfo.RPYcombo= [cpL+.005 .489 cpW-.01 .025]; -posInfo.Ycorrection= [cpL+.005 .455 cpW-.01 .025]; -posInfo.maxYStepInput= [cpL+.005 .421 cpW/3 .025]; -posInfo.maxYStepTxt= [cpL+cpW/3+.005 .421 cpW/2 .025]; +% Control panel layout — cpL/cpW/rh/rs/ddh/cpM/cbW inherited from PIDscope.m (pixel-based) +% yTop tracks where TOP of next element goes; Position Y = yTop - height +listH_step = 8*rs; gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; +tbOff_s4 = 40/screensz(4); +yTop = 1 - tbOff_s4 - cpTitleH - cpMv; +posInfo.fileListWindowStep= [cpL+cpM yTop-listH_step fw listH_step]; yTop=yTop-listH_step-gap; +posInfo.run4= [cpL+cpM yTop-rh hw rh]; +posInfo.clearPlots= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.saveFig4= [cpL+cpM yTop-rh hw rh]; +posInfo.saveSettings4= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.smooth_tuning= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.plotR= [cpL+cpM yTop-rh cbW rh]; +posInfo.plotP= [cpL+cpM+cbW yTop-rh cbW rh]; +posInfo.plotY= [cpL+cpM+2*cbW yTop-rh cbW rh]; yTop=yTop-rh-gap; +posInfo.RPYcombo= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.Ycorrection= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.maxYStepInput= [cpL+cpM yTop-rh cpW/3 rh]; +posInfo.maxYStepTxt= [cpL+cpW/3+cpM yTop-rhs cpW/2 rhs]; if ~exist('tuneCrtlpanel_init','var') || ~ishandle(guiHandlesTune.tuneCrtlpanel) guiHandlesTune.tuneCrtlpanel = uipanel('Title','select files (max 10)','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[cpL .41 cpW .51]); + 'Position',[cpL yTop-rh-gap cpW vPos-(yTop-rh-gap)+cpTitleH+cpMv]); guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... 'callback','PStuningParams;'); @@ -125,6 +127,27 @@ tuneCrtlpanel_init = true; end % ishandle(tuneCrtlpanel) +% Register CP for fixed-pixel resize +cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... + 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); +cpI = {}; +listH_step_px = 8*rs_px; +cpI{end+1} = struct('h', guiHandlesTune.tuneCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0); +cpI{end+1} = struct('h', guiHandlesTune.fileListWindowStep, 'type','full', 'row',0, 'col',0, 'hpx',listH_step_px); +cpI{end+1} = struct('h', guiHandlesTune.run4, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.clearPlots, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.saveFig4, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.saveSettings, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.smoothFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesTune.plotR, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.plotP, 'type','cb', 'row',0, 'col',1, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.plotY, 'type','cb_end', 'row',0, 'col',2, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.RPYcombo, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.Ycorrection, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.maxYStepInput, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.maxYStepTxt, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +PSregisterResize(PStunefig, cpPx, cpI, 'seq'); + try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end try set(guiHandlesTune.plotP, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotP')))), catch, set(guiHandlesTune.plotP, 'Value', 1), end try set(guiHandlesTune.plotY, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotY')))), catch, set(guiHandlesTune.plotY, 'Value', 1), end diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 82926a6..95f20f6 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -9,46 +9,57 @@ % ---------------------------------------------------------------------------------- -posInfo.checkbox0=[.1 .965 .1 .025]; -posInfo.checkbox1=[.1 .94 .1 .025]; -posInfo.checkbox2=[.18 .965 .1 .025]; -posInfo.checkbox3=[.18 .94 .1 .025]; -posInfo.checkbox4=[.26 .965 .1 .025]; -posInfo.checkbox5=[.26 .94 .1 .025]; -posInfo.checkbox6=[.34 .965 .1 .025]; -posInfo.checkbox7=[.34 .94 .1 .025]; -posInfo.checkbox8=[.42 .965 .1 .025]; -posInfo.checkbox9=[.42 .94 .1 .025]; -posInfo.checkbox13=[.50 .965 .06 .025];%m4 -posInfo.checkbox12=[.50 .94 .06 .025];%m3 -posInfo.checkbox11=[.58 .965 .06 .025];%m2 -posInfo.checkbox10=[.58 .94 .06 .025]; %m1 -posInfo.checkbox14=[.66 .965 .06 .025]; -posInfo.checkbox15=[.66 .94 .06 .025]; - -posInfo.maxYtext = [.70 .965 .04 .025]; -posInfo.maxYinput = [.735 .965 .025 .025]; - -posInfo.nCols_text = [.70 .94 .04 .025]; -posInfo.nCols_input = [.735 .94 .025 .025]; +% Checkbox bar — pixel-based sizes +chkW = 130/screensz(3); chkH = rh; chkMotW = 100/screensz(3); +chkEdtW = 45/screensz(3); chkTxtW = 65/screensz(3); +tbOff = 40/screensz(4); % toolbar offset +chkRow1 = 1 - tbOff; chkRow2 = chkRow1 - rs; +chkX = 0.10; +posInfo.checkbox0=[chkX chkRow1 chkW chkH]; +posInfo.checkbox1=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; +posInfo.checkbox2=[chkX chkRow1 chkW chkH]; +posInfo.checkbox3=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; +posInfo.checkbox4=[chkX chkRow1 chkW chkH]; +posInfo.checkbox5=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; +posInfo.checkbox6=[chkX chkRow1 chkW chkH]; +posInfo.checkbox7=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; +posInfo.checkbox8=[chkX chkRow1 chkW chkH]; +posInfo.checkbox9=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; +posInfo.checkbox13=[chkX chkRow1 chkMotW chkH]; %m4 +posInfo.checkbox12=[chkX chkRow2 chkMotW chkH]; %m3 chkX=chkX+chkMotW; +posInfo.checkbox11=[chkX chkRow1 chkMotW chkH]; %m2 +posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; %m1 chkX=chkX+chkMotW; +posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; +posInfo.checkbox15=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; + +posInfo.maxYtext = [chkX chkRow1 chkTxtW chkH]; +posInfo.maxYinput = [chkX+chkTxtW chkRow1 chkEdtW chkH]; +posInfo.nCols_text = [chkX chkRow2 chkTxtW chkH]; +posInfo.nCols_input = [chkX+chkTxtW chkRow2 chkEdtW chkH]; posInfo.YTstick = [cpL+.005 vPos-0.39 .05 .085]; posInfo.RPstick = [cpL+cpW/2 vPos-0.39 .05 .085]; -posInfo.linepos1=[0.095 0.685 0.77 0.21]; -posInfo.linepos2=[0.095 0.47 0.77 0.21]; -posInfo.linepos3=[0.095 0.255 0.77 0.21]; +% Plot positions computed from checkbox bar (dynamic for toolbar offset) +posInfo.slider = [0.0826 chkRow2-2*cpMv-0.02 0.787 0.02]; +plotTop = posInfo.slider(2) - 0.005; +gapV = 0.005; +linepos4H = 0.11; +plotH = (plotTop - 0.1 - linepos4H - 4*gapV) / 3; +posInfo.linepos1=[0.095 plotTop-plotH 0.77 plotH]; +posInfo.linepos2=[0.095 plotTop-2*plotH-gapV 0.77 plotH]; +posInfo.linepos3=[0.095 plotTop-3*plotH-2*gapV 0.77 plotH]; +posInfo.linepos4=[0.095 0.1 0.77 linepos4H]; -posInfo.linepos4=[0.095 0.1 0.77 0.11];% - -fullszPlot = [0.095 0.255 0.77 0.63]; +fullszPlot = [0.095 posInfo.linepos3(2) 0.77 plotTop-posInfo.linepos3(2)]; if ~exist('checkpanel','var') || ~ishandle(checkpanel) +chkPanelW = chkX + chkTxtW + chkEdtW + cpM - 0.096; checkpanel = uipanel('Title','','FontSize',fontsz,... 'BackgroundColor',panelBg,'ForegroundColor',panelFg,... 'HighlightColor',panelBorder,... - 'Position',[.096 .932 .68 .065]); + 'Position',[0.096 chkRow2-cpMv chkPanelW chkRow1+rh+cpMv-chkRow2+cpMv]); guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox0],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); @@ -93,8 +104,8 @@ 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; -guiHandles.FileNum = uicontrol(PSfig,'Style','popupmenu','string',[fnameMaster],'TooltipString', [TooltipString_FileNum],... - 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameAText],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); +set(guiHandles.FileNum, 'string', fnameMaster, 'TooltipString', TooltipString_FileNum,... + 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... @@ -121,8 +132,10 @@ end % set IND for data subset. Updated in logviewer. -for f = 1 : Nfiles - tIND{f} = tta{f} > (epoch1_A(f)*us2sec) & tta{f} < (epoch2_A(f)*us2sec); +if exist('tta','var') && iscell(tta) + for f = 1 : min(Nfiles, numel(tta)) + tIND{f} = tta{f} > (epoch1_A(f)*us2sec) & tta{f} < (epoch2_A(f)*us2sec); + end end try set(guiHandles.maxY_input, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'LogViewer-Ymax'))))), catch, end diff --git a/src/util/PSregisterResize.m b/src/util/PSregisterResize.m new file mode 100644 index 0000000..58f9d20 --- /dev/null +++ b/src/util/PSregisterResize.m @@ -0,0 +1,14 @@ +function PSregisterResize(fig, cpPx, cpItems, mode, topBarL) +%% PSregisterResize - register CP items for PSresizeCP callback +cpd = struct('px', cpPx, 'items', {cpItems}, 'mode', mode); +if nargin >= 5 + cpd.topBarL = topBarL; +end +setappdata(fig, 'PScp', cpd); +try set(fig, 'SizeChangedFcn', @PSresizeCP); catch + try set(fig, 'ResizeFcn', @PSresizeCP); catch, end +end +% Ensure figure is rendered at final size before computing layout +drawnow; +PSresizeCP(fig, []); +end diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m new file mode 100644 index 0000000..3694613 --- /dev/null +++ b/src/util/PSresetData.m @@ -0,0 +1,40 @@ +%% PSresetData - clear all loaded data and reset UI state +% Called from Reset button and firmware-change dialog + +clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo; +clear rollPIDF pitchPIDF yawPIDF filenameA fnameMaster loaded_firmware; +clear debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis; +clear notchData rpmFilterData ampmat freq2d2 amp2d2 specMat; +clear delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02; +clear gyro_phase_shift_deg dterm_phase_shift_deg; +clear tuneCrtlpanel_init setupInfoWidgets_init; + +fcnt = 0; filenameA = {}; fnameMaster = {}; Nfiles = 0; expandON = 0; + +try, delete(checkpanel); clear checkpanel; catch, end +try + delete(subplot('position', posInfo.linepos1)); + delete(subplot('position', posInfo.linepos2)); + delete(subplot('position', posInfo.linepos3)); + delete(subplot('position', posInfo.linepos4)); +catch, end + +% close all secondary figures +figs = findobj('Type', 'figure'); +for fi = 1:numel(figs) + if figs(fi) ~= PSfig + try, close(figs(fi)); catch, end + end +end + +% clear secondary figure and panel handles +clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp; +clear errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel; +clear freqTimeCrtlpanel tuneCrtlpanel; + +% reset UI +set(guiHandles.FileNum, 'String', ' '); +try + set(guiHandles.Epoch1_A_Input, 'String', ' '); + set(guiHandles.Epoch2_A_Input, 'String', ' '); +catch, end diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m new file mode 100644 index 0000000..c458b0b --- /dev/null +++ b/src/util/PSresizeCP.m @@ -0,0 +1,153 @@ +function PSresizeCP(fig, ~) +%% PSresizeCP - resize callback: keeps CP elements at fixed pixel sizes +cpd = getappdata(fig, 'PScp'); +if isempty(cpd), return; end + +figPos = get(fig, 'Position'); +figW = figPos(3); figH = figPos(4); +if figW < 300 || figH < 200, return; end + +px = cpd.px; +cpW = px.cpW / figW; +cpL = 1 - cpW - px.cpM / figW; +cpM = px.cpM / figW; +cpMv = px.cpM / figH; +rh = px.rh / figH; +rs = px.rs / figH; +ddh = px.ddh / figH; +cbW = px.cbW / figW; +rhs = px.rhs / figH; +cpTitleH = px.cpTitle / figH; +vPos = 1 - cpTitleH - cpMv; +hw = cpW/2 - cpM; +fw = cpW - 2*cpM; +gap = rs - rh; + +mode = 'rows'; +if isfield(cpd, 'mode'), mode = cpd.mode; end + +if strcmp(mode, 'seq') + % Sequential layout: items placed top-to-bottom, each with its own height + tbOff = 40 / figH; % toolbar offset + vPos = 1 - tbOff - cpTitleH - cpMv; + yTop = vPos; + panelH = 0; panelBot = 0; + for i = 1:numel(cpd.items) + it = cpd.items{i}; + if ~ishandle(it.h), continue; end + try + hpx = it.hpx; + h = hpx / figH; + switch it.type + case 'full', pos = [cpL+cpM yTop-h fw h]; yTop = yTop-h-gap; + case 'left', pos = [cpL+cpM yTop-h hw h]; + case 'right', pos = [cpL+cpW/2 yTop-h hw h]; yTop = yTop-h-gap; + case 'cb', pos = [cpL+cpM+it.col*cbW yTop-h cbW h]; + case 'cb_end', pos = [cpL+cpM+it.col*cbW yTop-h cbW h]; yTop = yTop-h-gap; + case 'dd_full', pos = [cpL+cpM yTop-h fw h]; yTop = yTop-h-gap; + case 'dd_left', pos = [cpL+cpM yTop-h hw h]; + case 'dd_right',pos = [cpL+cpW/2 yTop-h hw h]; yTop = yTop-h-gap; + case 'text_left', pos = [cpL+cpM yTop-h cpW/4 h]; + case 'text_right', pos = [cpL+cpW/2 yTop-h cpW/4 h]; yTop = yTop-h-gap; + case 'input_left', pos = [cpL+cpM yTop-h cpW/4 h]; + case 'input_right',pos = [cpL+cpW/2 yTop-h cpW/4 h]; yTop = yTop-h-gap; + case 'panel' + continue; + otherwise, continue; + end + set(it.h, 'Position', pos); + catch + end + end + % Panel sized to wrap all items + for i = 1:numel(cpd.items) + it = cpd.items{i}; + if strcmp(it.type, 'panel') && ishandle(it.h) + panelBot = yTop - gap; + panelH = vPos - panelBot + cpTitleH; + set(it.h, 'Position', [cpL panelBot cpW panelH]); + end + end + +elseif strcmp(mode, 'topbar') + % Horizontal top-bar layout (PID Error, Flight Stats, Setup Info) + topBtnH = rh; + tbOff = 40 / figH; % toolbar offset + % Check if any items use 'lbl' type — if not, no label row above buttons + hasLbl = false; + for i = 1:numel(cpd.items), if strcmp(cpd.items{i}.type,'lbl'), hasLbl = true; break; end; end + if hasLbl + topLblY = 1 - tbOff - rhs - cpMv; + topBtnY = topLblY - rhs - cpMv; + else + topBtnY = 1 - tbOff - rh - cpMv; + topLblY = topBtnY; + end + topX = cpd.topBarL + cpM; + for i = 1:numel(cpd.items) + it = cpd.items{i}; + if ~ishandle(it.h), continue; end + try + w = it.wpx / figW; + switch it.type + case 'btn', pos = [topX topBtnY w topBtnH]; topX = topX+w+cpM; + case 'lbl', pos = [topX topLblY w rhs]; + case 'input', pos = [topX topBtnY w topBtnH]; topX = topX+w+cpM; + case 'cb', pos = [topX topBtnY w topBtnH]; topX = topX+w+cpM; + case 'dd', pos = [topX topBtnY w topBtnH]; topX = topX+w+cpM; + case 'panel' + panelW = topX - cpd.topBarL; + topPanelH = 1 - tbOff - topBtnY + cpMv; + pos = [cpd.topBarL topBtnY-cpMv panelW topPanelH]; + otherwise, continue; + end + set(it.h, 'Position', pos); + catch + end + end + +else + % Row-based layout (PIDscope.m main CP) + tbOff = 40 / figH; % toolbar offset + vPos = 1 - tbOff - cpTitleH - cpMv; + for i = 1:numel(cpd.items) + it = cpd.items{i}; + if ~ishandle(it.h), continue; end + try + row = it.row; + switch it.type + case 'full' + pos = [cpL+cpM vPos-rs*row fw rh]; + case 'left' + pos = [cpL+cpM vPos-rs*row hw rh]; + case 'right' + pos = [cpL+cpW/2 vPos-rs*row hw rh]; + case 'cb' + pos = [cpL+cpM+it.col*cbW vPos-rs*row cbW rh]; + case 'dd_left' + pos = [cpL+cpM vPos-rs*row hw rh]; + case 'dd_right' + pos = [cpL+cpW/2 vPos-rs*row hw rh]; + case 'panel' + cpH = rs * it.nrows + cpTitleH + cpMv; + pos = [cpL vPos-cpH+cpTitleH cpW cpH]; + case 'infotable' + cpH = rs * it.nrows + cpTitleH + cpMv; + cpBot = vPos - cpH + cpTitleH; + infoH = px.infoH / figH; + pos = [cpL cpBot-infoH-cpMv cpW infoH]; + case 'below_info' + cpH = rs * it.nrows + cpTitleH + cpMv; + cpBot = vPos - cpH + cpTitleH; + infoH = px.infoH / figH; + infoY = cpBot - infoH - cpMv; + pos = [cpL+cpM infoY-rh-cpMv fw rh]; + otherwise + continue; + end + set(it.h, 'Position', pos); + catch + end + end +end +end diff --git a/src/util/PSsaveSettings.m b/src/util/PSsaveSettings.m index e8deff2..33353b2 100644 --- a/src/util/PSsaveSettings.m +++ b/src/util/PSsaveSettings.m @@ -127,14 +127,8 @@ try defaults = readtable('PSdefaults.txt'); - a = char([cellstr([char(defaults.Parameters) num2str(defaults.Values)]); {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) catch - defaults = ' '; - a = char(['Unable to set user defaults '; {rdr}; {mdr}; {ldr}]); - t = uitable(PSfig,'ColumnWidth',{500},'ColumnFormat',{'char'},'Data',[cellstr(a)]); - set(t,'units','normalized','Position',infoTablePos,'FontSize',fontsz*.8, 'ColumnName', ['']) + defaults = ' '; end clear var diff --git a/src/util/PSstyleControls.m b/src/util/PSstyleControls.m index dde069a..e108212 100644 --- a/src/util/PSstyleControls.m +++ b/src/util/PSstyleControls.m @@ -4,7 +4,10 @@ function PSstyleControls(fig, th) controls = findobj(fig, 'Type', 'uicontrol'); for i = 1:numel(controls) style = get(controls(i), 'Style'); - if strcmp(style, 'pushbutton'), continue; end + if strcmp(style, 'pushbutton') + set(controls(i), 'BackgroundColor', th.btnBg); + continue; + end fg = get(controls(i), 'ForegroundColor'); % fix controls still at default black [0 0 0] if all(abs(fg) < 0.01) diff --git a/src/util/PStheme.m b/src/util/PStheme.m index deb0762..bdb40ac 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -27,9 +27,10 @@ th.epochAlpha = 0.7; % buttons -th.btnRun = [.20 .70 .30]; -th.btnReset = [.85 .55 .15]; -th.btnSave = [.65 .65 .65]; +th.btnBg = [.30 .30 .32]; +th.btnRun = [.15 .85 .25]; +th.btnReset = [.95 .55 .15]; +th.btnSave = [.70 .70 .70]; th.btnDash1 = [.85 .25 .25]; % Spectral Analyzer th.btnDash2 = [.25 .50 .90]; % Step Response th.btnDash3 = [.85 .55 .15]; % Filter Sim @@ -40,6 +41,13 @@ % font size - single source of truth screensz = get(0, 'ScreenSize'); th.fontsz = round(screensz(4) * .011); +% Octave Qt renders fonts bigger than MATLAB +if exist('OCTAVE_VERSION', 'builtin') + th.fontsz = round(th.fontsz * 0.85); +end + +% diff highlight (Setup Info) +th.diffBg = [.45 .18 .18]; % checkbox / input bg th.checkBg = [.18 .18 .20]; From 97ceee9dc9204b6eeeca4f44f7e21fcc8a922658 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 14:40:52 +0100 Subject: [PATCH 09/80] chore: remove dead code and unused compat shims --- src/compat/detectImportOptions.m | 40 -------------------------------- src/compat/fields.m | 6 ----- src/core/PSgetcsv.m | 1 - 3 files changed, 47 deletions(-) delete mode 100644 src/compat/detectImportOptions.m delete mode 100644 src/compat/fields.m diff --git a/src/compat/detectImportOptions.m b/src/compat/detectImportOptions.m deleted file mode 100644 index 2d64759..0000000 --- a/src/compat/detectImportOptions.m +++ /dev/null @@ -1,40 +0,0 @@ -function opts = detectImportOptions(filename) -%% DETECTIMPORTOPTIONS - Octave-compatible import options detector -% Simplified drop-in for MATLAB's detectImportOptions(). -% Reads the header row of a CSV file and returns options with VariableNames. -% -% Usage: -% opts = detectImportOptions(filename) - - fid = fopen(filename, 'r'); - if fid == -1 - error('detectImportOptions: cannot open file %s', filename); - end - - header_line = fgetl(fid); - fclose(fid); - - if ~ischar(header_line) - opts = struct('VariableNames', {{}}); - return; - end - - col_names = strsplit(strtrim(header_line), ','); - clean_names = cell(size(col_names)); - for i = 1:length(col_names) - s = strtrim(col_names{i}); - s = regexprep(s, '[\[\]\(\) ]+', '_'); - s = regexprep(s, '[^a-zA-Z0-9_]', '_'); - if ~isempty(s) && (s(1) >= '0' && s(1) <= '9') - s = ['x' s]; - end - s = regexprep(s, '_+', '_'); - if isempty(s) - s = sprintf('Var%d', i); - end - clean_names{i} = s; - end - - opts = struct('VariableNames', {clean_names}); - -end diff --git a/src/compat/fields.m b/src/compat/fields.m deleted file mode 100644 index 25041ee..0000000 --- a/src/compat/fields.m +++ /dev/null @@ -1,6 +0,0 @@ -function f = fields(s) -% fields - Octave compatibility shim, calls fieldnames -% Copyright (C) 2026 Grzegorz Sterniczuk -% License: GPL v3 (see LICENSE) - f = fieldnames(s); -end diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index 8782e3a..85b65c6 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -99,7 +99,6 @@ if emptysubfiles delete(files(k).name) - files(k).name; else f2(m,:)=files(k); m=m+1; From d6c68ec52251ab9684db62472aa3971441c300dd Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 15:06:38 +0100 Subject: [PATCH 10/80] fix(spectral): handle short/empty logs in Spectral Analyzer without crash --- src/core/PSSpec2d.m | 14 +++--- src/plot/PSplotSpec2D.m | 104 +++++++++++++++++++++++----------------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/core/PSSpec2d.m b/src/core/PSSpec2d.m index a5ce14f..e3f468d 100644 --- a/src/core/PSSpec2d.m +++ b/src/core/PSSpec2d.m @@ -9,12 +9,13 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- +N = length(Y); +if N < 4 + Fs = []; spec = []; + return; +end + if psd - % N = length(Y); - % [psdx,Fs] = periodogram(Y,[], N-1, F*1000,'psd'); % power psd - % [spec,Fs] = pspectrum(Y, F*1000, 'FrequencyResolution', 10); - - N = length(Y); Fs = ((F*1000)*(0:(N/2))/N); Y = Y.*hann(N)'; Y = fft(Y); @@ -23,8 +24,7 @@ psdx = psdx(1:N/2+1); % % scale to dB spec = 10 * log10(psdx)'; -else - N = length(Y); +else Fs = ((F*1000)*(0:(N/2))/N); Y = Y.*hann(N)'; Y = fft(Y); diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 179fd1f..931346e 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -46,46 +46,52 @@ try pg = smooth(T{k}.debug_0_(tIND{k}),50); catch - pg = 0; + pg = []; end - g1 = smooth(T{k}.gyroADC_0_(tIND{k}),50); - s1 = smooth(T{k}.setpoint_0_(tIND{k}),50); - g2 = smooth(T{k}.gyroADC_1_(tIND{k}),50); - s2 = smooth(T{k}.setpoint_1_(tIND{k}),50); - g3 = smooth(T{k}.gyroADC_2_(tIND{k}),50); - s3 = smooth(T{k}.setpoint_2_(tIND{k}),50); - - [c,lags] = xcorr(g1,pg,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, Debug01{k} = ' '; else Debug01{k} = num2str(d); end - - [c,lags] = xcorr(s1,pg,maxlag); - d = lags(find(c==max(c),1)); - d = d * (Fs / 1000); - if d<.1, Debug02{k} = ' '; else Debug02{k} = num2str(d); end - - [c,lags] = xcorr(g1,s1,maxlag); - d = lags(find(c==max(c),1)); d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,1) = 0; else, SPGyroDelay(k,1) = d; end - - [c,lags] = xcorr(g2,s2,maxlag); - d = lags(find(c==max(c),1)); d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,2) = 0; else, SPGyroDelay(k,2) = d; end - - [c,lags] = xcorr(g3,s3,maxlag); - d = lags(find(c==max(c),1)); d = d * (Fs / 1000); - if d<.1, SPGyroDelay(k,3) = 0; else, SPGyroDelay(k,3) = d; end - try - d1 = smooth(T{k}.axisDpf_0_(tIND{k}),50); - d2 = smooth(T{k}.axisD_0_(tIND{k}),50); - [c,lags] = xcorr(d2,d1,maxlag); - d = lags(find(c==max(c))); + g1 = smooth(T{k}.gyroADC_0_(tIND{k}),50); + s1 = smooth(T{k}.setpoint_0_(tIND{k}),50); + g2 = smooth(T{k}.gyroADC_1_(tIND{k}),50); + s2 = smooth(T{k}.setpoint_1_(tIND{k}),50); + g3 = smooth(T{k}.gyroADC_2_(tIND{k}),50); + s3 = smooth(T{k}.setpoint_2_(tIND{k}),50); + + if isempty(pg), pg = zeros(size(g1)); end + + [c,lags] = xcorr(g1,pg,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); - if d<.1, FilterDelayDterm{k} = ' '; else FilterDelayDterm{k} = num2str(d); end + if d<.1, Debug01{k} = ' '; else Debug01{k} = num2str(d); end + + [c,lags] = xcorr(s1,pg,maxlag); + d = lags(find(c==max(c),1)); + d = d * (Fs / 1000); + if d<.1, Debug02{k} = ' '; else Debug02{k} = num2str(d); end + + [c,lags] = xcorr(g1,s1,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,1) = 0; else, SPGyroDelay(k,1) = d; end + + [c,lags] = xcorr(g2,s2,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,2) = 0; else, SPGyroDelay(k,2) = d; end + + [c,lags] = xcorr(g3,s3,maxlag); + d = lags(find(c==max(c),1)); d = d * (Fs / 1000); + if d<.1, SPGyroDelay(k,3) = 0; else, SPGyroDelay(k,3) = d; end + + try + d1 = smooth(T{k}.axisDpf_0_(tIND{k}),50); + d2 = smooth(T{k}.axisD_0_(tIND{k}),50); + [c,lags] = xcorr(d2,d1,maxlag); + d = lags(find(c==max(c))); + d = d * (Fs / 1000); + if d<.1, FilterDelayDterm{k} = ' '; else FilterDelayDterm{k} = num2str(d); end + catch + FilterDelayDterm{k} = ' '; + end catch - FilterDelayDterm{k} = ' '; + Debug01{k} = ' '; Debug02{k} = ' '; FilterDelayDterm{k} = ' '; end try @@ -153,7 +159,13 @@ smat{p}=s; waitbar(min(1, p/(length(tmpSpecVal)*size(tmpFileVal,2)*length(axesOptionsSpec))), hw_fft, ['computing FFT... ' int2str(p)]); ff = ['f' int2str(f)]; - [freq2d2{p}.(ff) amp2d2{p}.(ff)] = PSSpec2d(dat,lograte, tmpPSDVal); + [tmpF tmpA] = PSSpec2d(dat,lograte, tmpPSDVal); + if isempty(tmpF) + smat{p}=[]; amp2d2{p}=[]; freq2d2{p}=[]; + else + freq2d2{p}.(ff) = tmpF; + amp2d2{p}.(ff) = tmpA; + end end end end @@ -345,15 +357,17 @@ end end end -if ~isempty(freq2d2) && ~isempty(amp2d2) - if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - h=legend(legnd); - hPos = get(h, 'Position'); set(h, 'Position', [0.35 0.01 hPos(3:4)]); - else - h=legend(legnd, 'Location','NorthEast') +try + if ~isempty(freq2d2) && ~isempty(amp2d2) + if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 + h=legend(legnd); + hPos = get(h, 'Position'); set(h, 'Position', [0.35 0.01 hPos(3:4)]); + else + h=legend(legnd, 'Location','NorthEast'); + end + try PSstyleLegend(h, th); catch, end end - try PSstyleLegend(h, th); catch, end -end +catch, end allax = findobj(PSspecfig2, 'Type', 'axes'); From 13e536947cbdfabcb006241644386145e8efd9e8 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 15:17:17 +0100 Subject: [PATCH 11/80] refactor: str2double, preallocate arrays, extract preset callback --- src/core/PSimport.m | 2 +- src/core/PSload.m | 4 ++-- src/core/PSstepcalc.m | 5 +++-- src/core/PSthrSpec.m | 4 ++-- src/plot/PSplotLogViewer.m | 4 ++-- src/plot/PSplotSpec2D.m | 8 ++++---- src/plot/PStuningParams.m | 2 +- src/ui/PSspecUIcontrol.m | 14 ++------------ src/util/PSapplySpecPreset.m | 23 +++++++++++++++++++++++ src/util/PSsaveSettings.m | 6 +++--- 10 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 src/util/PSapplySpecPreset.m diff --git a/src/core/PSimport.m b/src/core/PSimport.m index d707661..28ebaa6 100644 --- a/src/core/PSimport.m +++ b/src/core/PSimport.m @@ -40,7 +40,7 @@ end logEndPoints = find(cellfun(@(a)~isempty(a)&&a>0,a)); -relevantLogNum=str2num(csvFname(end-5:end-4)); +relevantLogNum=str2double(csvFname(end-5:end-4)); s=c{1}(logStartPoints(relevantLogNum):logEndPoints(relevantLogNum)); n=1; for m=1:size(s,1) diff --git a/src/core/PSload.m b/src/core/PSload.m index f0967bb..6b62803 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -147,7 +147,7 @@ %%%%%%%%%% collect debug mode info %%%%%%%%%% try - debugmode(fcnt) = str2num(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'debug_mode')),2))); + debugmode(fcnt) = str2double(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'debug_mode')),2))); catch % BF 2025.12+: GYRO_SCALED removed, use GYRO_FILTERED as default if debugIdx{fcnt}.GYRO_SCALED == -1 @@ -159,7 +159,7 @@ %%%%%%%%%% parse gyro_debug_axis (BF 2025.12+, for FFT_FREQ axis) %%%%%%%%%% try - gyro_debug_axis(fcnt) = str2num(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'gyro_debug_axis')),2))); + gyro_debug_axis(fcnt) = str2double(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'gyro_debug_axis')),2))); catch gyro_debug_axis(fcnt) = 0; % default Roll end diff --git a/src/core/PSstepcalc.m b/src/core/PSstepcalc.m index f5ca1e6..25c43a2 100644 --- a/src/core/PSstepcalc.m +++ b/src/core/PSstepcalc.m @@ -32,9 +32,10 @@ end segment_vector = 1 : round(segment_length/subsampleFactor) : length(SP); -NSegs = max(find((segment_vector+segment_length) < segment_vector(end))); +tmp = find((segment_vector+segment_length) < segment_vector(end)); +if isempty(tmp), NSegs = 0; else NSegs = max(tmp); end if NSegs > 0 - SPseg = []; GYseg = []; + SPseg = zeros(NSegs, segment_length+1); GYseg = zeros(NSegs, segment_length+1); j = 0; for i = 1 : NSegs if max(abs(SP(segment_vector(i):segment_vector(i)+segment_length))) >= minInput diff --git a/src/core/PSthrSpec.m b/src/core/PSthrSpec.m index 3a22380..0aa0596 100644 --- a/src/core/PSthrSpec.m +++ b/src/core/PSthrSpec.m @@ -48,8 +48,8 @@ for i = 1 : Tr clear tmp - if ~isempty(find(Thr_sort > i - wnd & Thr_sort <= i + wnd)) - ind = find(Thr_sort > i - wnd & Thr_sort <= i + wnd); + ind = find(Thr_sort > i - wnd & Thr_sort <= i + wnd); + if ~isempty(ind) for j = 1 : length(ind) try clear Ytmp Ytmp2 Y N Np Fs diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 4acebd6..5024267 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -14,11 +14,11 @@ th = PStheme(); global logviewerYscale - logviewerYscale = str2num(get(guiHandles.maxY_input, 'String')); + logviewerYscale = str2double(get(guiHandles.maxY_input, 'String')); figure(PSfig); - maxY=str2num(get(guiHandles.maxY_input, 'String')); + maxY=str2double(get(guiHandles.maxY_input, 'String')); alpha_red=.8; alpha_blue=.8; diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 931346e..dda9ede 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -95,11 +95,11 @@ end try - if ~isempty(str2num(Debug01{k})) && SPGyroDelay(k,1) > 0 - gyro_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2num(Debug01{k}), 1000/(SPGyroDelay(k,1)))); + if ~isempty(str2double(Debug01{k})) && ~isnan(str2double(Debug01{k})) && SPGyroDelay(k,1) > 0 + gyro_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2double(Debug01{k}), 1000/(SPGyroDelay(k,1)))); end - if ~isempty(str2num(FilterDelayDterm{k})) && SPGyroDelay(k,1) > 0 - dterm_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2num(FilterDelayDterm{k}), 1000/(SPGyroDelay(k,1)))); + if ~isempty(str2double(FilterDelayDterm{k})) && ~isnan(str2double(FilterDelayDterm{k})) && SPGyroDelay(k,1) > 0 + dterm_phase_shift_deg(k,1) = round(PSphaseShiftDeg(str2double(FilterDelayDterm{k}), 1000/(SPGyroDelay(k,1)))); end catch, end diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index e28719a..42feaa4 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -25,7 +25,7 @@ %%%%%%%%%%%%% step resp %%%%%%%%%%%%% figure(PStunefig) -ymax = str2num(get(guiHandlesTune.maxYStepInput, 'String')); +ymax = str2double(get(guiHandlesTune.maxYStepInput, 'String')); ypos = [(ymax/3)*2.9 (ymax/3)*1.85 (ymax/3)*.8]; hwarn=[]; if ~get(guiHandlesTune.clearPlots, 'Value') diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 52a1020..48ab2de 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -151,18 +151,8 @@ % 'fontsize',fontsz, 'units','normalized','Position', [posInfo.fnameASpec],'callback','PSplotSpec;'); guiHandlesSpec.specPresets = uicontrol(PSspecfig,'Style','popupmenu','string',{'Presets:'; '1. Gyro prefilt | Gyro | Dterm prefilt | Dterm' ; '2. Gyro prefilt | Gyro | Pterm | Dterm' ; '3. Gyro | Dterm | Set point | PID error' ; '4. A|A|B|B Gyro prefilt | Gyro' ; '5. A|A|B|B Dterm prefilt | Dterm' ; '6. A|B|C|D Gyro prefilt ' ;'7. A|B|C|D Gyro '; '8. A|B|C|D Dterm '; '9. A|B|C|D PID error'},... - 'fontsize',fontsz,'TooltipString', [TooltipString_presets], 'units','normalized','Position', [posInfo.specPresets],'callback',... - ['pv=get(guiHandlesSpec.specPresets,''Value''); ',... - 'if pv==1, set(guiHandlesSpec.SpecSelect{1},''Value'',1); set(guiHandlesSpec.SpecSelect{2},''Value'',1); set(guiHandlesSpec.SpecSelect{3},''Value'',1); set(guiHandlesSpec.SpecSelect{4},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==2, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==3, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',6); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==4, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',5); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',1); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',1); end;',... - 'if pv==5, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==6, set(guiHandlesSpec.SpecSelect{1},''Value'',8); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',8); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==7, set(guiHandlesSpec.SpecSelect{1},''Value'',3); set(guiHandlesSpec.SpecSelect{2},''Value'',3); set(guiHandlesSpec.SpecSelect{3},''Value'',3); set(guiHandlesSpec.SpecSelect{4},''Value'',3); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==8, set(guiHandlesSpec.SpecSelect{1},''Value'',2); set(guiHandlesSpec.SpecSelect{2},''Value'',2); set(guiHandlesSpec.SpecSelect{3},''Value'',2); set(guiHandlesSpec.SpecSelect{4},''Value'',2); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==9, set(guiHandlesSpec.SpecSelect{1},''Value'',7); set(guiHandlesSpec.SpecSelect{2},''Value'',7); set(guiHandlesSpec.SpecSelect{3},''Value'',7); set(guiHandlesSpec.SpecSelect{4},''Value'',7); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;',... - 'if pv==10, set(guiHandlesSpec.SpecSelect{1},''Value'',4); set(guiHandlesSpec.SpecSelect{2},''Value'',4); set(guiHandlesSpec.SpecSelect{3},''Value'',4); set(guiHandlesSpec.SpecSelect{4},''Value'',4); set(guiHandlesSpec.Sub100HzCheck{1},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{2},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{3},''Value'',0); set(guiHandlesSpec.Sub100HzCheck{4},''Value'',0); end;']); + 'fontsize',fontsz,'TooltipString', [TooltipString_presets], 'units','normalized','Position', [posInfo.specPresets],... + 'callback','PSapplySpecPreset(get(guiHandlesSpec.specPresets,''Value''), guiHandlesSpec);updateSpec=1;PSplotSpec;'); guiHandlesSpec.computeSpec = uicontrol(PSspecfig,'string','Run','fontsize',fontsz,'TooltipString', [TooltipString_specRun],'units','normalized','Position',[posInfo.computeSpec],... 'callback','PSplotSpec;'); diff --git a/src/util/PSapplySpecPreset.m b/src/util/PSapplySpecPreset.m new file mode 100644 index 0000000..fe9115c --- /dev/null +++ b/src/util/PSapplySpecPreset.m @@ -0,0 +1,23 @@ +function PSapplySpecPreset(pv, guiHandlesSpec) +% Apply Freq x Throttle preset to SpecSelect and Sub100Hz checkboxes +if pv < 2 + vals = [1 1 1 1]; sub = [0 0 0 0]; +else + switch pv + case 2, vals = [3 2 8 7]; sub = [0 0 0 0]; + case 3, vals = [3 2 6 7]; sub = [0 0 0 0]; + case 4, vals = [2 7 5 4]; sub = [0 0 1 1]; + case 5, vals = [3 2 3 2]; sub = [0 0 0 0]; + case 6, vals = [8 7 8 7]; sub = [0 0 0 0]; + case 7, vals = [3 3 3 3]; sub = [0 0 0 0]; + case 8, vals = [2 2 2 2]; sub = [0 0 0 0]; + case 9, vals = [7 7 7 7]; sub = [0 0 0 0]; + case 10, vals = [4 4 4 4]; sub = [0 0 0 0]; + otherwise, return; + end +end +for k = 1:4 + set(guiHandlesSpec.SpecSelect{k}, 'Value', vals(k)); + set(guiHandlesSpec.Sub100HzCheck{k}, 'Value', sub(k)); +end +end diff --git a/src/util/PSsaveSettings.m b/src/util/PSsaveSettings.m index 33353b2..e6d91ec 100644 --- a/src/util/PSsaveSettings.m +++ b/src/util/PSsaveSettings.m @@ -67,8 +67,8 @@ defaults.Values(5) = get(guiHandles.plotY, 'Value'); defaults.Values(6) = get(guiHandles.lineSmooth, 'Value'); defaults.Values(7) = get(guiHandles.linewidth, 'Value'); - defaults.Values(8) = str2num(get(guiHandles.maxY_input, 'String')); - defaults.Values(9) = str2num(get(guiHandles.nCols_input, 'String')); + defaults.Values(8) = str2double(get(guiHandles.maxY_input, 'String')); + defaults.Values(9) = str2double(get(guiHandles.nCols_input, 'String')); catch end try @@ -106,7 +106,7 @@ defaults.Values(30) = get(guiHandlesTune.plotP, 'Value'); defaults.Values(31) = get(guiHandlesTune.plotY, 'Value'); defaults.Values(32) = get(guiHandlesTune.RPYcombo, 'Value'); - defaults.Values(33) = str2num(get(guiHandlesTune.maxYStepInput, 'String')); + defaults.Values(33) = str2double(get(guiHandlesTune.maxYStepInput, 'String')); catch end From daeabdbb8e4dd62f568ebc94b186a2de2c153c76 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 15:55:38 +0100 Subject: [PATCH 12/80] perf(ui): cache smooth() in Log Viewer, remove eval() --- PIDscope.m | 4 +- src/plot/PSplotLogViewer.m | 92 +++++++++++++++++++------------------- src/util/PSresetData.m | 1 + src/util/PSsmoothLV.m | 23 ++++++++++ 4 files changed, 70 insertions(+), 50 deletions(-) create mode 100644 src/util/PSsmoothLV.m diff --git a/PIDscope.m b/PIDscope.m index 96a62fe..87792a0 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -119,9 +119,7 @@ filepathA=[]; filenameA={}; -hexpand1=[]; -hexpand2=[]; -hexpand3=[]; +hexpand = {[], [], []}; errmsg=[]; diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 5024267..917930c 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -102,9 +102,7 @@ lineStyle2LVnames = {'solid' ; 'dashed' ; 'dotted'}; axesOptionsLV = find([get(guiHandles.plotR, 'Value') get(guiHandles.plotP, 'Value') get(guiHandles.plotY, 'Value')]); - try delete(hexpand1); catch, end - try delete(hexpand2); catch, end - try delete(hexpand3); catch, end + for ei=1:3, try delete(hexpand{ei}); catch, end, end expandON = 0; ylabelname=''; @@ -127,12 +125,16 @@ delete(subplot('position',posInfo.linepos4)); end - for i = 1 : 19 - try - eval(['delete([hch' int2str(i) '])']) - catch - end - end + try delete(hch1); catch, end, try delete(hch2); catch, end + try delete(hch3); catch, end, try delete(hch4); catch, end + try delete(hch5); catch, end, try delete(hch6); catch, end + try delete(hch7); catch, end, try delete(hch8); catch, end + try delete(hch9); catch, end, try delete(hch10); catch, end + try delete(hch11); catch, end, try delete(hch12); catch, end + try delete(hch13); catch, end, try delete(hch14); catch, end + try delete(hch15); catch, end, try delete(hch16); catch, end + try delete(hch17); catch, end, try delete(hch18); catch, end + try delete(hch19); catch, end try % datacursormode not available in Octave @@ -142,32 +144,33 @@ cntLV = 0; lnstyle = lineStyleLV; - + LVpanels = {[], [], []}; + if exist('fnameMaster','var') && ~isempty(fnameMaster) - for ii = axesOptionsLV + for ii = axesOptionsLV if get(guiHandles.RPYcomboLV, 'Value'), expandON = 0; end - %%%%%%% + lpKey = ['linepos' int2str(ii)]; if ~get(guiHandles.RPYcomboLV, 'Value') && ~expandON - eval(['LVpanel' int2str(ii) '=subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) + LVpanels{ii} = subplot('position', posInfo.(lpKey)); LVpanel5 = subplot('position',posInfo.linepos4); end if ~get(guiHandles.RPYcomboLV, 'Value') && expandON try - eval(['subplot(hexpand' int2str(ii) ',' '''position''' ',expand_sz);']) - warning off + subplot(hexpand{ii}, 'position', expand_sz); catch end end - if eval(['~isempty(hexpand' int2str(ii) ') && ishandle(hexpand' int2str(ii) ') || ~expandON']) - + hexp = []; try hexp = hexpand{ii}; catch, end + if (~isempty(hexp) && ishandle(hexp)) || ~expandON + cntLV = cntLV + 1; - if get(guiHandles.RPYcomboLV, 'Value') - LVpanel4 = subplot('position' ,fullszPlot) + if get(guiHandles.RPYcomboLV, 'Value') + LVpanel4 = subplot('position' ,fullszPlot); lnstyle = lineStyle2LV; end if ~get(guiHandles.RPYcomboLV, 'Value') && expandON == 0 - eval(['LVpanel' int2str(ii) '= subplot(' '''position''' ',posInfo.linepos' int2str(ii) ');']) + LVpanels{ii} = subplot('position', posInfo.(lpKey)); lnstyle = lineStyleLV; end @@ -185,16 +188,17 @@ fileIdx = get(guiHandles.FileNum, 'Value'); lwVal = get(guiHandles.linewidth, 'Value')/2; - if get(guiHandles.checkbox0, 'Value'), hch1=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['debug_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox1, 'Value'), hch2=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['gyroADC_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox2, 'Value'), hch3=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisP_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox3, 'Value'), hch4=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisI_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox4, 'Value') && ii<3, hch5=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisDpf_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox5, 'Value') && ii<3, hch6=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisD_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox6, 'Value'), hch7=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['axisF_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox7, 'Value'), hch8=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['setpoint_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox8, 'Value'), hch9=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['pidsum_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox9, 'Value'), hch10=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.(['piderr_' int2str(ii-1) '_']), sFactor, 'loess'));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + tSec = tta{fileIdx}/us2sec; + if get(guiHandles.checkbox0, 'Value'), hch1=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['debug_' int2str(ii-1) '_'], sFactor));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox1, 'Value'), hch2=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['gyroADC_' int2str(ii-1) '_'], sFactor));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox2, 'Value'), hch3=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisP_' int2str(ii-1) '_'], sFactor));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox3, 'Value'), hch4=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisI_' int2str(ii-1) '_'], sFactor));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox4, 'Value') && ii<3, hch5=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisDpf_' int2str(ii-1) '_'], sFactor));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox5, 'Value') && ii<3, hch6=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisD_' int2str(ii-1) '_'], sFactor));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox6, 'Value'), hch7=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisF_' int2str(ii-1) '_'], sFactor));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox7, 'Value'), hch8=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['setpoint_' int2str(ii-1) '_'], sFactor));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox8, 'Value'), hch9=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['pidsum_' int2str(ii-1) '_'], sFactor));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox9, 'Value'), hch10=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['piderr_' int2str(ii-1) '_'], sFactor));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],th.epochFill); @@ -226,17 +230,17 @@ % Percent variables LVpanel5 = subplot('position',posInfo.linepos4); - if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_0_, sFactor, 'loess'));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_1_, sFactor, 'loess'));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_2_, sFactor, 'loess'));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end - if get(guiHandles.checkbox13, 'Value'), try hch14=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_3_, sFactor, 'loess'));hold on;set(hch14,'color', [linec.col13],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_0_', sFactor));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_1_', sFactor));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_2_', sFactor));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox13, 'Value'), try hch14=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_3_', sFactor));hold on;set(hch14,'color', [linec.col13],'LineWidth',lwVal), catch, end, end % motor sigs 4-7 for x8 configuration - if get(guiHandles.checkbox10, 'Value'), try hch15=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_4_, sFactor, 'loess'));hold on;set(hch15,'color', [linec.col10],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox11, 'Value'), try hch16=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_5_, sFactor, 'loess'));hold on;set(hch16,'color', [linec.col11],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox12, 'Value'), try hch17=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_6_, sFactor, 'loess'));hold on;set(hch17,'color', [linec.col12],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox13, 'Value'), try hch18=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.motor_7_, sFactor, 'loess'));hold on;set(hch18,'color', [linec.col13],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox10, 'Value'), try hch15=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_4_', sFactor));hold on;set(hch15,'color', [linec.col10],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox11, 'Value'), try hch16=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_5_', sFactor));hold on;set(hch16,'color', [linec.col11],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox12, 'Value'), try hch17=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_6_', sFactor));hold on;set(hch17,'color', [linec.col12],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end + if get(guiHandles.checkbox13, 'Value'), try hch18=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_7_', sFactor));hold on;set(hch18,'color', [linec.col13],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox14, 'Value'), hch19=plot(tta{fileIdx}/us2sec, smooth(T{fileIdx}.setpoint_3_/10, sFactor, 'loess'));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end + if get(guiHandles.checkbox14, 'Value'), hch19=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'setpoint_3_', sFactor, 0.1));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end axis([0 xmax 0 100]) set(gca,'Color',th.axesBg); @@ -259,14 +263,8 @@ end try - if ii==1 && ~expandON - set(LVpanel1,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos1]); - end - if ii==2 && ~expandON - set(LVpanel2,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos2]); - end - if ii==3 && ~expandON - set(LVpanel3,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos3]); + if ~expandON && ~isempty(LVpanels{ii}) + set(LVpanels{ii},'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',posInfo.(['linepos' int2str(ii)])); end if ~expandON set(LVpanel5,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m index 3694613..52adec3 100644 --- a/src/util/PSresetData.m +++ b/src/util/PSresetData.m @@ -10,6 +10,7 @@ clear tuneCrtlpanel_init setupInfoWidgets_init; fcnt = 0; filenameA = {}; fnameMaster = {}; Nfiles = 0; expandON = 0; +try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end try, delete(checkpanel); clear checkpanel; catch, end try diff --git a/src/util/PSsmoothLV.m b/src/util/PSsmoothLV.m new file mode 100644 index 0000000..f456995 --- /dev/null +++ b/src/util/PSsmoothLV.m @@ -0,0 +1,23 @@ +function y = PSsmoothLV(fig, Tfile, fileIdx, fieldName, sFactor, scale) +%% PSsmoothLV - cached smooth() for Log Viewer performance +% Avoids recomputing loess on 100k+ samples when user only toggles checkboxes + +if nargin < 6, scale = 1; end + +sc = getappdata(fig, 'smoothCacheLV'); +if isempty(sc) || ~isfield(sc, 'fIdx') || sc.fIdx ~= fileIdx + sc = struct('fIdx', fileIdx); +end + +cacheKey = [fieldName '_s' int2str(sFactor)]; +if isfield(sc, cacheKey) + y = sc.(cacheKey); + return; +end + +raw = Tfile.(fieldName); +if scale ~= 1, raw = raw * scale; end +y = smooth(raw, sFactor, 'loess'); +sc.(cacheKey) = y; +setappdata(fig, 'smoothCacheLV', sc); +end From 06d2d438611ed91187e3156512fa474b7e919527 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 16:36:51 +0100 Subject: [PATCH 13/80] fix(ui): add file dropdowns to PID Error/Stats, fix checkbox layout and dark theme colors --- src/plot/PSplotLogViewer.m | 4 ++-- src/plot/PSplotPIDerror.m | 12 ++++++++---- src/plot/PSplotStats.m | 8 ++++++-- src/ui/PSerrUIcontrol.m | 23 ++++++++++++++++++++--- src/ui/PSstatsUIcontrol.m | 22 ++++++++++++++++++++-- src/ui/PSviewerUIcontrol.m | 8 ++++---- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 917930c..1ea544a 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -264,10 +264,10 @@ try if ~expandON && ~isempty(LVpanels{ii}) - set(LVpanels{ii},'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',posInfo.(['linepos' int2str(ii)])); + set(LVpanels{ii},'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',posInfo.(['linepos' int2str(ii)])); end if ~expandON - set(LVpanel5,'color',[1 1 1],'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); + set(LVpanel5,'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); end catch end diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index 812f7f0..ac8b291 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -15,8 +15,12 @@ if exist('fnameMaster','var') && ~isempty(fnameMaster) - fA = 1; - fB = []; if Nfiles >= 2, fB = 2; end + fA = get(guiHandlesPIDerr.FileA, 'Value'); + fB = []; + if Nfiles >= 2 && isfield(guiHandlesPIDerr, 'FileB') && ishandle(guiHandlesPIDerr.FileB) + fB = get(guiHandlesPIDerr.FileB, 'Value'); + if fB == fA, fB = []; end + end axPIDerr = {'piderr_0_', 'piderr_1_', 'piderr_2_'}; axSP = {'setpoint_0_', 'setpoint_1_', 'setpoint_2_'}; @@ -50,7 +54,7 @@ end ylabel('normalized freq', 'fontweight', 'bold'); h = text(-37, .9, ylab2{p}); - set(h, 'fontsize', fontsz3, 'fontweight', 'bold'); + set(h, 'fontsize', fontsz3, 'fontweight', 'bold', 'color', th.textPrimary); grid on; axis([-40 40 0 1]); h = text(10, .9, ['[1]s.d.=' num2str(round(std(piderr_A(mask_A))*10)/10)]); @@ -74,7 +78,7 @@ [~, pval] = kstest2(yA, yB); if pval <= .05, sigflag = '*'; else, sigflag = ''; end h = text(10, .7, ['p=' num2str(pval) sigflag]); - set(h, 'fontsize', fontsz3, 'fontweight', 'bold'); + set(h, 'fontsize', fontsz3, 'fontweight', 'bold', 'color', th.textSecondary); catch end end diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index a14bfe8..bd68083 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -15,8 +15,12 @@ set(PSstatsfig, 'pointer', 'watch'); -fA = 1; -fB = []; if Nfiles >= 2, fB = 2; end +fA = get(guiHandlesStats.FileA, 'Value'); +fB = []; +if Nfiles >= 2 && isfield(guiHandlesStats, 'FileB') && ishandle(guiHandlesStats.FileB) + fB = get(guiHandlesStats.FileB, 'Value'); + if fB == fA, fB = []; end +end plotMode = get(guiHandlesStats.crossAxesStats, 'Value'); diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index 04754e0..d8b950f 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -43,15 +43,17 @@ % Top bar layout — pixel-based sizes topBtnW = 100/screensz(3); topBtnH = rh; topEdtW = 80/screensz(3); -topTxtW = 120/screensz(3); topBarL = 0.09; +topTxtW = 120/screensz(3); topDdW = 160/screensz(3); topBarL = 0.09; tbOff = 40/screensz(4); % toolbar offset topBtnY = 1 - tbOff - rh - cpMv; topX = topBarL + cpM; posInfo.refresh2= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; posInfo.saveFig3= [topX topBtnY topBtnW topBtnH]; topX=topX+topBtnW+cpM; posInfo.maxStick= [topX topBtnY topEdtW topBtnH]; topX=topX+topEdtW+cpM; -posInfo.maxSticktext=[topX topBtnY topTxtW topBtnH]; -topPanelW = topX + topTxtW + cpM - topBarL; +posInfo.maxSticktext=[topX topBtnY topTxtW topBtnH]; topX=topX+topTxtW+cpM; +posInfo.errFileA= [topX topBtnY topDdW ddh]; topX=topX+topDdW+cpM; +posInfo.errFileB= [topX topBtnY topDdW ddh]; +topPanelW = topX + topDdW + cpM - topBarL; topPanelH = 1 - tbOff - topBtnY + cpMv; if ~exist('errCrtlpanel','var') || ~ishandle(errCrtlpanel) @@ -71,6 +73,17 @@ guiHandlesPIDerr.saveFig3 = uicontrol(PSerrfig,'string','Save Fig','fontsize',fontsz3,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig3],... 'callback','PSsaveFig;'); set(guiHandlesPIDerr.saveFig3, 'ForegroundColor', saveCol); + +guiHandlesPIDerr.FileA = uicontrol(PSerrfig,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz3,'TooltipString','File A (red)','units','normalized','Position',[posInfo.errFileA],... + 'callback','updateErr=0;PSplotPIDerror;'); +set(guiHandlesPIDerr.FileA, 'Value', 1); +if Nfiles > 1 + guiHandlesPIDerr.FileB = uicontrol(PSerrfig,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz3,'TooltipString','File B (blue)','units','normalized','Position',[posInfo.errFileB],... + 'callback','updateErr=0;PSplotPIDerror;'); + set(guiHandlesPIDerr.FileB, 'Value', min(2, Nfiles)); +end end % ishandle(errCrtlpanel) % Register top bar for fixed-pixel resize @@ -81,6 +94,10 @@ cpI{end+1} = struct('h', guiHandlesPIDerr.saveFig3, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',100); cpI{end+1} = struct('h', guiHandlesPIDerr.maxStick, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',80); cpI{end+1} = struct('h', guiHandlesPIDerr.maxSticktext, 'type','btn', 'row',0, 'col',0, 'hpx',0, 'wpx',120); +cpI{end+1} = struct('h', guiHandlesPIDerr.FileA, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +if Nfiles > 1 && isfield(guiHandlesPIDerr, 'FileB') && ishandle(guiHandlesPIDerr.FileB) + cpI{end+1} = struct('h', guiHandlesPIDerr.FileB, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +end cpI{end+1} = struct('h', errCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); PSregisterResize(PSerrfig, cpPx, cpI, 'topbar', topBarL); diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index b1605ce..3b64083 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -81,8 +81,11 @@ posInfo.crossAxesStats_text = [topX topLblY topTxtW rhs]; posInfo.crossAxesStats_input = [topX topBtnY topEdtW topBtnH]; topX=topX+topEdtW+cpM; posInfo.crossAxesStats_text2 = [topX topLblY topTxtW rhs]; -posInfo.crossAxesStats_input2 = [topX topBtnY topEdtW topBtnH]; -topPanelW = topX + topEdtW + cpM - topBarL; +posInfo.crossAxesStats_input2 = [topX topBtnY topEdtW topBtnH]; topX=topX+topEdtW+cpM; +topDdW2 = 160/screensz(3); +posInfo.statsFileA = [topX topBtnY topDdW2 ddh]; topX=topX+topDdW2+cpM; +posInfo.statsFileB = [topX topBtnY topDdW2 ddh]; +topPanelW = topX + topDdW2 + cpM - topBarL; if ~exist('statsCrtlpanel','var') || ~ishandle(statsCrtlpanel) statsCrtlpanel = uipanel('Title','','FontSize',fontsz5,... @@ -111,6 +114,17 @@ guiHandlesStats.crossAxesStats_text2 = uicontrol(PSstatsfig,'style','text','string','alpha','fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text2]); guiHandlesStats.crossAxesStats_input2 = uicontrol(PSstatsfig,'style','edit','string',[num2str(zTransparency)],'fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','Position',[posInfo.crossAxesStats_input2],... 'callback','zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); + +guiHandlesStats.FileA = uicontrol(PSstatsfig,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz5,'TooltipString','File A (red)','units','normalized','Position',[posInfo.statsFileA],... + 'callback','updateStats=0;PSplotStats;'); +set(guiHandlesStats.FileA, 'Value', 1); +if Nfiles > 1 + guiHandlesStats.FileB = uicontrol(PSstatsfig,'Style','popupmenu','string',[fnameMaster],... + 'fontsize',fontsz5,'TooltipString','File B (blue)','units','normalized','Position',[posInfo.statsFileB],... + 'callback','updateStats=0;PSplotStats;'); + set(guiHandlesStats.FileB, 'Value', min(2, Nfiles)); +end end % ishandle(statsCrtlpanel) % Register top bar for fixed-pixel resize @@ -125,6 +139,10 @@ cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_input, 'type','input', 'row',0, 'col',0, 'hpx',0, 'wpx',50); cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_text2, 'type','lbl', 'row',0, 'col',0, 'hpx',0, 'wpx',50); cpI{end+1} = struct('h', guiHandlesStats.crossAxesStats_input2, 'type','input', 'row',0, 'col',0, 'hpx',0, 'wpx',50); +cpI{end+1} = struct('h', guiHandlesStats.FileA, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +if Nfiles > 1 && isfield(guiHandlesStats, 'FileB') && ishandle(guiHandlesStats.FileB) + cpI{end+1} = struct('h', guiHandlesStats.FileB, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); +end cpI{end+1} = struct('h', statsCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); PSregisterResize(PSstatsfig, cpPx, cpI, 'topbar', topBarL); diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 95f20f6..1c32e4b 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -25,10 +25,10 @@ posInfo.checkbox7=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; posInfo.checkbox8=[chkX chkRow1 chkW chkH]; posInfo.checkbox9=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; -posInfo.checkbox13=[chkX chkRow1 chkMotW chkH]; %m4 -posInfo.checkbox12=[chkX chkRow2 chkMotW chkH]; %m3 chkX=chkX+chkMotW; -posInfo.checkbox11=[chkX chkRow1 chkMotW chkH]; %m2 -posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; %m1 chkX=chkX+chkMotW; +posInfo.checkbox13=[chkX chkRow1 chkMotW chkH]; +posInfo.checkbox12=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkbox11=[chkX chkRow1 chkMotW chkH]; +posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; posInfo.checkbox15=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; From 4348516e7aa45a10628633ad2e5a1d9970906bc4 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 17:37:35 +0100 Subject: [PATCH 14/80] perf(parser): use --output-dir instead of copying decoder and log files --- PIDscope.m | 10 +++ src/core/PSgetcsv.m | 169 ++++++++++++++++---------------------- src/core/PSimport.m | 3 +- src/core/PSload.m | 37 ++------- src/core/PSquicJson2csv.m | 13 +-- 5 files changed, 99 insertions(+), 133 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index 87792a0..2de8ba8 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -337,6 +337,16 @@ || (~exist(fullfile(main_directory, 'blackbox_decode'), 'file') && ~exist(fullfile(main_directory, 'blackbox_decode.exe'), 'file')) main_directory = executableDir; end + +% Store decoder paths globally so PSgetcsv can use absolute paths +if ispc() + setappdata(0, 'PSdecoderPath', fullfile(main_directory, 'blackbox_decode.exe')); + setappdata(0, 'PSdecoderPathINAV', fullfile(main_directory, 'blackbox_decode_INAV.exe')); +else + setappdata(0, 'PSdecoderPath', fullfile(main_directory, 'blackbox_decode')); + setappdata(0, 'PSdecoderPathINAV', fullfile(main_directory, 'blackbox_decode_INAV')); +end + cd(configDir) try diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index 85b65c6..f5b00e9 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -1,6 +1,8 @@ -function [filename csvFnames] = PSgetcsv(filename, firmware_flag) -%% [filename csvFnames] = PSgetcsv(filename, firmware_flag) +function [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir) +%% [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir) % Converts bbl files to csv using blackbox_decode +% filename: full path to BBL/BFL/TXT/BTFL/JSON/BIN file +% outdir: directory for CSV output (default: same as input file) % ---------------------------------------------------------------------------------- % "THE BEER-WARE LICENSE" (Revision 42): @@ -11,124 +13,99 @@ fnums = 1; +filename_nchars = 17; -a=strfind(filename, ' ');% had to remove white space to run in bb decode -a=fliplr(a); -if ~isempty((find(isspace(filename)))) - filename2=filename(find(~isspace(filename)));% have to get rid of spaces to run blackbox_decode using 'system' function - movefile(filename,filename2);%rename file without spaces - filename=filename2; - clear filename2 -end +mainFname = filename; +[fdir, fname, fext] = fileparts(filename); -filename_nchars = 17; -if size(filename,2)>20 - f=[filename(1:filename_nchars) '...']; - else - f=filename; +if nargin < 3 || isempty(outdir) + outdir = fdir; end - -mainFname=filename; -[fdir, fname, fext] = fileparts(filename); -fbase = fullfile(fdir, fname); + if strcmpi(fext, '.bin') - % ArduPilot DataFlash binary log - handled directly in PSload csvFnames = {filename}; return; elseif strcmpi(fext, '.json') - % QuickSilver JSON blackbox export - parse directly - [headerFile, csvFile] = PSquicJson2csv(filename); + [headerFile, csvFile] = PSquicJson2csv(filename, outdir); filename = headerFile; files(1).name = csvFile; fnums = 1; elseif any(strcmpi(fext, {'.BFL', '.BBL', '.TXT', '.BTFL'})) - if ispc() - bb_decode = 'blackbox_decode.exe'; - bb_decode_inav = 'blackbox_decode_INAV.exe'; - else - bb_decode = './blackbox_decode'; - bb_decode_inav = './blackbox_decode_INAV'; - end - if firmware_flag == 3 - [status,result]=system([bb_decode_inav ' ' filename ' 2>&1']); + decoder_path = getappdata(0, 'PSdecoderPath'); + decoder_inav = getappdata(0, 'PSdecoderPathINAV'); + + if firmware_flag == 3 && ~isempty(decoder_inav) + cmd = ['"' decoder_inav '" --output-dir "' outdir '" "' filename '" 2>&1']; else - [status,result]=system([bb_decode ' ' filename ' 2>&1']); + cmd = ['"' decoder_path '" --output-dir "' outdir '" "' filename '" 2>&1']; end - files=dir([fbase '*.csv']); - - % only choose files that don't have .bbl or .bfl extension - clear f2;m=1; - for k=1:size(files,1) - if ~contains(files(k).name,'.bbl','IgnoreCase',true) & ~contains(files(k).name,'.bfl','IgnoreCase',true) - f2(m,:)=files(k); - m=m+1; + [status, result] = system(cmd); + + fbase = fullfile(outdir, fname); + files = dir([fbase '*.csv']); + + % filter out files with .bbl or .bfl in name + valid = true(size(files,1), 1); + for k = 1:size(files,1) + if contains(files(k).name, '.bbl', 'IgnoreCase', true) || contains(files(k).name, '.bfl', 'IgnoreCase', true) + valid(k) = false; end end - % report to user, the most common loading error - try - files=f2;clear f2; - catch % report blackbox_decode error to user - set(gcf, 'pointer', 'arrow') - end - - % get rid of all event files and gps.gpx files - fevt=dir([fbase '*.event']); - for k=1:size(fevt,1) - delete([fevt(k).name]); - end - fevt=dir([fbase '*.gps.gpx']); - for k=1:size(fevt,1) - delete([fevt(k).name]); - end - fevt=dir([fbase '*.gps.csv']); - for k=1:size(fevt,1) - delete([fevt(k).name]); + files = files(valid, :); + + if isempty(files) + set(gcf, 'pointer', 'arrow'); + csvFnames = {}; + return; end - - % get list of files after erasing junk + + % clean up junk files in outdir + fevt = dir([fbase '*.event']); + for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end + fevt = dir([fbase '*.gps.gpx']); + for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end + fevt = dir([fbase '*.gps.csv']); + for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end + + % refresh file list after cleanup files = dir([fbase '*.csv']); - - % if more than one file + if size(files,1) > 1 - x=size(files,1); - clear f2; m=1; - for k=1:x - % Check file size instead of parsing entire CSV (much faster for large files) - emptysubfiles = (files(k).bytes < 1000); - - if emptysubfiles - delete(files(k).name) - else - f2(m,:)=files(k); - m=m+1; - end - end - files=f2;clear f2 - a=strfind(result,'duration'); - logDurStr=''; - for d=1:length(a) - logDurStr{d}=[int2str(d) ') ' result(a(d):a(d)+filename_nchars)]; + % remove empty subfiles (<1KB) + valid = true(size(files,1), 1); + for k = 1:size(files,1) + if files(k).bytes < 1000 + delete(fullfile(outdir, files(k).name)); + valid(k) = false; + end end - - if size(files,1)>0 - x=size(files,1); - if x>1 % if multiple logs exist in BB file - [fnums , tf] = listdlg('ListString',logDurStr, 'ListSize',[250,round(size(logDurStr,2) * 20)], 'Name','Select file(s): ' ); - %%%% delete all unused BB decoded csv files - for k=1:x - if ~ismember(k, fnums), delete(files(k).name); end + files = files(valid, :); + + a = strfind(result, 'duration'); + logDurStr = ''; + for d = 1:length(a) + logDurStr{d} = [int2str(d) ') ' result(a(d):a(d)+filename_nchars)]; + end + + if size(files,1) > 0 + x = size(files,1); + if x > 1 + [fnums, tf] = listdlg('ListString', logDurStr, 'ListSize', [250, round(size(logDurStr,2)*20)], 'Name', 'Select file(s): '); + for k = 1:x + if ~ismember(k, fnums), delete(fullfile(outdir, files(k).name)); end end end else - validData=0; - a=errordlg(['no valid data in ' mainFname]);pause(3);close(a); - end + validData = 0; + a = errordlg(['no valid data in ' mainFname]); pause(3); close(a); + end end end -csvFnames={}; -for k = 1 : length(fnums) - csvFnames{k} = files(fnums(k)).name; + +csvFnames = {}; +for k = 1:length(fnums) + csvFnames{k} = fullfile(outdir, files(fnums(k)).name); end -end \ No newline at end of file +end diff --git a/src/core/PSimport.m b/src/core/PSimport.m index 28ebaa6..5b0a42e 100644 --- a/src/core/PSimport.m +++ b/src/core/PSimport.m @@ -9,7 +9,8 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- -newFileName=csvFname; +[~, nfn, nfx] = fileparts(csvFname); +newFileName = [nfn nfx]; T=readtable(csvFname); delete(csvFname);% we r done with this-dont wanna leave junk on main directory diff --git a/src/core/PSload.m b/src/core/PSload.m index 6b62803..7080991 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -78,37 +78,14 @@ n = size(filenameA,2); waitbarFid = waitbar(0,'Please wait...'); - % Work in temp dir (main_directory may be read-only in AppImage) workdir = tempname(); mkdir(workdir); - prev_dir = pwd(); - - % Copy blackbox_decode into workdir so ./blackbox_decode works - if ispc() - decoders = {'blackbox_decode.exe', 'blackbox_decode_INAV.exe'}; - else - decoders = {'blackbox_decode', 'blackbox_decode_INAV'}; - end - for dec = decoders - src = fullfile(main_directory, dec{1}); - if exist(src, 'file') - copyfile(src, workdir); - end - end - - cd(workdir); for ii = 1 : n - source = fullfile(logfile_directory, filenameA{ii}); - try - copyfile(source, workdir); - catch e - warning('PSload: cannot copy %s to workdir: %s', source, e.message); - continue; - end + srcFile = fullfile(logfile_directory, filenameA{ii}); clear subFiles; - [filenameA{ii} subFiles] = PSgetcsv(filenameA{ii}, get(guiHandles.Firmware, 'Value')); + [filenameA{ii} subFiles] = PSgetcsv(srcFile, get(guiHandles.Firmware, 'Value'), workdir); for jj = 1 : size(subFiles,2) @@ -120,12 +97,12 @@ [~, ~, sfext] = fileparts(subFiles{jj}); if strcmpi(sfext, '.bin') % ArduPilot DataFlash binary - direct parse - binpath = fullfile(logfile_directory, subFiles{jj}); - [ardu_data, ardu_parms] = PSarduRead(binpath); + [ardu_data, ardu_parms] = PSarduRead(subFiles{jj}); [T{fcnt}, SetupInfo{fcnt}, A_lograte(fcnt)] = PSarduConvert(ardu_data, ardu_parms); - fnameMaster{fcnt} = subFiles{jj}; + [~, sfname, sfx] = fileparts(subFiles{jj}); + fnameMaster{fcnt} = [sfname sfx]; else - [dataA(fcnt) fnameMaster{fcnt}] = PSimport(subFiles{jj}, char(filenameA{ii})); + [dataA(fcnt) fnameMaster{fcnt}] = PSimport(subFiles{jj}, filenameA{ii}); T{fcnt}=dataA(fcnt).T; A_lograte(fcnt)=round((1000/median(diff(T{fcnt}.time_us_-T{fcnt}.time_us_(1)))) * 10) / 10; SetupInfo{fcnt}=dataA(fcnt).SetupInfo; @@ -253,13 +230,11 @@ end end % Clean up workdir - cd(prev_dir); try if ispc(), system(['rmdir /s /q "' workdir '"']); else system(['rm -rf ' workdir]); end; catch, end end try close(waitbarFid), catch, end catch ME - try cd(prev_dir); catch, end try if ispc(), system(['rmdir /s /q "' workdir '"']); else system(['rm -rf ' workdir]); end; catch, end try close(waitbarFid); catch, end warning('PSload error: %s', ME.message); diff --git a/src/core/PSquicJson2csv.m b/src/core/PSquicJson2csv.m index a3f231e..80d91e7 100644 --- a/src/core/PSquicJson2csv.m +++ b/src/core/PSquicJson2csv.m @@ -1,4 +1,4 @@ -function [headerFile, csvFile] = PSquicJson2csv(jsonFile) +function [headerFile, csvFile] = PSquicJson2csv(jsonFile, outdir) %% PSquicJson2csv - Convert QuickSilver JSON blackbox export to CSV % % Reads JSON exported by BossHobby Configurator ("QUIC download") and @@ -11,7 +11,7 @@ % PID terms: divided by 1000 (stored as int16*1000) % throttle setpoint: 0-1 -> 0-1000 (BF scale) % -% [headerFile, csvFile] = PSquicJson2csv(jsonFile) +% [headerFile, csvFile] = PSquicJson2csv(jsonFile, outdir) RAD2DEG = 180 / pi; @@ -129,8 +129,11 @@ end % Build CSV output - [fdir, fname, ~] = fileparts(jsonFile); - csvFile = fullfile(fdir, [fname '.01.csv']); + [~, fname, ~] = fileparts(jsonFile); + if nargin < 2 || isempty(outdir) + [outdir, ~, ~] = fileparts(jsonFile); + end + csvFile = fullfile(outdir, [fname '.01.csv']); % Column names matching blackbox_decode output header = 'loopIteration,time (us),axisP[0],axisP[1],axisP[2],axisI[0],axisI[1],axisI[2],axisD[0],axisD[1],axisD[2],gyroADC[0],gyroADC[1],gyroADC[2],rcCommand[0],rcCommand[1],rcCommand[2],rcCommand[3],setpoint[0],setpoint[1],setpoint[2],setpoint[3],motor[0],motor[1],motor[2],motor[3],debug[0],debug[1],debug[2],debug[3]'; @@ -145,7 +148,7 @@ dlmwrite(csvFile, M, '-append', 'delimiter', ',', 'precision', '%.6g'); % Write synthetic header file (mimics BBL header for PSimport) - headerFile = fullfile(fdir, [fname '.quic_header.txt']); + headerFile = fullfile(outdir, [fname '.quic_header.txt']); fid = fopen(headerFile, 'w'); fprintf(fid, 'H Firmware version:QuickSilver\n'); fprintf(fid, 'H Firmware revision:QUICKSILVER\n'); From c0f4702ae91a632bea1d8c190716e02b58912b59 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 19:28:38 +0100 Subject: [PATCH 15/80] fix(ui): add drawnow after figure(1) to fix maximize race condition --- PIDscope.m | 1 + 1 file changed, 1 insertion(+) diff --git a/PIDscope.m b/PIDscope.m index 2de8ba8..7bdf011 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -91,6 +91,7 @@ %%%% assign main figure handle and define some UI variables PSfig = figure(1); +drawnow; % flush Qt event queue so figure is mapped before setting Position set(PSfig, 'InvertHardcopy', 'off'); th = PStheme(); bgcolor = th.figBg; From 66413a8100fb69675f1da68442534ebc846aa566 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 19:44:26 +0100 Subject: [PATCH 16/80] fix(filter-sim): fall back to BF 4.2 header names for filter params --- src/plot/PSfilterSim.m | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 249d450..ce1f545 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -186,14 +186,16 @@ function doUpdate() end function fp = parseFilterParams(si) - fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', 0); - fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', 0); - fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', 0); - fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', 0); - fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', 0); - fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', 100); - fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', 0); - fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', 0); + % BF 4.3+: gyro_lpf1_type / gyro_lpf1_static_hz + % BF 4.2: gyro_lowpass_type / gyro_lowpass_hz + fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', hval(si, 'gyro_lowpass_type', 0)); + fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', 0)); + fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', hval(si, 'gyro_lowpass2_type', 0)); + fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', 0)); + fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', hval(si, 'dterm_lowpass_type', 0)); + fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 100)); + fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', hval(si, 'dterm_lowpass2_type', 0)); + fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); fp.dterm_notch_cut = hval(si, 'dterm_notch_cutoff', 0); tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2num(tmp); From 90651002c722df8841dda08f24e1f5f64d77ff86 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 19:53:54 +0100 Subject: [PATCH 17/80] fix(filter-sim): correct BF filter type mapping (0=PT1 not OFF) --- src/plot/PSfilterSim.m | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index ce1f545..9e474aa 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -41,14 +41,14 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) % Gyro LPF1 mkSection(fig, '--- Gyro LPF1 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); row = row - rh - gap; -[h.glpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf1_type, cb, fontsz); +[h.glpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf1_type, fp.gyro_lpf1_hz, cb, fontsz); [h.glpf1_hz, h.glpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf1_hz, cb, fontsz); row = row - gap*2; % Gyro LPF2 mkSection(fig, '--- Gyro LPF2 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); row = row - rh - gap; -[h.glpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf2_type, cb, fontsz); +[h.glpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf2_type, fp.gyro_lpf2_hz, cb, fontsz); [h.glpf2_hz, h.glpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf2_hz, cb, fontsz); row = row - gap*2; @@ -69,14 +69,14 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) % D-term LPF1 mkSection(fig, '--- D-term LPF1 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); row = row - rh - gap; -[h.dlpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf1_type, cb, fontsz); +[h.dlpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf1_type, fp.dterm_lpf1_hz, cb, fontsz); [h.dlpf1_hz, h.dlpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf1_hz, cb, fontsz); row = row - gap*2; % D-term LPF2 mkSection(fig, '--- D-term LPF2 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); row = row - rh - gap; -[h.dlpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf2_type, cb, fontsz); +[h.dlpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf2_type, fp.dterm_lpf2_hz, cb, fontsz); [h.dlpf2_hz, h.dlpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf2_hz, cb, fontsz); row = row - gap*2; @@ -241,12 +241,18 @@ function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) 'FontSize', fsz, 'FontWeight', 'bold', 'BackgroundColor', bgc, 'ForegroundColor', col); end -function [hType, rowOut] = mkType(fig, cpL, row, rh, gap, bgc, fgc, initVal, cb, fsz) +function [hType, rowOut] = mkType(fig, cpL, row, rh, gap, bgc, fgc, initVal, initHz, cb, fsz) uicontrol(fig, 'Style', 'text', 'String', 'Type:', ... 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); + % BF header: 0=PT1, 1=Biquad, 2=PT2, 3=PT3; dropdown[1]=OFF + if initHz == 0 + ddVal = 1; % OFF + else + ddVal = min(initVal + 2, 5); + end hType = uicontrol(fig, 'Style', 'popupmenu', ... - 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', initVal + 1, ... + 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', ddVal, ... 'Units', 'normalized', 'Position', [cpL+.06 row .10 rh], 'FontSize', fsz, 'Callback', cb); rowOut = row - rh - gap; end From 5f9a075b20edd09646a2db9d14494f4909ed1621 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 8 Mar 2026 20:18:34 +0100 Subject: [PATCH 18/80] fix(parser): replace str2num with str2double across codebase --- src/core/PSprocess.m | 12 ++++++------ src/plot/PSfilterSim.m | 12 ++++++------ src/ui/PSfreqTimeUIcontrol.m | 6 +++--- src/ui/PSspec2DUIcontrol.m | 6 +++--- src/ui/PSspecUIcontrol.m | 14 +++++++------- src/ui/PSstatsUIcontrol.m | 4 ++-- src/ui/PStuneUIcontrol.m | 2 +- src/ui/PSviewerUIcontrol.m | 2 +- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/core/PSprocess.m b/src/core/PSprocess.m index 092c5c7..8d8c69e 100644 --- a/src/core/PSprocess.m +++ b/src/core/PSprocess.m @@ -19,14 +19,14 @@ epoch1_A=round(tta(1)/us2sec)+2; epoch2_A=round(tta(end)/us2sec)-2; guiHandles.Epoch1_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_A_Input],... - 'callback','@textinput_call; epoch1_A=str2num(get(guiHandles.Epoch1_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + 'callback','@textinput_call; epoch1_A=str2double(get(guiHandles.Epoch1_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... - 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + 'callback','@textinput_call;epoch2_A=str2double(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); end if (epoch2_A>round(tta(end)/us2sec)) epoch2_A=round(tta(end)/us2sec); guiHandles.Epoch2_A_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_A)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_A_Input],... - 'callback','@textinput_call;epoch2_A=str2num(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); + 'callback','@textinput_call;epoch2_A=str2double(get(guiHandles.Epoch2_A_Input, ''String'')); PSprocess;PSplotLogViewer;'); end x=[epoch1_A*us2sec epoch2_A*us2sec]; x2=tta>tta(find(tta>x(1),1)) & ttax(2),1)); @@ -69,14 +69,14 @@ epoch1_B=round(ttb(1)/us2sec)+2; epoch2_B=round(ttb(end)/us2sec)-2; guiHandles.Epoch1_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch1_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch1_B_Input],... - 'callback','@textinput_call; epoch1_B=str2num(get(guiHandles.Epoch1_B_Input, ''String''));PSprocess;PSplotLogViewer; '); + 'callback','@textinput_call; epoch1_B=str2double(get(guiHandles.Epoch1_B_Input, ''String''));PSprocess;PSplotLogViewer; '); guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... - 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String''));PSprocess;PSplotLogViewer; '); + 'callback','@textinput_call; epoch2_B=str2double(get(guiHandles.Epoch2_B_Input, ''String''));PSprocess;PSplotLogViewer; '); end if (epoch2_B>round(ttb(end)/us2sec)) epoch2_B=round(ttb(end)/us2sec); guiHandles.Epoch2_B_Input = uicontrol(PSfig,'style','edit','string',[int2str(epoch2_B)],'fontsize',fontsz,'units','normalized','Position',[posInfo.Epoch2_B_Input],... - 'callback','@textinput_call; epoch2_B=str2num(get(guiHandles.Epoch2_B_Input, ''String'')); PSprocess;PSplotLogViewer;'); + 'callback','@textinput_call; epoch2_B=str2double(get(guiHandles.Epoch2_B_Input, ''String'')); PSprocess;PSplotLogViewer;'); end x=[epoch1_B*us2sec epoch2_B*us2sec]; x2=ttb>ttb(find(ttb>x(1),1)) & ttbx(2),1)); diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 9e474aa..332662f 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -198,12 +198,12 @@ function doUpdate() fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); fp.dterm_notch_cut = hval(si, 'dterm_notch_cutoff', 0); - tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2num(tmp); - if isempty(v), v = [0 0]; end + tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2double(strsplit(tmp, ',')); + if any(isnan(v)), v = [0 0]; end fp.gyro_notch1_hz = v(1); fp.gyro_notch2_hz = 0; if numel(v) > 1, fp.gyro_notch2_hz = v(2); end - tmp = hstr(si, 'gyro_notch_cutoff', '0,0'); v = str2num(tmp); - if isempty(v), v = [0 0]; end + tmp = hstr(si, 'gyro_notch_cutoff', '0,0'); v = str2double(strsplit(tmp, ',')); + if any(isnan(v)), v = [0 0]; end fp.gyro_notch1_cut = v(1); fp.gyro_notch2_cut = 0; if numel(v) > 1, fp.gyro_notch2_cut = v(2); end end @@ -212,8 +212,8 @@ function doUpdate() v = default; for k = 1:size(si, 1) if strcmp(strtrim(si{k,1}), key) - tmp = str2num(strtrim(si{k,2})); - if ~isempty(tmp), v = tmp(1); end + tmp = str2double(strtrim(si{k,2})); + if ~isnan(tmp), v = tmp; end return; end end diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index f48e76f..9d3c809 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -120,11 +120,11 @@ guiHandlesSpec3.climMax1_text = uicontrol(PSspecfig3,'style','text','string','Z min','fontsize',fontsz,'TooltipString',['adjusts the color limits'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.clim3Max1_text]); guiHandlesSpec3.climMax1_input = uicontrol(PSspecfig3,'style','edit','string',[num2str(ClimScale3(1))],'fontsize',fontsz,'TooltipString',['adjusts the color limits'],'units','normalized','Position',[posInfo.clim3Max1_input],... - 'callback','@textinput_call2; ClimScale3(1)=str2num(get(guiHandlesSpec3.climMax1_input, ''String''));updateSpec=1;PSfreqTime;'); + 'callback','@textinput_call2; ClimScale3(1)=str2double(get(guiHandlesSpec3.climMax1_input, ''String''));updateSpec=1;PSfreqTime;'); guiHandlesSpec3.climMax2_text = uicontrol(PSspecfig3,'style','text','string','Z max','fontsize',fontsz,'TooltipString',['adjusts the color limits'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.clim3Max2_text]); guiHandlesSpec3.climMax2_input = uicontrol(PSspecfig3,'style','edit','string',[num2str(ClimScale3(2))],'fontsize',fontsz,'TooltipString',['adjusts the color limits'],'units','normalized','Position',[posInfo.clim3Max2_input],... - 'callback','@textinput_call2; ClimScale3(2)=str2num(get(guiHandlesSpec3.climMax2_input, ''String''));updateSpec=1;PSfreqTime;'); + 'callback','@textinput_call2; ClimScale3(2)=str2double(get(guiHandlesSpec3.climMax2_input, ''String''));updateSpec=1;PSfreqTime;'); guiHandlesSpec3.sub100HzfreqTime = uicontrol(PSspecfig3,'Style','checkbox','String','sub 100Hz','fontsize',fontsz,'ForegroundColor',panelFg,'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.sub100HzfreqTime],'callback','@selection2;updateSpec=1; PSfreqTime;'); @@ -185,7 +185,7 @@ function getList2(hObj,event) function textinput_call2(src,eventdata) str=get(src,'String'); - if isempty(str2num(str)) + if isnan(str2double(str)) set(src,'string','0'); warndlg('Input must be numerical'); end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 37955a4..a99ab62 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -191,11 +191,11 @@ guiHandlesSpec2.climMax1_text = uicontrol(PSspecfig2,'style','text','string','Y min','fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax1_text]); guiHandlesSpec2.climMax1_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y min'],'units','normalized','Position',[posInfo.climMax1_input],... - 'callback','@textinput_call2; climScale1(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax1_input, ''String''));PSplotSpec2D;'); + 'callback','@textinput_call2; climScale1(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2double(get(guiHandlesSpec2.climMax1_input, ''String''));PSplotSpec2D;'); guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... - 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); + 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2double(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); end % ishandle(spec2Crtlpanel) % Register CP for fixed-pixel resize @@ -268,7 +268,7 @@ function getList2(hObj,event) function textinput_call2(src,eventdata) str=get(src,'String'); - if isempty(str2num(str)) + if isnan(str2double(str)) set(src,'string','0'); warndlg('Input must be numerical'); end diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 48ab2de..aca1f43 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -179,9 +179,9 @@ guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); guiHandlesSpec.controlFreq1Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim1))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq1Cutoff],... - 'callback','@textinput_call2; Flim1=round(str2num(get(guiHandlesSpec.controlFreq1Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; Flim1=round(str2double(get(guiHandlesSpec.controlFreq1Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); guiHandlesSpec.controlFreq2Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim2))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq2Cutoff],... - 'callback','@textinput_call2; Flim2=round(str2num(get(guiHandlesSpec.controlFreq2Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; Flim2=round(str2double(get(guiHandlesSpec.controlFreq2Cutoff, ''String'')));updateSpec=1;PSplotSpec;'); guiHandlesSpec.saveFig1 = uicontrol(PSspecfig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','ForegroundColor',[saveCol],'Position',[posInfo.saveFig1],... 'callback','set(guiHandlesSpec.saveFig1, ''FontWeight'', ''bold'');PSsaveFig;set(guiHandlesSpec.saveFig1, ''FontWeight'', ''normal'');'); @@ -213,19 +213,19 @@ guiHandlesSpec.climMax_text = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text]); guiHandlesSpec.climMax_input = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2num(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 1)=str2double(get(guiHandlesSpec.climMax_input, ''String''));updateSpec=1;PSplotSpec;'); guiHandlesSpec.climMax_text2 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text2]); guiHandlesSpec.climMax_input2 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 2))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input2],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2num(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 2)=str2double(get(guiHandlesSpec.climMax_input2, ''String''));updateSpec=1;PSplotSpec;'); guiHandlesSpec.climMax_text3 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text3]); guiHandlesSpec.climMax_input3 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 3))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input3],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2num(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 3)=str2double(get(guiHandlesSpec.climMax_input3, ''String''));updateSpec=1;PSplotSpec;'); guiHandlesSpec.climMax_text4 = uicontrol(PSspecfig,'style','text','string','scale','fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax_text4]); guiHandlesSpec.climMax_input4 = uicontrol(PSspecfig,'style','edit','string',[num2str(climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, 4))],'fontsize',fontsz,'TooltipString',[TooltipString_scale],'units','normalized','Position',[posInfo.climMax_input4],... - 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2num(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); + 'callback','@textinput_call2; climScale(get(guiHandlesSpec.checkboxPSD, ''Value'')+1, 4)=str2double(get(guiHandlesSpec.climMax_input4, ''String''));updateSpec=1;PSplotSpec;'); guiHandlesSpec.ColormapSelect = uicontrol(PSspecfig,'Style','popupmenu','string',{'viridis','jet','hot','cool','gray','bone','copper','linear-RED','linear-GREY'},... 'fontsize',fontsz,'TooltipString', [TooltipString_cmap], 'units','normalized','Position',[posInfo.ColormapSelect],'callback','@selection2;updateSpec=1; PSplotSpec;'); @@ -282,7 +282,7 @@ function getList2(hObj,event) function textinput_call2(src,eventdata) str=get(src,'String'); - if isempty(str2num(str)) + if isnan(str2double(str)) set(src,'string','0'); warndlg('Input must be numerical'); end diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index 3b64083..ebfbe9b 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -109,11 +109,11 @@ guiHandlesStats.crossAxesStats_text = uicontrol(PSstatsfig,'style','text','string','scale','fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text]); guiHandlesStats.crossAxesStats_input = uicontrol(PSstatsfig,'style','edit','string',[num2str(zScale)],'fontsize',fontsz5,'TooltipString',[TooltipString_statScale],'units','normalized','Position',[posInfo.crossAxesStats_input],... - 'callback','zScale=str2num(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); + 'callback','zScale=str2double(get(guiHandlesStats.crossAxesStats_input, ''String''));updateStats=1;PSplotStats;'); guiHandlesStats.crossAxesStats_text2 = uicontrol(PSstatsfig,'style','text','string','alpha','fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.crossAxesStats_text2]); guiHandlesStats.crossAxesStats_input2 = uicontrol(PSstatsfig,'style','edit','string',[num2str(zTransparency)],'fontsize',fontsz5,'TooltipString',[TooltipString_statAlpha],'units','normalized','Position',[posInfo.crossAxesStats_input2],... - 'callback','zTransparency=str2num(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); + 'callback','zTransparency=str2double(get(guiHandlesStats.crossAxesStats_input2, ''String'')); if (zTransparency>1), zTransparency=1; end; if (zTransparency<0), zTransparency=0; end; updateStats=1;PSplotStats;'); guiHandlesStats.FileA = uicontrol(PSstatsfig,'Style','popupmenu','string',[fnameMaster],... 'fontsize',fontsz5,'TooltipString','File A (red)','units','normalized','Position',[posInfo.statsFileA],... diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index ba8585d..8159443 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -162,7 +162,7 @@ try set(guiHandlesTune.maxYStepInput, 'String', num2str(defaults.Values(find(str % functions function textinput_call3(src,eventdata) str=get(src,'String'); - if isempty(str2num(str)) + if isnan(str2double(str)) set(src,'string','0'); warndlg('Input must be numerical'); end diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 1c32e4b..c5a088d 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -113,7 +113,7 @@ guiHandles.nCols_text = uicontrol(PSfig,'style','text','string','N colors','fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.nCols_text]); guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... - 'callback','if str2num(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2num(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2num(get(guiHandles.nCols_input, ''String''))); '); + 'callback','if str2double(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2double(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2double(get(guiHandles.nCols_input, ''String''))); '); subplot('position',[posInfo.YTstick]); set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); From 16995e923c8c592bd8a390219491e9bef364f951 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 9 Mar 2026 00:17:03 +0100 Subject: [PATCH 19/80] =?UTF-8?q?fix(ui):=20fix=20Freq=C3=97Throttle=20col?= =?UTF-8?q?orbars=20and=20Step=20Response=204-column=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plot/PSplotSpec.m | 36 ++++----- src/plot/PStuningParams.m | 80 ++++++++++-------- src/ui/PSspecUIcontrol.m | 96 +++++++++++++--------- src/ui/PStuneUIcontrol.m | 22 +++-- src/util/PSresizeCP.m | 166 +++++++++++++++++++++++++++++++++++++- 5 files changed, 297 insertions(+), 103 deletions(-) diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 41ae720..31d93c6 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -93,6 +93,7 @@ try delete(subplot('position',posInfo.SpecPos(p,:))); h1=subplot('position',posInfo.SpecPos(p,:)); cla + set(h1, 'Tag', 'PSgrid'); img = flipud((filter2(ftr, ampmat{p} ))') + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); imagesc(img); @@ -238,28 +239,19 @@ % color bar2 at the top try - delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) + delete(findobj(PSspecfig, 'Tag', 'PScbar')) catch end - if vars(1)>1 % 1=none - subplot('position',posInfo.SpecPos(1,:)); - hCbar1= colorbar('NorthOutside'); - set(hCbar1,'Position', [posInfo.hCbar1pos]); - end - if vars(2)>1 % 1=none - subplot('position',posInfo.SpecPos(4,:)); - hCbar2= colorbar('NorthOutside'); - set(hCbar2,'Position', [posInfo.hCbar2pos]) - end - if vars(3)>1 % 1=none - subplot('position',posInfo.SpecPos(7,:)); - hCbar3= colorbar('NorthOutside'); - set(hCbar3,'Position', [posInfo.hCbar3pos]) - end - if vars(4)>1 % 1=none - subplot('position',posInfo.SpecPos(10,:)); - hCbar4= colorbar('NorthOutside'); - set(hCbar4,'Position', [posInfo.hCbar4pos]) + % Standalone colorbar axes — avoids colorbar('NorthOutside') which resizes subplots in Octave + bY = baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); + cbarPosAll = {posInfo.hCbar1pos, posInfo.hCbar2pos, posInfo.hCbar3pos, posInfo.hCbar4pos}; + for ci = 1:4 + if vars(ci) > 1 + cHi = climScale(get(guiHandlesSpec.checkboxPSD, 'Value')+1, ci); + hCb = axes('Position', cbarPosAll{ci}); + imagesc(hCb, linspace(bY, cHi, 256)); + set(hCb, 'CLim', [bY cHi], 'XTick', [], 'YTick', [], 'Tag', 'PScbar', 'UserData', 'north'); + end end % color maps @@ -283,7 +275,7 @@ if get(guiHandlesSpec.checkbox2d, 'Value')==1 && ~isempty(amp2d) figure(PSspecfig); try - delete(hCbar1);delete(hCbar2);delete(hCbar3);delete(hCbar4) + delete(findobj(PSspecfig, 'Tag', 'PScbar')) catch end baselineYlines = [0 -50]; @@ -296,6 +288,7 @@ delete(subplot('position',posInfo.SpecPos(p,:))); if ~isempty(amp2d{p}) h2=subplot('position',posInfo.SpecPos(p,:)); cla + set(h2, 'Tag', 'PSgrid'); h=plot(freq2d{p}, smooth(amp2d{p}, log10(size(amp2d{p},1)) * (get(guiHandlesSpec.smoothFactor_select, 'Value')^2), 'lowess'));hold on set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2) set(h2,'fontsize',fontsz,'fontweight','bold') @@ -338,6 +331,7 @@ end end PSdatatipSetup(PSspecfig); +try PSresizeCP(PSspecfig, []); catch, end set(PSspecfig, 'pointer', 'arrow') updateSpec=0; diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 42feaa4..e45b236 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -6,7 +6,7 @@ % can do whatever you want with this stuff. If we meet some day, and you think % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- - + PStunefig=figure(4); th = PStheme(); @@ -32,16 +32,16 @@ cnt = 0; set(PStunefig, 'pointer', 'watch') pause(.05); - - for f = get(guiHandlesTune.fileListWindowStep, 'Value') - fcntSR = fcntSR + 1; + + for f = get(guiHandlesTune.fileListWindowStep, 'Value') + fcntSR = fcntSR + 1; if fcntSR <= 10 cnt2 = 0; - for p = axesOptions + for p = axesOptions cnt = cnt + 1; cnt2 = cnt2 + 1; - try - if ~updateStep + try + if ~updateStep clear H G L H = T{f}.(['setpoint_' int2str(p-1) '_'])(tIND{f}); G = T{f}.(['gyroADC_' int2str(p-1) '_'])(tIND{f}); @@ -53,7 +53,8 @@ end if get(guiHandlesTune.RPYcombo, 'Value') == 0 - h1=subplot('position',posInfo.TparamsPos(p,:)); + h1=subplot('position',posInfo.TparamsPos(p,:)); + set(h1, 'Tag', 'PSgrid'); hold on if size(stepresp_A{p},1)>1 @@ -61,25 +62,19 @@ s = stepresp_A{p}; m=nanmean(s); - h1=plot(tA,m); + h1=plot(tA,m); set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5); latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; - peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% + peakresp(p, fcntSR)=max(m(find(tA<150))); peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); pidvar = [ylab2{p} 'PIDF']; PID = eval([pidvar '{f}']); - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); - set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') - set(h,'fontsize',fontsz) else peakresp(p, fcntSR) = nan; peaktime(p, fcntSR) = nan; latencyHalfHeight(p, fcntSR) = nan; - if cnt <= 3, h=text(505, ymax, [pidlabels]);set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(505, ymax-(fcntSR*(ymax*.09)), [int2str(fcntSR) ') insufficient data']); - set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') + PID = ''; end set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); @@ -95,8 +90,23 @@ axis([0 500 0 ymax]) grid on + % Col 2: PID text in dedicated column + hTxt=subplot('position',posInfo.TparamsPos(p+3,:)); + set(hTxt, 'Tag', 'PSgrid', 'Visible', 'off', 'XLim', [0 1], 'YLim', [0 1]); + hold on + if size(stepresp_A{p},1)>1 + if cnt <= 3, h=text(0.05, 0.97, [pidlabels],'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end + h=text(0.05, 0.97-(fcntSR*.09), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')'],'fontsize',fontsz); + set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') + else + if cnt <= 3, h=text(0.05, 0.97, [pidlabels],'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end + h=text(0.05, 0.97-(fcntSR*.09), [int2str(fcntSR) ') insufficient data'],'fontsize',fontsz); + set(h,'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') + end + % Col 3: Peak h2=subplot('position',posInfo.TparamsPos(p+6,:)); + set(h2, 'Tag', 'PSgrid'); h=plot(fcntSR, peakresp(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) @@ -106,8 +116,10 @@ grid on plot([0 10],[1 1],'--','Color',th.axesFg) - h3=subplot('position',posInfo.TparamsPos(p+9,:)); - h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); + % Col 4: Latency + h3=subplot('position',posInfo.TparamsPos(p+9,:)); + set(h3, 'Tag', 'PSgrid'); + h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) mn = min(latencyHalfHeight(p, :))-rem(min(latencyHalfHeight(p, :)),2); @@ -120,14 +132,14 @@ catch end ylabel([ylab{p} ' Latency (ms) '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); + xlabel('Test', 'fontweight','bold'); hold on grid on - - + + end - + if get(guiHandlesTune.RPYcombo, 'Value') == 1 h1=subplot('position',[0.0500 0.1 0.72 0.84]) hold on @@ -137,10 +149,10 @@ s = stepresp_A{p}; m=nanmean(s); - h1=plot(tA,m); + h1=plot(tA,m); set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5, 'linestyle', lineStyle{cnt2}); latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; - peakresp(p, fcntSR)=max(m(find(tA<150)));%max(m); %%%%%%%%%%%%% CONSTRAIN from 0-150ms %%%%% + peakresp(p, fcntSR)=max(m(find(tA<150))); peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); PID = eval([ylab2{p} 'PIDF{f}']); @@ -173,31 +185,32 @@ h=text(505, ypos(p), [pidlabels]); set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') insufficient data']); + h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') insufficient data']); set(h,'Color',[multiLineCols(fcntSR,:)],'fontsize',fontsz, 'fontweight','bold') end - set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); + set(gca,'fontsize',fontsz,'xminortick','on','yminortick','on','xtick',[0 100 200 300 400 500],'xticklabel',{'0' '100' '200' '300' '400' '500'},'ytick',[0 .25 .5 .75 1 1.25 1.5 1.75 2],'tickdir','out'); box off if cnt <= 3, h=ylabel(['Response '], 'fontweight','bold'); end xlabel('Time (ms)', 'fontweight','bold'); - + title('Step Response Functions'); h=plot([0 500],[1 1],'--','Color',th.axesFg); set(h,'linewidth',.5) axis([0 500 0 ymax]) grid on - end - end - + end + end + elseif fcntSR == 11 warndlg('10 files maximum. Click reset.'); - end + end end - allax = findobj(PStunefig, 'Type', 'axes'); + allax = findobj(PStunefig, 'Type', 'axes', 'Visible', 'on'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end + try PSresizeCP(PStunefig, []); catch, end set(PStunefig, 'pointer', 'arrow') updateStep=0; @@ -214,7 +227,6 @@ peakresp_std = []; end end - diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index aca1f43..a4113fc 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -48,13 +48,15 @@ tbOff_spec = 40/screensz(4); clear posInfo.SpecPos -cols=[0.04 0.25 0.46 0.67]; -rows=[0.64-tbOff_spec 0.35-tbOff_spec 0.06-tbOff_spec]; +plotR = cpL - 0.04; plotL4 = 0.04; colGap4 = 0.01; +colW4 = (plotR - plotL4 - 3*colGap4) / 4; +cols = plotL4 + (0:3)*(colW4 + colGap4); +rows=[0.64-tbOff_spec 0.38-tbOff_spec 0.12-tbOff_spec]; k=0; for c=1:4 for r=1:3 k=k+1; - posInfo.SpecPos(k,:)=[cols(c) rows(r) 0.175 0.24]; + posInfo.SpecPos(k,:)=[cols(c) rows(r) colW4 0.21]; end end @@ -81,41 +83,43 @@ posInfo.rpmLegend2= [cpL+cpM yTop-rhs fw rhs]; yTop=yTop-rhs-cpMv; posInfo.rpmLegend3= [cpL+cpM yTop-rhs fw rhs]; -posInfo.AphasedelayText1=[.06 .984-tbOff_spec .14 .02]; -posInfo.AphasedelayText2=[.27 .984-tbOff_spec .14 .02]; -posInfo.AphasedelayText3=[.48 .984-tbOff_spec .14 .02]; -posInfo.AphasedelayText4=[.69 .984-tbOff_spec .14 .02]; - -posInfo.hCbar1pos=[0.04 0.89-tbOff_spec 0.175 0.02]; -posInfo.hCbar2pos=[0.25 0.89-tbOff_spec 0.175 0.02]; -posInfo.hCbar3pos=[0.46 0.89-tbOff_spec 0.175 0.02]; -posInfo.hCbar4pos=[0.67 0.89-tbOff_spec 0.175 0.02]; - -ddh = 0.01; % dropdown height -if exist('isOctave','var') && isOctave, ddh = 0.025; end -posInfo.hDropdn1pos=[0.08 0.97-tbOff_spec 0.095 ddh]; -posInfo.hDropdn2pos=[0.29 0.97-tbOff_spec 0.095 ddh]; -posInfo.hDropdn3pos=[0.50 0.97-tbOff_spec 0.095 ddh]; -posInfo.hDropdn4pos=[0.71 0.97-tbOff_spec 0.095 ddh]; - -posInfo.fDropdn1pos=[0.08 0.942-tbOff_spec 0.095 ddh]; -posInfo.fDropdn2pos=[0.29 0.942-tbOff_spec 0.095 ddh]; -posInfo.fDropdn3pos=[0.50 0.942-tbOff_spec 0.095 ddh]; -posInfo.fDropdn4pos=[0.71 0.942-tbOff_spec 0.095 ddh]; - -posInfo.Sub100HzCheck1=[0.175 0.942-tbOff_spec .06 .025]; -posInfo.Sub100HzCheck2=[.385 .942-tbOff_spec .06 .025]; -posInfo.Sub100HzCheck3=[.595 .942-tbOff_spec .06 .025]; -posInfo.Sub100HzCheck4=[.805 .942-tbOff_spec .06 .025]; - -posInfo.climMax_text = [.01 .913-tbOff_spec .025 .024]; -posInfo.climMax_input = [.01 .888-tbOff_spec .025 .024]; -posInfo.climMax_text2 = [.22 .913-tbOff_spec .025 .024]; -posInfo.climMax_input2 = [.22 .888-tbOff_spec .025 .024]; -posInfo.climMax_text3 = [.43 .913-tbOff_spec .025 .024]; -posInfo.climMax_input3 = [.43 .888-tbOff_spec .025 .024]; -posInfo.climMax_text4 = [.64 .913-tbOff_spec .025 .024]; -posInfo.climMax_input4 = [.64 .888-tbOff_spec .025 .024]; +posInfo.AphasedelayText1=[cols(1)+0.02 .984-tbOff_spec colW4*0.7 .02]; +posInfo.AphasedelayText2=[cols(2)+0.02 .984-tbOff_spec colW4*0.7 .02]; +posInfo.AphasedelayText3=[cols(3)+0.02 .984-tbOff_spec colW4*0.7 .02]; +posInfo.AphasedelayText4=[cols(4)+0.02 .984-tbOff_spec colW4*0.7 .02]; + +posInfo.hCbar1pos=[cols(1) 0.86-tbOff_spec colW4 0.02]; +posInfo.hCbar2pos=[cols(2) 0.86-tbOff_spec colW4 0.02]; +posInfo.hCbar3pos=[cols(3) 0.86-tbOff_spec colW4 0.02]; +posInfo.hCbar4pos=[cols(4) 0.86-tbOff_spec colW4 0.02]; + +ddh_top = 0.01; +if exist('isOctave','var') && isOctave, ddh_top = 0.025; end +ddW_top = min(0.095, colW4*0.5); +cbOff = ddW_top + 0.005; +posInfo.hDropdn1pos=[cols(1)+0.04 0.97-tbOff_spec ddW_top ddh_top]; +posInfo.hDropdn2pos=[cols(2)+0.04 0.97-tbOff_spec ddW_top ddh_top]; +posInfo.hDropdn3pos=[cols(3)+0.04 0.97-tbOff_spec ddW_top ddh_top]; +posInfo.hDropdn4pos=[cols(4)+0.04 0.97-tbOff_spec ddW_top ddh_top]; + +posInfo.fDropdn1pos=[cols(1)+0.04 0.942-tbOff_spec ddW_top ddh_top]; +posInfo.fDropdn2pos=[cols(2)+0.04 0.942-tbOff_spec ddW_top ddh_top]; +posInfo.fDropdn3pos=[cols(3)+0.04 0.942-tbOff_spec ddW_top ddh_top]; +posInfo.fDropdn4pos=[cols(4)+0.04 0.942-tbOff_spec ddW_top ddh_top]; + +posInfo.Sub100HzCheck1=[cols(1)+0.04+cbOff 0.942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck2=[cols(2)+0.04+cbOff .942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck3=[cols(3)+0.04+cbOff .942-tbOff_spec .06 .025]; +posInfo.Sub100HzCheck4=[cols(4)+0.04+cbOff .942-tbOff_spec .06 .025]; + +posInfo.climMax_text = [cols(1)-0.03 .913-tbOff_spec .025 .024]; +posInfo.climMax_input = [cols(1)-0.03 .888-tbOff_spec .025 .024]; +posInfo.climMax_text2 = [cols(2)-0.03 .913-tbOff_spec .025 .024]; +posInfo.climMax_input2 = [cols(2)-0.03 .888-tbOff_spec .025 .024]; +posInfo.climMax_text3 = [cols(3)-0.03 .913-tbOff_spec .025 .024]; +posInfo.climMax_input3 = [cols(3)-0.03 .888-tbOff_spec .025 .024]; +posInfo.climMax_text4 = [cols(4)-0.03 .913-tbOff_spec .025 .024]; +posInfo.climMax_input4 = [cols(4)-0.03 .888-tbOff_spec .025 .024]; climScale=[0.5 0.5 0.5 0.5; 10 10 10 10]; Flim1=20; % 3.3333Hz steps Flim2=60; @@ -253,6 +257,22 @@ cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend1, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend2, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend3, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); +setappdata(PSspecfig, 'PSplotGrid', struct('plotL',plotL4, 'colGap',colGap4, ... + 'ncols',4, 'rows',rows, 'rowH',0.21, 'margin',0.04)); +% Per-column top-bar widgets: {handle, col_index, xOffset_from_col_start} +perColItems = {}; +for pci_k = 1:4 + perColItems{end+1} = {guiHandlesSpec.SpecSelect{pci_k}, pci_k, 0.04}; + perColItems{end+1} = {guiHandlesSpec.FileSelect{pci_k}, pci_k, 0.04}; + perColItems{end+1} = {guiHandlesSpec.Sub100HzCheck{pci_k}, pci_k, cbOff+0.04}; +end +climTextH = {guiHandlesSpec.climMax_text, guiHandlesSpec.climMax_text2, guiHandlesSpec.climMax_text3, guiHandlesSpec.climMax_text4}; +climInputH = {guiHandlesSpec.climMax_input, guiHandlesSpec.climMax_input2, guiHandlesSpec.climMax_input3, guiHandlesSpec.climMax_input4}; +for pci_k = 1:4 + perColItems{end+1} = {climTextH{pci_k}, pci_k, -0.03}; + perColItems{end+1} = {climInputH{pci_k}, pci_k, -0.03}; +end +setappdata(PSspecfig, 'PSperColItems', perColItems); PSregisterResize(PSspecfig, cpPx, cpI, 'seq'); try set(guiHandlesSpec.SpecSelect{1}, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Column1')))), catch, set(guiHandlesSpec.SpecSelect{1}, 'Value', 3); end diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 8159443..64a05e1 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -39,17 +39,21 @@ fcntSR = 0; clear posInfo.TparamsPos -cols=[0.05 0.45 0.58 0.73]; +plotR = cpL - 0.02; plotLt = 0.07; colGapT = 0.015; +% Col 1 = step response, Col 2 = PID text, Col 3 = peak, Col 4 = latency +totalW = plotR - plotLt; +colFracs = [0.42, 0.12, 0.23, 0.23]; +usableW = totalW - 3*colGapT; +wCols = usableW * colFracs / sum(colFracs); +cols = zeros(1,4); +cols(1) = plotLt; +for ci = 2:4, cols(ci) = cols(ci-1) + wCols(ci-1) + colGapT; end rows=[0.69 0.395 0.1]; k=0; -for c=1 : size(cols,2) - for r=1 : size(rows,2) +for c=1:4 + for r=1:3 k=k+1; - if c == 1 - posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.4 0.245]; - else - posInfo.TparamsPos(k,:)=[cols(c) rows(r) 0.11 0.245]; - end + posInfo.TparamsPos(k,:)=[cols(c) rows(r) wCols(c) 0.245]; end end @@ -146,6 +150,8 @@ cpI{end+1} = struct('h', guiHandlesTune.Ycorrection, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepInput, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepTxt, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +setappdata(PStunefig, 'PSplotGrid', struct('plotL',plotLt, 'colGap',colGapT, ... + 'ncols',4, 'rows',rows, 'rowH',0.245, 'margin',0.02, 'colWidthFracs',colFracs)); PSregisterResize(PStunefig, cpPx, cpI, 'seq'); try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m index c458b0b..8714cab 100644 --- a/src/util/PSresizeCP.m +++ b/src/util/PSresizeCP.m @@ -10,6 +10,7 @@ function PSresizeCP(fig, ~) px = cpd.px; cpW = px.cpW / figW; cpL = 1 - cpW - px.cpM / figW; +setappdata(fig, 'PScpL', cpL); cpM = px.cpM / figW; cpMv = px.cpM / figH; rh = px.rh / figH; @@ -108,8 +109,11 @@ function PSresizeCP(fig, ~) else % Row-based layout (PIDscope.m main CP) - tbOff = 40 / figH; % toolbar offset - vPos = 1 - tbOff - cpTitleH - cpMv; + % Always offset for checkbox bar + slider (present on main window) + tbOff = 40 / figH; + chkRow2 = (1 - tbOff) - rs; + sliderBottom = chkRow2 - 2*cpMv - 0.02; + vPos = sliderBottom - 0.005 - cpTitleH - cpMv; for i = 1:numel(cpd.items) it = cpd.items{i}; if ~ishandle(it.h), continue; end @@ -149,5 +153,163 @@ function PSresizeCP(fig, ~) catch end end + +% Reposition Log Viewer checkbox bar (fixed pixel sizes) +chkBar = getappdata(fig, 'PScheckboxBar'); +if ~isempty(chkBar) + tbOff = 40 / figH; + chkH = rh; + chkRow1 = 1 - tbOff; + chkRow2 = chkRow1 - rs; + chkX = chkBar.x0; + for i = 1:numel(chkBar.items) + it = chkBar.items{i}; + if ~ishandle(it.h), continue; end + w = it.wpx / figW; + row = chkRow1; if it.row == 2, row = chkRow2; end + set(it.h, 'Position', [chkX row w chkH]); + if it.advance, chkX = chkX + w; end + end + % Reposition panel background + if isfield(chkBar, 'panel') && ishandle(chkBar.panel) + panelW = chkX - chkBar.x0 + cpM; + set(chkBar.panel, 'Position', [chkBar.x0 chkRow2-cpMv panelW chkRow1+chkH+cpMv-chkRow2+cpMv]); + end + % Reposition slider + sliderY = chkRow2 - 2*cpMv - 0.02; + if isfield(chkBar, 'slider') && ishandle(chkBar.slider) + sliderW = cpL - 0.0826 - 0.005; + set(chkBar.slider, 'Position', [0.0826 sliderY sliderW 0.02]); + end + + % Reposition Log Viewer plot axes + plotL = 0.095; plotGap = 0.01; + gapV = 0.005; linepos4H = 0.11; + plotTop = sliderY - 0.005; + plotW = cpL - plotL - plotGap; + + motorAx = findobj(fig, 'Tag', 'PSmotor'); + rpyAxes = findobj(fig, 'Tag', 'PSrpy'); + comboAx = findobj(fig, 'Tag', 'PScombo'); + upperAxes = []; + for k = 1:numel(rpyAxes), if ishandle(rpyAxes(k)), upperAxes(end+1) = rpyAxes(k); end; end + for k = 1:numel(comboAx), if ishandle(comboAx(k)), upperAxes(end+1) = comboAx(k); end; end + + if ~isempty(motorAx) && ishandle(motorAx(1)) + set(motorAx(1), 'Position', [plotL 0.1 plotW linepos4H]); + end + + nUpper = numel(upperAxes); + if nUpper > 0 + upperBot = 0.1 + linepos4H + gapV; + upperH = (plotTop - upperBot - max(0,nUpper-1)*gapV) / max(1,nUpper); + if nUpper > 1 + yVals = zeros(nUpper, 1); + for k = 1:nUpper, p = get(upperAxes(k), 'Position'); yVals(k) = p(2); end + [~, si] = sort(yVals, 'descend'); + upperAxes = upperAxes(si); + end + for k = 1:nUpper + y = plotTop - k*upperH - (k-1)*gapV; + set(upperAxes(k), 'Position', [plotL y plotW upperH]); + end + end +end + +end % if/elseif/else mode + +% Recompute subplot grid positions based on current cpL +grid = getappdata(fig, 'PSplotGrid'); +if ~isempty(grid) && isfield(grid, 'ncols') + plotR = cpL - grid.margin; + totalW = plotR - grid.plotL; + if totalW > 0.1 + if isfield(grid, 'colWidthFracs') && numel(grid.colWidthFracs) == grid.ncols + usable = totalW - (grid.ncols-1)*grid.colGap; + colWidths = usable * grid.colWidthFracs / sum(grid.colWidthFracs); + elseif isfield(grid, 'bigColFrac') && ~isempty(grid.bigColFrac) + wBig = totalW * grid.bigColFrac; + wSmall = (totalW - wBig - (grid.ncols-1)*grid.colGap) / max(1, grid.ncols-1); + colWidths = [wBig, repmat(wSmall, 1, grid.ncols-1)]; + else + colW_new = (totalW - (grid.ncols-1)*grid.colGap) / grid.ncols; + colWidths = repmat(colW_new, 1, grid.ncols); + end + newCols = zeros(1, grid.ncols); + newCols(1) = grid.plotL; + for c = 2:grid.ncols + newCols(c) = newCols(c-1) + colWidths(c-1) + grid.colGap; + end + + allAx = findobj(fig, 'Type', 'axes', 'Tag', 'PSgrid'); + validAx = []; validPos = []; + for i = 1:numel(allAx) + try + axP = get(allAx(i), 'Position'); + if axP(3) >= 0.05 && axP(4) >= 0.04 + validAx(end+1) = allAx(i); + validPos(end+1,:) = axP; + end + catch, end + end + + for ri = 1:numel(grid.rows) + rowIdx = []; + rowX = []; + for j = 1:numel(validAx) + [~, best_ri] = min(abs(grid.rows - validPos(j,2))); + if best_ri == ri && abs(grid.rows(ri) - validPos(j,2)) < 0.15 + rowIdx(end+1) = j; + rowX(end+1) = validPos(j,1); + end + end + if numel(rowIdx) < 1 || numel(rowIdx) > grid.ncols, continue; end + [~, si] = sort(rowX); + for k = 1:numel(si) + ci = min(k, grid.ncols); + j = rowIdx(si(k)); + set(validAx(j), 'Position', [newCols(ci) grid.rows(ri) colWidths(ci) grid.rowH]); + end + end + + % Store grid results for plot files + setappdata(fig, 'PSgridCols', newCols); + setappdata(fig, 'PSgridWidths', colWidths); + + % Reposition tagged colorbars + cbars = findobj(fig, 'Tag', 'PScbar'); + for cbi = 1:numel(cbars) + try + cpos = get(cbars(cbi), 'Position'); + ud = get(cbars(cbi), 'UserData'); + if ischar(ud) && strcmp(ud, 'north') + [~, cci] = min(abs(newCols - cpos(1))); + if cci <= numel(colWidths) + set(cbars(cbi), 'Position', [newCols(cci) cpos(2) colWidths(cci) cpos(4)]); + end + elseif ischar(ud) && strcmp(ud, 'east') + cbarX = plotR + 0.005; + cbarW = max(0.02, grid.margin * 0.35); + set(cbars(cbi), 'Position', [cbarX cpos(2) cbarW cpos(4)]); + end + catch, end + end + + % Reposition per-column top-bar widgets (Freq x Throttle) + pci = getappdata(fig, 'PSperColItems'); + if ~isempty(pci) + for pii = 1:numel(pci) + try + ph = pci{pii}{1}; pci_col = pci{pii}{2}; pci_xOff = pci{pii}{3}; + if pci_col <= numel(newCols) && ishandle(ph) + ppos = get(ph, 'Position'); + ppos(1) = newCols(pci_col) + pci_xOff; + set(ph, 'Position', ppos); + end + catch, end + end + end + end end + end From d0963ad272a62a8e079828d93363bec042789ba6 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 9 Mar 2026 00:19:16 +0100 Subject: [PATCH 20/80] fix(ui): dynamic plot widths and PSgrid resize for all dashboards --- src/plot/PSfreqTime.m | 5 +++- src/plot/PSplotLogViewer.m | 25 +++++++++++++--- src/plot/PSplotPIDerror.m | 3 ++ src/plot/PSplotSpec2D.m | 8 ++++-- src/plot/PSplotStats.m | 4 +++ src/ui/PSerrUIcontrol.m | 8 ++++-- src/ui/PSfreqTimeUIcontrol.m | 14 ++++----- src/ui/PSspec2DUIcontrol.m | 8 ++++-- src/ui/PSstatsUIcontrol.m | 8 ++++-- src/ui/PSviewerUIcontrol.m | 56 +++++++++++++++++++++++++++++------- 10 files changed, 108 insertions(+), 31 deletions(-) diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 93683ce..316a572 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -40,6 +40,7 @@ end h2=subplot('position',posInfo.Spec3Pos(i,:)); + set(h2, 'Tag', 'PSgrid'); h = imagesc(specMat{i}); set(gca,'Clim',[ClimScale3], 'fontsize',fontsz,'fontweight','bold') @@ -68,7 +69,8 @@ set(PSspecfig3, 'Colormap', cm); catch, end cbar = colorbar('EastOutside'); - set(get(cbar, 'Label'), 'String', 'Power Spectral density (dB)'); + set(cbar, 'Tag', 'PScbar', 'UserData', 'east'); + set(get(cbar, 'Label'), 'String', 'Power Spectral density (dB)', 'Color', th.textPrimary); try set(cbar, 'Color', th.axesFg); catch, end if i == 3 && (strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisD') || strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisDpf')) @@ -114,6 +116,7 @@ allax = findobj(PSspecfig3, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end PSdatatipSetup(PSspecfig3); +try PSresizeCP(PSspecfig3, []); catch, end set(PSspecfig3, 'pointer', 'arrow') diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 1ea544a..59bc65d 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -46,7 +46,17 @@ end plotall_flag=-1; - expand_sz=[0.05 0.06 0.815 posInfo.slider(2)-0.07]; + dynCpL = getappdata(PSfig, 'PScpL'); if isempty(dynCpL), dynCpL = 0.875; end + plotL = 0.095; plotGap = 0.01; + plotW = dynCpL - plotL - plotGap; + expandW = dynCpL - 0.05 - 0.01; + expand_sz=[0.05 0.06 expandW posInfo.slider(2)-0.07]; + % Update linepos widths to match current CP position + posInfo.linepos1(3) = plotW; + posInfo.linepos2(3) = plotW; + posInfo.linepos3(3) = plotW; + posInfo.linepos4(3) = plotW; + fullszPlot(3) = plotW; %% where you want full range of data @@ -89,8 +99,13 @@ % set(jRangeSlider, 'MajorTickSpacing',50, 'PaintTicks',true, 'PaintLabels',true, 'Background',java.awt.Color.white) % jRangeSlider.LowValue = 20;, jRangeSlider.HighValue = 180; - guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',posInfo.slider,... + sliderPos = posInfo.slider; + sliderPos(3) = dynCpL - sliderPos(1) - 0.005; + guiHandles.slider = uicontrol(PSfig, 'style','slider','SliderStep',[0.001 0.01],'Visible', 'on', 'units','normalized','position',sliderPos,... 'min',0,'max',1, 'callback','PSslider1Actions;'); + % Update resize data with slider handle + chkBarData = getappdata(PSfig, 'PScheckboxBar'); + if ~isempty(chkBarData), chkBarData.slider = guiHandles.slider; setappdata(PSfig, 'PScheckboxBar', chkBarData); end @@ -167,6 +182,7 @@ cntLV = cntLV + 1; if get(guiHandles.RPYcomboLV, 'Value') LVpanel4 = subplot('position' ,fullszPlot); + set(LVpanel4, 'Tag', 'PScombo'); lnstyle = lineStyle2LV; end if ~get(guiHandles.RPYcomboLV, 'Value') && expandON == 0 @@ -264,10 +280,10 @@ try if ~expandON && ~isempty(LVpanels{ii}) - set(LVpanels{ii},'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',posInfo.(['linepos' int2str(ii)])); + set(LVpanels{ii},'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',posInfo.(['linepos' int2str(ii)]),'Tag','PSrpy'); end if ~expandON - set(LVpanel5,'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4]); + set(LVpanel5,'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','position',[posInfo.linepos4],'Tag','PSmotor'); end catch end @@ -283,6 +299,7 @@ 'end, end']); PSdatatipSetup(PSfig); + try PSresizeCP(PSfig, []); catch, end set(PSfig, 'pointer', 'arrow') else diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index ac8b291..3fbe4da 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -31,6 +31,7 @@ for p = 1:3 delete(subplot('position', posInfo.PIDerrAnalysis(p,:))); h1 = subplot('position', posInfo.PIDerrAnalysis(p,:)); cla; + set(h1, 'Tag', 'PSgrid'); hold on; piderr_A = T{fA}.(axPIDerr{p})(tIND{fA})'; @@ -143,6 +144,7 @@ for p = 1:3 delete(subplot('position', posInfo.PIDerrAnalysis(p+3,:))); h1 = subplot('position', posInfo.PIDerrAnalysis(p+3,:)); cla; + set(h1, 'Tag', 'PSgrid'); posAx = .8:1:9.8; posBx = 1.2:1:10.2; @@ -192,6 +194,7 @@ allax = findobj(PSerrfig, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end +try PSresizeCP(PSerrfig, []); catch, end set(PSerrfig, 'pointer', 'arrow'); catch err diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index dda9ede..2f6d81b 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -204,7 +204,8 @@ if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - h2=subplot('position',posInfo.Spec2Pos(a,:)); + h2=subplot('position',posInfo.Spec2Pos(a,:)); + set(h2, 'Tag', 'PSgrid'); ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -230,6 +231,7 @@ grid on h2=subplot('position',posInfo.Spec2Pos(a+3,:)); + set(h2, 'Tag', 'PSgrid'); ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -297,7 +299,8 @@ else % combine R P Y - h2=subplot('position',[0.0500 0.1000 0.800 0.840]); + h2=subplot('position',[0.0500 0.1000 cpL-0.1 0.840]); + set(h2, 'Tag', 'PSgrid'); ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -373,6 +376,7 @@ allax = findobj(PSspecfig2, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end PSdatatipSetup(PSspecfig2); +try PSresizeCP(PSspecfig2, []); catch, end set(PSspecfig2, 'pointer', 'arrow') updateSpec=0; diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index bd68083..90ab8ac 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -52,6 +52,7 @@ for sp = 1:4 hhist = subplot('position', posInfo.statsPos(sp,:)); cla; + set(hhist, 'Tag', 'PSgrid'); if sp <= 3 pctData = Rpct_A{sp}; else @@ -84,6 +85,7 @@ for sp = 1:4 hhist = subplot('position', posInfo.statsPos(sp+4,:)); cla; + set(hhist, 'Tag', 'PSgrid'); if sp <= 3 pctData = Rpct_B{sp}; else @@ -156,6 +158,7 @@ useAbs = grp{4}; h1 = subplot('position', posInfo.statsPos2(slots(g),:)); cla; + set(h1, 'Tag', 'PSgrid'); vals = zeros(nAx, 1); sds = zeros(nAx, 1); @@ -223,6 +226,7 @@ allax = findobj(PSstatsfig, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end +try PSresizeCP(PSstatsfig, []); catch, end set(PSstatsfig, 'pointer', 'arrow'); catch err diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index d8b950f..3a1db07 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -31,13 +31,15 @@ newline , 'This cutoff helps to reduce inclusion of data with inflated PID error as a result of snap maneuvers' ]; clear posInfo.PIDerrAnalysis -cols=[0.1 0.55]; +plotR = cpL - 0.04; plotLe = 0.08; colGapE = 0.02; +colWe = (plotR - plotLe - colGapE) / 2; +cols = [plotLe, plotLe + colWe + colGapE]; rows=[0.63 0.36 0.09]; k=0; for c=1:2 for r=1:3 k=k+1; - posInfo.PIDerrAnalysis(k,:)=[cols(c) rows(r) 0.39 0.24]; + posInfo.PIDerrAnalysis(k,:)=[cols(c) rows(r) colWe 0.24]; end end @@ -99,6 +101,8 @@ cpI{end+1} = struct('h', guiHandlesPIDerr.FileB, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); end cpI{end+1} = struct('h', errCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); +setappdata(PSerrfig, 'PSplotGrid', struct('plotL',plotLe, 'colGap',colGapE, ... + 'ncols',2, 'rows',rows, 'rowH',0.24, 'margin',0.04)); PSregisterResize(PSerrfig, cpPx, cpI, 'topbar', topBarL); PSstyleControls(PSerrfig); diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 9d3c809..43f8095 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -21,20 +21,18 @@ %%% clear posInfo.Spec3Pos -cols=[0.09 ]; +plotR = cpL - 0.10; plotL1 = 0.09; +colW1 = plotR - plotL1; +cols=[plotL1]; rows=[0.69 0.395 0.1]; k=0; for c=1 : size(cols,2) for r=1 : size(rows,2) - k=k+1; - posInfo.Spec3Pos(k,:)=[cols(c) rows(r) 0.77 0.255]; + k=k+1; + posInfo.Spec3Pos(k,:)=[cols(c) rows(r) colW1 0.255]; end end -if exist('isOctave','var') && isOctave - posInfo.Spec3Pos(:,3) = 0.74; -end - updateSpec = 0; clear specMat @@ -159,6 +157,8 @@ cpI{end+1} = struct('h', guiHandlesSpec3.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec3.sub100HzfreqTime, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec3.playerBtn, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +setappdata(PSspecfig3, 'PSplotGrid', struct('plotL',plotL1, 'colGap',0, ... + 'ncols',1, 'rows',rows, 'rowH',0.255, 'margin',0.10)); PSregisterResize(PSspecfig3, cpPx, cpI, 'seq'); try set(guiHandlesSpec3.SpecList, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-Preset')))), catch, set(guiHandlesSpec3.SpecList, 'Value', 1), end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index a99ab62..2c8e02d 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -28,13 +28,15 @@ clear posInfo.Spec2Pos -cols=[0.05 0.47]; +plotR = cpL - 0.04; plotL2 = 0.05; colGap2 = 0.02; +colW2 = (plotR - plotL2 - colGap2) / 2; +cols = [plotL2, plotL2 + colW2 + colGap2]; rows=[0.69 0.395 0.1]; k=0; for c=1 : size(cols,2) for r=1 : size(rows,2) k=k+1; - posInfo.Spec2Pos(k,:)=[cols(c) rows(r) 0.37 0.25]; + posInfo.Spec2Pos(k,:)=[cols(c) rows(r) colW2 0.25]; end end @@ -226,6 +228,8 @@ cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_text, 'type','text_right', 'row',0, 'col',0, 'hpx',rhs_px); cpI{end+1} = struct('h', guiHandlesSpec2.climMax1_input, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); +setappdata(PSspecfig2, 'PSplotGrid', struct('plotL',plotL2, 'colGap',colGap2, ... + 'ncols',2, 'rows',rows, 'rowH',0.25, 'margin',0.04)); PSregisterResize(PSspecfig2, cpPx, cpI, 'seq'); try set(guiHandlesSpec2.SpecList, 'Value', [defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term1'))) defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term2')))]), catch, set(guiHandlesSpec2.SpecList, 'Value', [1 2]), end diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index ebfbe9b..c0262ed 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -57,13 +57,15 @@ fontsz5 = fontsz; clear posInfo.statsPos -cols=[0.06 0.54]; +plotR = cpL - 0.04; plotLs = 0.06; colGapS = 0.02; +colWs = (plotR - plotLs - colGapS) / 2; +cols = [plotLs, plotLs + colWs + colGapS]; rows=[0.69 0.48 0.27 0.06]; k=0; for c=1:2 for r=1:4 k=k+1; - posInfo.statsPos(k,:)=[cols(c) rows(r) 0.39 0.18]; + posInfo.statsPos(k,:)=[cols(c) rows(r) colWs 0.18]; end end @@ -144,6 +146,8 @@ cpI{end+1} = struct('h', guiHandlesStats.FileB, 'type','dd', 'row',0, 'col',0, 'hpx',0, 'wpx',160); end cpI{end+1} = struct('h', statsCrtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0, 'wpx',0); +setappdata(PSstatsfig, 'PSplotGrid', struct('plotL',plotLs, 'colGap',colGapS, ... + 'ncols',2, 'rows',rows, 'rowH',0.18, 'margin',0.04)); PSregisterResize(PSstatsfig, cpPx, cpI, 'topbar', topBarL); PSstyleControls(PSstatsfig); diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index c5a088d..17d775b 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -9,10 +9,12 @@ % ---------------------------------------------------------------------------------- -% Checkbox bar — pixel-based sizes -chkW = 130/screensz(3); chkH = rh; chkMotW = 100/screensz(3); -chkEdtW = 45/screensz(3); chkTxtW = 65/screensz(3); -tbOff = 40/screensz(4); % toolbar offset +% Checkbox bar — pixel sizes (constant across resizes) +chkW_px = 130; chkMotW_px = 100; chkEdtW_px = 45; chkTxtW_px = 65; +figPos = get(PSfig, 'Position'); figW = figPos(3); figH = figPos(4); +chkW = chkW_px/figW; chkH = rh; chkMotW = chkMotW_px/figW; +chkEdtW = chkEdtW_px/figW; chkTxtW = chkTxtW_px/figW; +tbOff = 40/figH; chkRow1 = 1 - tbOff; chkRow2 = chkRow1 - rs; chkX = 0.10; posInfo.checkbox0=[chkX chkRow1 chkW chkH]; @@ -40,18 +42,22 @@ posInfo.YTstick = [cpL+.005 vPos-0.39 .05 .085]; posInfo.RPstick = [cpL+cpW/2 vPos-0.39 .05 .085]; -% Plot positions computed from checkbox bar (dynamic for toolbar offset) -posInfo.slider = [0.0826 chkRow2-2*cpMv-0.02 0.787 0.02]; +% Plot positions — right edge stops at CP left edge +plotL = 0.095; plotGap = 0.01; +dynCpL = getappdata(PSfig, 'PScpL'); if isempty(dynCpL), dynCpL = cpL; end +plotW = dynCpL - plotL - plotGap; +sliderW = dynCpL - 0.0826 - 0.005; +posInfo.slider = [0.0826 chkRow2-2*cpMv-0.02 sliderW 0.02]; plotTop = posInfo.slider(2) - 0.005; gapV = 0.005; linepos4H = 0.11; plotH = (plotTop - 0.1 - linepos4H - 4*gapV) / 3; -posInfo.linepos1=[0.095 plotTop-plotH 0.77 plotH]; -posInfo.linepos2=[0.095 plotTop-2*plotH-gapV 0.77 plotH]; -posInfo.linepos3=[0.095 plotTop-3*plotH-2*gapV 0.77 plotH]; -posInfo.linepos4=[0.095 0.1 0.77 linepos4H]; +posInfo.linepos1=[plotL plotTop-plotH plotW plotH]; +posInfo.linepos2=[plotL plotTop-2*plotH-gapV plotW plotH]; +posInfo.linepos3=[plotL plotTop-3*plotH-2*gapV plotW plotH]; +posInfo.linepos4=[plotL 0.1 plotW linepos4H]; -fullszPlot = [0.095 posInfo.linepos3(2) 0.77 plotTop-posInfo.linepos3(2)]; +fullszPlot = [plotL posInfo.linepos3(2) plotW plotTop-posInfo.linepos3(2)]; if ~exist('checkpanel','var') || ~ishandle(checkpanel) @@ -115,6 +121,34 @@ guiHandles.nCols_input = uicontrol(PSfig,'style','edit','string',int2str(nLineCols),'fontsize',fontsz,'TooltipString', ['sets the number of colors for other tools (allowable range 1 - 20)'],'units','normalized','Position',[posInfo.nCols_input],... 'callback','if str2double(get(guiHandles.nCols_input, ''String'')) > 20, set(guiHandles.nCols_input, ''String'', ''20''); end; if str2double(get(guiHandles.nCols_input, ''String'')) < 1, set(guiHandles.nCols_input, ''String'', ''1''); end; multiLineCols=PSlinecmap(str2double(get(guiHandles.nCols_input, ''String''))); '); +% Register checkbox bar for pixel-based resize +chkBarItems = {}; +chkBarItems{end+1} = struct('h', guiHandles.checkbox0, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox1, 'wpx', chkW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox2, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox3, 'wpx', chkW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox4, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox5, 'wpx', chkW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox6, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox7, 'wpx', chkW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox8, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox9, 'wpx', chkW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox13, 'wpx', chkMotW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox12, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox11, 'wpx', chkMotW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox10, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox14, 'wpx', chkMotW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.maxY_text, 'wpx', chkTxtW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.nCols_text, 'wpx', chkTxtW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.maxY_input, 'wpx', chkEdtW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.nCols_input, 'wpx', chkEdtW_px, 'row', 2, 'advance', true); +chkBarData = struct('x0', 0.10, 'items', {chkBarItems}, 'panel', checkpanel); +if exist('guiHandles','var') && isfield(guiHandles, 'slider') && ishandle(guiHandles.slider) + chkBarData.slider = guiHandles.slider; +end +setappdata(PSfig, 'PScheckboxBar', chkBarData); + subplot('position',[posInfo.YTstick]); set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); box on From 1a295a19645cccc40c9650def170cfb645505244 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 10 Mar 2026 13:34:30 +0100 Subject: [PATCH 21/80] feat(filter-sim): redesign with 2x4 layout and edit boxes --- PIDscope.m | 30 +- src/plot/PSfilterSim.m | 1005 ++++++++++++++++++++++++++++++------ src/ui/PSspec2DUIcontrol.m | 15 - 3 files changed, 870 insertions(+), 180 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index 7bdf011..a3bd02b 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -197,6 +197,7 @@ posInfo.linewidth = [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; posInfo.spectrogramButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.TuningButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.filterSimButton=[cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.PIDErrorButton = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; posInfo.FlightStatsButton=[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; posInfo.period2Hz = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; @@ -292,6 +293,16 @@ 'callback','PStuneUIcontrol'); set(guiHandles.TuningButton, 'ForegroundColor', colorB); +guiHandles.filterSimButton = uicontrol(PSfig,'string','Filter Sim','fontsize',fontsz,... + 'TooltipString','Simulate BF filter chain (theoretical response)','units','normalized',... + 'Position',[posInfo.filterSimButton],... + 'callback',['try,' ... + 'tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... + 'PSfilterSim([],1000*A_lograte(tmpFcnt),SetupInfo{tmpFcnt});' ... + 'clear tmpFcnt;' ... + 'catch e,warndlg([''Filter Sim: '' e.message]),end']); +set(guiHandles.filterSimButton, 'ForegroundColor', [.85 .55 .15]); + guiHandles.PIDErrorButton = uicontrol(PSfig,'string','PID Error','fontsize',fontsz,'TooltipString', ['PID error distribution analysis'],'units','normalized','Position',[posInfo.PIDErrorButton],... 'callback','PSerrUIcontrol; PSplotPIDerror;'); set(guiHandles.PIDErrorButton, 'ForegroundColor', [.8 .4 .1]); @@ -393,9 +404,7 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para % Register CP elements for resize — keeps fixed pixel sizes when window changes cpPx = struct('cpW', cpW_px, 'cpM', cpM_px, 'rh', rh_px, 'rs', rs_px, ... 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', infoH_px); -nrows = 12; % number of rows in CP cpItems = {}; -cpItems{end+1} = struct('h', controlpanel, 'type','panel', 'row',0, 'col',0, 'nrows',nrows); cpItems{end+1} = struct('h', guiHandles.Firmware, 'type','full', 'row',1, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.fileA, 'type','left', 'row',2, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.clr, 'type','right', 'row',2, 'col',0, 'nrows',0); @@ -409,11 +418,14 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para cpItems{end+1} = struct('h', guiHandles.linewidth, 'type','right', 'row',6, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.spectrogramButton, 'type','full', 'row',7, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.TuningButton, 'type','full', 'row',8, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',9, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',9, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',10, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.DispInfoButton, 'type','right', 'row',10, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.saveFig, 'type','left', 'row',11, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.saveSettings, 'type','right', 'row',11, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.PIDtuningService, 'type','full', 'row',12, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.filterSimButton, 'type','full', 'row',9, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.DispInfoButton, 'type','right', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveFig, 'type','left', 'row',12, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveSettings, 'type','right', 'row',12, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDtuningService, 'type','full', 'row',13, 'col',0, 'nrows',0); +nrows = max(cellfun(@(x) x.row, cpItems)); +cpItems = [{struct('h', controlpanel, 'type','panel', 'row',0, 'col',0, 'nrows',nrows)}, cpItems]; PSregisterResize(PSfig, cpPx, cpItems, 'rows'); diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 332662f..fb723e6 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -1,8 +1,6 @@ -function PSfilterSim(gyroRaw, Fs, setupInfo) -%% PSfilterSim - simulate BF gyro filter chain on raw gyro data -% gyroRaw - struct with fields r/p/y (column vectors, deg/s) -% Fs - sample rate (Hz) -% setupInfo - cell array {param, value} from header +function PSfilterSim(~, Fs, setupInfo) +%% PSfilterSim - BF filter chain visualization (theoretical response) +% Layout: 2 cols (Lowpass | Notch) x 4 rows (Magnitude, Delay, Phase, Step) thm = PStheme(); fontsz = thm.fontsz; @@ -16,159 +14,735 @@ function PSfilterSim(gyroRaw, Fs, setupInfo) fp = parseFilterParams(setupInfo); -axData = {gyroRaw.r, gyroRaw.p, gyroRaw.y}; -axNames = {'Roll', 'Pitch', 'Yaw'}; - -axSpec = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .54 .62 .40]); -axTime = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .10 .62 .36]); - -cpL = .72; cpW = .27; -uipanel('Parent', fig, 'Title', 'Filter Settings', 'FontWeight', 'bold', ... +% Layout: 2 cols x 4 rows + gradient bars +plotL = 0.05; +plotR = 0.75; +colGap = 0.055; +colW = (plotR - plotL - colGap) / 2; +colL = [plotL, plotL + colW + colGap]; +titleH = 0.04; +barH = 0.018; +topMargin = 0.01; +botMargin = 0.06; +rowGap = 0.012; +topY = 1 - topMargin - titleH; +barY = topY - barH; +plotTop = barY - rowGap*0.5; +rowH = (plotTop - botMargin - 3*rowGap) / 4; +rowB1 = plotTop - rowH; +rowB2 = rowB1 - rowH - rowGap; +rowB3 = rowB2 - rowH - rowGap; +rowB4 = rowB3 - rowH - rowGap; + +axLmag = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) rowB1 colW rowH]); +axLdelay = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) rowB2 colW rowH]); +axLphase = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) rowB3 colW rowH]); +axLstep = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) rowB4 colW rowH]); + +axNmag = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB1 colW rowH]); +axNdelay = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB2 colW rowH]); +axNphase = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB3 colW rowH]); +axNstep = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB4 colW rowH]); + +% Gradient frequency bars +axBarL = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) barY colW barH], 'Tag', 'gradbar'); +axBarN = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) barY colW barH], 'Tag', 'gradbar'); + +titleY = topY; +hTitleL = uicontrol(fig, 'Style', 'text', 'String', 'LOWPASS FILTERS', ... + 'Units', 'normalized', 'Position', [colL(1) titleY colW titleH], ... + 'FontSize', fontsz+1, 'FontWeight', 'bold', ... + 'BackgroundColor', thm.figBg, 'ForegroundColor', [.4 .9 1]); +hTitleN = uicontrol(fig, 'Style', 'text', 'String', 'NOTCH FILTERS', ... + 'Units', 'normalized', 'Position', [colL(2) titleY colW titleH], ... + 'FontSize', fontsz+1, 'FontWeight', 'bold', ... + 'BackgroundColor', thm.figBg, 'ForegroundColor', [1 .6 .3]); + +% Control panel +cpL = .76; cpW = .23; +uipanel('Parent', fig, 'Title', 'Filter Controls', 'FontWeight', 'bold', ... 'BackgroundColor', thm.panelBg, 'ForegroundColor', thm.panelFg, ... 'HighlightColor', thm.panelBorder, ... 'FontSize', fontsz, 'Position', [cpL .02 cpW .96]); -row = .92; rh = .032; gap = .005; +row = .93; rh = .026; gap = .004; bgc = thm.panelBg; fgc = thm.panelFg; -cb = @(~,~) doUpdate(); +cb = @(~,~) autoUpdate(); +ibc = thm.inputBg; ifc = thm.inputFg; +lc = thm.textSecondary; + +% grid: label col = cpL+.01, half-width col = cpL+.115 +x0 = cpL + .01; cW = cpW - .02; halfW = cW / 2; + +% Looprate + #Motors +loopRates = [1000 1100 3200 6400 6664 8000 9000 32000]; +loopLabels = {'1k','1.1k','3.2k','6.4k','6.664k','8k','9k','32k'}; +[~, lrIdx] = min(abs(loopRates - Fs)); +uicontrol(fig, 'Style', 'text', 'String', 'Rate:', ... + 'Units', 'normalized', 'Position', [x0 row .035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.looprate = uicontrol(fig, 'Style', 'popupmenu', 'String', loopLabels, 'Value', lrIdx, ... + 'Units', 'normalized', 'Position', [x0+.04 row halfW-.04 rh], 'FontSize', fontsz, ... + 'Callback', @(~,~) loopRateChanged()); +uicontrol(fig, 'Style', 'text', 'String', 'Mot:', ... + 'Units', 'normalized', 'Position', [x0+halfW row .035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.rpm_nmot = uicontrol(fig, 'Style', 'popupmenu', ... + 'String', {'1','2','3','4','5','6','7','8'}, 'Value', 4, ... + 'Units', 'normalized', 'Position', [x0+halfW+.04 row halfW-.04 rh], 'FontSize', fontsz, 'Callback', cb); +row = row - rh - gap*2; + +mkSection(fig, 'Gyro LPF1', cpL, row, cpW, rh, bgc, [.4 .9 1], fontsz-1); +row = row - rh - gap; +[h.glpf1_type, h.glpf1_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.gyro_lpf1_type, fp.gyro_lpf1_hz, cb, fontsz); -% axis selector -mkLabel(fig, 'Axis:', cpL, row, rh, bgc, fgc, fontsz); -h.axis = uicontrol(fig, 'Style', 'popupmenu', 'String', axNames, 'Value', 1, ... - 'Units', 'normalized', 'Position', [cpL+.06 row .08 rh], 'FontSize', fontsz, 'Callback', cb); -row = row - rh - gap*3; +mkSection(fig, 'Gyro LPF2', cpL, row, cpW, rh, bgc, [.4 .9 1], fontsz-1); +row = row - rh - gap; +[h.glpf2_type, h.glpf2_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.gyro_lpf2_type, fp.gyro_lpf2_hz, cb, fontsz); + +mkSection(fig, 'Gyro Notch 1', cpL, row, cpW, rh, bgc, [1 .7 .3], fontsz-1); +row = row - rh - gap; +[h.gn1_hz, h.gn1_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.gyro_notch1_hz, fp.gyro_notch1_cut, cb, fontsz); -% Gyro LPF1 -mkSection(fig, '--- Gyro LPF1 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); +mkSection(fig, 'Gyro Notch 2', cpL, row, cpW, rh, bgc, [1 .7 .3], fontsz-1); row = row - rh - gap; -[h.glpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf1_type, fp.gyro_lpf1_hz, cb, fontsz); -[h.glpf1_hz, h.glpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf1_hz, cb, fontsz); -row = row - gap*2; +[h.gn2_hz, h.gn2_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.gyro_notch2_hz, fp.gyro_notch2_cut, cb, fontsz); -% Gyro LPF2 -mkSection(fig, '--- Gyro LPF2 ---', cpL, row, cpW, rh, bgc, [.5 .9 1], fontsz); +mkSection(fig, 'D-term LPF1', cpL, row, cpW, rh, bgc, [.4 1 .4], fontsz-1); row = row - rh - gap; -[h.glpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.gyro_lpf2_type, fp.gyro_lpf2_hz, cb, fontsz); -[h.glpf2_hz, h.glpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_lpf2_hz, cb, fontsz); -row = row - gap*2; +[h.dlpf1_type, h.dlpf1_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.dterm_lpf1_type, fp.dterm_lpf1_hz, cb, fontsz); -% Gyro Notch 1 -mkSection(fig, '--- Gyro Notch 1 ---', cpL, row, cpW, rh, bgc, [1 .8 .4], fontsz); +mkSection(fig, 'D-term LPF2', cpL, row, cpW, rh, bgc, [.4 1 .4], fontsz-1); row = row - rh - gap; -[h.gn1_hz, h.gn1_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch1_hz, cb, fontsz); -[h.gn1_cut, h.gn1_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch1_cut, cb, fontsz); -row = row - gap*2; +[h.dlpf2_type, h.dlpf2_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.dterm_lpf2_type, fp.dterm_lpf2_hz, cb, fontsz); -% Gyro Notch 2 -mkSection(fig, '--- Gyro Notch 2 ---', cpL, row, cpW, rh, bgc, [1 .8 .4], fontsz); +mkSection(fig, 'D-term Notch', cpL, row, cpW, rh, bgc, [1 .5 .5], fontsz-1); row = row - rh - gap; -[h.gn2_hz, h.gn2_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.gyro_notch2_hz, cb, fontsz); -[h.gn2_cut, h.gn2_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.gyro_notch2_cut, cb, fontsz); -row = row - gap*2; +[h.dn_hz, h.dn_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.dterm_notch_hz, fp.dterm_notch_cut, cb, fontsz); -% D-term LPF1 -mkSection(fig, '--- D-term LPF1 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); +% RPM: Hz + #Harm + Q on one line +mkSection(fig, 'RPM Filter Sim', cpL, row, cpW, rh, bgc, [1 .4 .4], fontsz-1); +row = row - rh - gap; +thirdW = cW / 3; +uicontrol(fig, 'Style', 'text', 'String', 'Hz:', ... + 'Units', 'normalized', 'Position', [x0 row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.rpm_base = uicontrol(fig, 'Style', 'edit', 'String', '200', ... + 'Units', 'normalized', 'Position', [x0+.03 row thirdW-.035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, 'HorizontalAlignment', 'center', 'Callback', cb); +uicontrol(fig, 'Style', 'text', 'String', '#H:', ... + 'Units', 'normalized', 'Position', [x0+thirdW row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.rpm_nharm = uicontrol(fig, 'Style', 'popupmenu', ... + 'String', {'0','1','2','3','4','5','6','7'}, 'Value', 4, ... + 'Units', 'normalized', 'Position', [x0+thirdW+.03 row thirdW-.035 rh], 'FontSize', fontsz, 'Callback', cb); +uicontrol(fig, 'Style', 'text', 'String', 'Q:', ... + 'Units', 'normalized', 'Position', [x0+thirdW*2 row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.rpm_q = uicontrol(fig, 'Style', 'edit', 'String', '500', ... + 'Units', 'normalized', 'Position', [x0+thirdW*2+.03 row thirdW-.035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, 'HorizontalAlignment', 'center', 'Callback', cb); row = row - rh - gap; -[h.dlpf1_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf1_type, fp.dterm_lpf1_hz, cb, fontsz); -[h.dlpf1_hz, h.dlpf1_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf1_hz, cb, fontsz); -row = row - gap*2; -% D-term LPF2 -mkSection(fig, '--- D-term LPF2 ---', cpL, row, cpW, rh, bgc, [.5 1 .5], fontsz); +% Test signal: Lo + Hi + Dur on one line +mkSection(fig, 'Test Signal', cpL, row, cpW, rh, bgc, [.8 .6 .2], fontsz-1); +row = row - rh - gap; +uicontrol(fig, 'Style', 'text', 'String', 'Lo:', ... + 'Units', 'normalized', 'Position', [x0 row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.sig_start = uicontrol(fig, 'Style', 'edit', 'String', '0', ... + 'Units', 'normalized', 'Position', [x0+.03 row thirdW-.035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, 'HorizontalAlignment', 'center', 'Callback', cb); +uicontrol(fig, 'Style', 'text', 'String', 'Hi:', ... + 'Units', 'normalized', 'Position', [x0+thirdW row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.sig_end = uicontrol(fig, 'Style', 'edit', 'String', '1000', ... + 'Units', 'normalized', 'Position', [x0+thirdW+.03 row thirdW-.035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, 'HorizontalAlignment', 'center', 'Callback', cb); +uicontrol(fig, 'Style', 'text', 'String', 'Dur:', ... + 'Units', 'normalized', 'Position', [x0+thirdW*2 row .025 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.sig_dur = uicontrol(fig, 'Style', 'edit', 'String', '1', ... + 'Units', 'normalized', 'Position', [x0+thirdW*2+.03 row thirdW-.035 rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, 'HorizontalAlignment', 'center', 'Callback', cb); row = row - rh - gap; -[h.dlpf2_type, row] = mkType(fig, cpL, row, rh, gap, bgc, fgc, fp.dterm_lpf2_type, fp.dterm_lpf2_hz, cb, fontsz); -[h.dlpf2_hz, h.dlpf2_lbl, row] = mkSlider(fig, 'Hz:', cpL, row, rh, gap, bgc, fgc, 0, 500, fp.dterm_lpf2_hz, cb, fontsz); -row = row - gap*2; -% D-term Notch -mkSection(fig, '--- D-term Notch ---', cpL, row, cpW, rh, bgc, [1 .6 .6], fontsz); +% Options +mkSection(fig, 'Options', cpL, row, cpW, rh, bgc, [.7 .7 .7], fontsz-1); +row = row - rh - gap; +h.magdB = uicontrol(fig, 'Style', 'checkbox', 'String', 'Magnitude dB', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0 row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); +h.logfreq = uicontrol(fig, 'Style', 'checkbox', 'String', 'Log Frequency', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); row = row - rh - gap; -[h.dn_hz, h.dn_hz_lbl, row] = mkSlider(fig, 'Center:', cpL, row, rh, gap, bgc, fgc, 0, 1000, fp.dterm_notch_hz, cb, fontsz); -[h.dn_cut, h.dn_cut_lbl, row] = mkSlider(fig, 'Cutoff:', cpL, row, rh, gap, bgc, fgc, 0, 800, fp.dterm_notch_cut, cb, fontsz); +h.combinelpf = uicontrol(fig, 'Style', 'checkbox', 'String', 'combine lpf', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0 row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); +h.addnoise = uicontrol(fig, 'Style', 'checkbox', 'String', 'Add noise', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); +row = row - rh - gap; +h.showstep = uicontrol(fig, 'Style', 'checkbox', 'String', 'Step resp.', 'Value', 1, ... + 'Units', 'normalized', 'Position', [x0 row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, ... + 'Callback', @(~,~) toggleStepRow()); +h.showboth = uicontrol(fig, 'Style', 'checkbox', 'String', 'Show Both', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); +row = row - rh - gap*2; +h.autoupd = uicontrol(fig, 'Style', 'checkbox', 'String', 'Auto Update', 'Value', 1, ... + 'Units', 'normalized', 'Position', [x0 row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, ... + 'Callback', @(~,~) toggleAutoUpdate()); +h.updBtn = uicontrol(fig, 'Style', 'pushbutton', 'String', 'Update', ... + 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... + 'FontSize', fontsz, 'BackgroundColor', thm.btnBg, 'ForegroundColor', thm.textPrimary, ... + 'Enable', 'off', 'Callback', @(~,~) doUpdate()); +row = row - rh - gap; +h.totalDelay = uicontrol(fig, 'Style', 'text', 'String', '', ... + 'Units', 'normalized', 'Position', [x0 row cW rh], ... + 'FontSize', fontsz, 'FontWeight', 'bold', ... + 'BackgroundColor', bgc, 'ForegroundColor', [.9 .2 .2], 'HorizontalAlignment', 'left'); doUpdate(); + function autoUpdate() + if get(h.autoupd, 'Value'), doUpdate(); end + end + + function loopRateChanged() + Fs = loopRates(get(h.looprate, 'Value')); + autoUpdate(); + end + + function toggleStepRow() + showStep = get(h.showstep, 'Value'); + if showStep + set(axLstep, 'Visible', 'on'); + set(axNstep, 'Visible', 'on'); + rH = (plotTop - botMargin - 3*rowGap) / 4; + rB1 = plotTop - rH; rB2 = rB1 - rH - rowGap; rB3 = rB2 - rH - rowGap; rB4 = rB3 - rH - rowGap; + set(axLmag, 'Position', [colL(1) rB1 colW rH]); + set(axLdelay, 'Position', [colL(1) rB2 colW rH]); + set(axLphase, 'Position', [colL(1) rB3 colW rH]); + set(axLstep, 'Position', [colL(1) rB4 colW rH]); + set(axNmag, 'Position', [colL(2) rB1 colW rH]); + set(axNdelay, 'Position', [colL(2) rB2 colW rH]); + set(axNphase, 'Position', [colL(2) rB3 colW rH]); + set(axNstep, 'Position', [colL(2) rB4 colW rH]); + else + set(axLstep, 'Visible', 'off'); + set(axNstep, 'Visible', 'off'); + rH = (plotTop - botMargin - 2*rowGap) / 3; + rB1 = plotTop - rH; rB2 = rB1 - rH - rowGap; rB3 = rB2 - rH - rowGap; + set(axLmag, 'Position', [colL(1) rB1 colW rH]); + set(axLdelay, 'Position', [colL(1) rB2 colW rH]); + set(axLphase, 'Position', [colL(1) rB3 colW rH]); + set(axNmag, 'Position', [colL(2) rB1 colW rH]); + set(axNdelay, 'Position', [colL(2) rB2 colW rH]); + set(axNphase, 'Position', [colL(2) rB3 colW rH]); + end + autoUpdate(); + end + + function toggleAutoUpdate() + if get(h.autoupd, 'Value') + set(h.updBtn, 'Enable', 'off'); + doUpdate(); + else + set(h.updBtn, 'Enable', 'on'); + end + end + function doUpdate() if ~ishandle(fig), return; end - ai = get(h.axis, 'Value'); - dat = axData{ai}; glpf1t = get(h.glpf1_type, 'Value') - 1; - glpf1f = round(get(h.glpf1_hz, 'Value')); + glpf1f = readEdit(h.glpf1_hz); glpf2t = get(h.glpf2_type, 'Value') - 1; - glpf2f = round(get(h.glpf2_hz, 'Value')); - gn1f = round(get(h.gn1_hz, 'Value')); - gn1c = round(get(h.gn1_cut, 'Value')); - gn2f = round(get(h.gn2_hz, 'Value')); - gn2c = round(get(h.gn2_cut, 'Value')); + glpf2f = readEdit(h.glpf2_hz); + gn1f = readEdit(h.gn1_hz); + gn1c = readEdit(h.gn1_cut); + gn2f = readEdit(h.gn2_hz); + gn2c = readEdit(h.gn2_cut); dlpf1t = get(h.dlpf1_type, 'Value') - 1; - dlpf1f = round(get(h.dlpf1_hz, 'Value')); + dlpf1f = readEdit(h.dlpf1_hz); dlpf2t = get(h.dlpf2_type, 'Value') - 1; - dlpf2f = round(get(h.dlpf2_hz, 'Value')); - dnf = round(get(h.dn_hz, 'Value')); - dnc = round(get(h.dn_cut, 'Value')); - - set(h.glpf1_lbl, 'String', num2str(glpf1f)); - set(h.glpf2_lbl, 'String', num2str(glpf2f)); - set(h.gn1_hz_lbl, 'String', num2str(gn1f)); - set(h.gn1_cut_lbl, 'String', num2str(gn1c)); - set(h.gn2_hz_lbl, 'String', num2str(gn2f)); - set(h.gn2_cut_lbl, 'String', num2str(gn2c)); - set(h.dlpf1_lbl, 'String', num2str(dlpf1f)); - set(h.dlpf2_lbl, 'String', num2str(dlpf2f)); - set(h.dn_hz_lbl, 'String', num2str(dnf)); - set(h.dn_cut_lbl, 'String', num2str(dnc)); - - % gyro filter chain - filt = dat; - filt = applyLPF(filt, glpf1t, glpf1f, Fs); - filt = applyLPF(filt, glpf2t, glpf2f, Fs); - filt = applyNotch(filt, gn1f, gn1c, Fs); - filt = applyNotch(filt, gn2f, gn2c, Fs); - - % D-term - dterm = [0; diff(dat)] * Fs; - dterm = applyLPF(dterm, dlpf1t, dlpf1f, Fs); - dterm = applyLPF(dterm, dlpf2t, dlpf2f, Fs); - dterm = applyNotch(dterm, dnf, dnc, Fs); - - F_kHz = Fs / 1000; - [fO, sO] = PSSpec2d(dat', F_kHz, 1); - [fF, sF] = PSSpec2d(filt', F_kHz, 1); - [~, sD] = PSSpec2d(dterm', F_kHz, 1); - - cla(axSpec); - plot(axSpec, fO, sO, 'Color', [.5 .5 .5], 'LineWidth', 0.8); - hold(axSpec, 'on'); - plot(axSpec, fF, sF, 'c', 'LineWidth', 1.3); - plot(axSpec, fO, sD, 'Color', [.4 .9 .4], 'LineWidth', 0.8); - hold(axSpec, 'off'); - PSstyleAxes(axSpec, thm); set(axSpec, 'XLim', [0 Fs/2]); - set(get(axSpec, 'XLabel'), 'String', 'Frequency (Hz)'); - set(get(axSpec, 'YLabel'), 'String', 'PSD (dB)'); - title(axSpec, [axNames{ai} ' - Spectrum']); - h_leg = legend(axSpec, {'Raw gyro', 'Filtered gyro', 'D-term (filtered)'}, ... - 'Location', 'northeast'); - try PSstyleLegend(h_leg, thm); catch, end - - N = min(2000, length(dat)); - t = (0:N-1) / Fs * 1000; - cla(axTime); - plot(axTime, t, dat(1:N), 'Color', [.5 .5 .5], 'LineWidth', 0.6); - hold(axTime, 'on'); - plot(axTime, t, filt(1:N), 'c', 'LineWidth', 1.2); - hold(axTime, 'off'); - PSstyleAxes(axTime, thm); - set(get(axTime, 'XLabel'), 'String', 'Time (ms)'); - set(get(axTime, 'YLabel'), 'String', 'deg/s'); - title(axTime, [axNames{ai} ' - Time Domain']); - h_leg = legend(axTime, {'Raw', 'Filtered'}, 'Location', 'northeast'); - try PSstyleLegend(h_leg, thm); catch, end + dlpf2f = readEdit(h.dlpf2_hz); + dnf = readEdit(h.dn_hz); + dnc = readEdit(h.dn_cut); + rpmBase = readEdit(h.rpm_base); + rpmNharm = get(h.rpm_nharm, 'Value') - 1; + rpmQ = readEdit(h.rpm_q); + nMotors = get(h.rpm_nmot, 'Value'); + usedB = get(h.magdB, 'Value'); + useLog = get(h.logfreq, 'Value'); + combineLPF = get(h.combinelpf, 'Value'); + addNoise = get(h.addnoise, 'Value'); + showBoth = get(h.showboth, 'Value'); + showStep = get(h.showstep, 'Value'); + sigHzLo = readEdit(h.sig_start); + sigHzHi = readEdit(h.sig_end); + sigDur = max(0.1, readEditF(h.sig_dur)); + + types = {'pt1', 'biquad', 'pt2', 'pt3'}; + Nfft = 2048; + fVec = linspace(0, Fs/2, Nfft)'; + fMax = min(Fs/2, 1000); + fIdx = fVec <= fMax; + if useLog, fMin = 10; fIdx = fVec >= fMin & fVec <= fMax; end + + % LPF frequency responses + H_lpf1 = lpfH(glpf1t, glpf1f, Fs, Nfft, types); + H_lpf2 = lpfH(glpf2t, glpf2f, Fs, Nfft, types); + H_dlpf1 = lpfH(dlpf1t, dlpf1f, Fs, Nfft, types); + H_dlpf2 = lpfH(dlpf2t, dlpf2f, Fs, Nfft, types); + H_gyroLPF = H_lpf1 .* H_lpf2; + H_dtermLPF = H_dlpf1 .* H_dlpf2; + + % Notch frequency responses (static) + H_gn1 = notchH(gn1f, gn1c, Fs, Nfft); + H_gn2 = notchH(gn2f, gn2c, Fs, Nfft); + H_dn = notchH(dnf, dnc, Fs, Nfft); + + % RPM harmonic notches (cascaded nMotors times per harmonic) + rpmCols = {[.9 .2 .2],[.9 .6 .1],[.9 .9 .2],[.3 .8 .3],[.2 .8 .8],[.9 .9 .9],[.8 .3 .8]}; + H_rpm = cell(rpmNharm, 1); + H_rpmAll = ones(Nfft, 1); + for ri = 1:rpmNharm + fc_rpm = rpmBase * ri; + if fc_rpm > 0 && fc_rpm < Fs/2 + H_single = notchH_Q(fc_rpm, rpmQ, Fs, Nfft); + H_rpm{ri} = H_single .^ nMotors; + else + H_rpm{ri} = ones(Nfft, 1); + end + H_rpmAll = H_rpmAll .* H_rpm{ri}; + end + H_gyroN = H_gn1 .* H_gn2 .* H_rpmAll; + H_dtermN = H_dn; + + % Group delay + dw = gradient(2*pi*fVec); + gd_gL = smooth(-gradient(unwrap(angle(H_gyroLPF))) ./ dw * 1000, 21, 'moving'); + gd_dL = smooth(-gradient(unwrap(angle(H_dtermLPF))) ./ dw * 1000, 21, 'moving'); + gd_gN = smooth(-gradient(unwrap(angle(H_gyroN))) ./ dw * 1000, 51, 'moving'); + gd_dN = smooth(-gradient(unwrap(angle(H_dtermN))) ./ dw * 1000, 51, 'moving'); + + delayL = gd_gL(2); + delayN = gd_gN(2); + set(hTitleL, 'String', sprintf('LOWPASS FILTERS | Delay %.5fms', delayL)); + set(hTitleN, 'String', sprintf('NOTCH FILTERS | Delay %.4fms', delayN)); + set(h.totalDelay, 'String', sprintf('Total Delay: %.3fms (LPF %.3f + Notch %.3f)', ... + delayL + delayN, delayL, delayN)); + + % Step responses — LPF: adaptive time, Notch: 100ms + minCutoff = Fs/2; + for cc = [glpf1f glpf2f dlpf1f dlpf2f] + if cc > 0, minCutoff = min(minCutoff, cc); end + end + lpfStepMs = max(4, min(10, 1000 / max(minCutoff, 50))); + stepLenL = round(Fs * lpfStepMs / 1000); + stepInL = ones(stepLenL, 1); + tStepL = (0:stepLenL-1)' / Fs * 1000; + + notchStepMs = 100; + stepLenN = round(Fs * notchStepMs / 1000); + stepInN = ones(stepLenN, 1); + tStepN = (0:stepLenN-1)' / Fs * 1000; + + sL_g = applyLPF(applyLPF(stepInL, glpf2t, glpf2f, Fs), glpf1t, glpf1f, Fs); + sL_d = applyLPF(applyLPF(stepInL, dlpf2t, dlpf2f, Fs), dlpf1t, dlpf1f, Fs); + sN_g = applyNotch(applyNotch(stepInN, gn2f, gn2c, Fs), gn1f, gn1c, Fs); + for ri = 1:rpmNharm + fc_rpm = rpmBase * ri; + if fc_rpm > 0 && fc_rpm < Fs/2 + for mi = 1:nMotors + sN_g = applyNotch_Q(sN_g, fc_rpm, rpmQ, Fs); + end + end + end + sN_d = applyNotch(stepInN, dnf, dnc, Fs); + % Per-RPM-harmonic steps (cascaded nMotors) + sN_rpm = cell(rpmNharm, 1); + for ri = 1:rpmNharm + fc_rpm = rpmBase * ri; + if fc_rpm > 0 && fc_rpm < Fs/2 + tmp = stepInN; + for mi = 1:nMotors, tmp = applyNotch_Q(tmp, fc_rpm, rpmQ, Fs); end + sN_rpm{ri} = tmp; + else + sN_rpm{ri} = stepInN; + end + end + sN_gn1 = applyNotch(stepInN, gn1f, gn1c, Fs); + sN_gn2 = applyNotch(stepInN, gn2f, gn2c, Fs); + + % Noisy step responses (for "Add noise" option) + if addNoise + noiseAmp = 0.03; + noisyL = stepInL + randn(stepLenL, 1) * noiseAmp; + nsL_g = applyLPF(applyLPF(noisyL, glpf2t, glpf2f, Fs), glpf1t, glpf1f, Fs); + nsL_d = applyLPF(applyLPF(noisyL, dlpf2t, dlpf2f, Fs), dlpf1t, dlpf1f, Fs); + noisyN = stepInN + randn(stepLenN, 1) * noiseAmp; + nsN_g = applyNotch(applyNotch(noisyN, gn2f, gn2c, Fs), gn1f, gn1c, Fs); + for ri = 1:rpmNharm + fc_rpm = rpmBase * ri; + if fc_rpm > 0 && fc_rpm < Fs/2 + for mi = 1:nMotors, nsN_g = applyNotch_Q(nsN_g, fc_rpm, rpmQ, Fs); end + end + end + nsN_d = applyNotch(noisyN, dnf, dnc, Fs); + end + + colG = [0 .85 .85]; colD = [.4 .9 .4]; + colRef = [.45 .45 .45]; + % Notch column: warm colors + colStaticN = [1 .65 .2]; + colCombN = [.95 .85 .2]; + plotFn = @plot; + if useLog, plotFn = @semilogx; end + + %% GRADIENT FREQUENCY BARS + drawGradientBar(axBarL, fMax, [.2 .7 .8; .3 .9 .5], ... + {glpf1f, glpf2f, dlpf1f, dlpf2f}, {colG*0.7, colG, colD*0.7, colD}, thm); + notchFreqs = {}; notchCols = {}; + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + notchFreqs{end+1} = rpmBase * ri; + notchCols{end+1} = rpmCols{ci}; + end + if gn1f > 0, notchFreqs{end+1} = gn1f; notchCols{end+1} = colStaticN; end + if gn2f > 0, notchFreqs{end+1} = gn2f; notchCols{end+1} = colStaticN*0.85; end + if dnf > 0, notchFreqs{end+1} = dnf; notchCols{end+1} = colD; end + drawGradientBar(axBarN, fMax, [.8 .3 .2; .9 .7 .2], notchFreqs, notchCols, thm); + if showBoth + set(axBarL, 'Position', [plotL barY plotR-plotL barH]); + set(axBarN, 'Visible', 'off'); + else + set(axBarL, 'Position', [colL(1) barY colW barH]); + set(axBarN, 'Visible', 'on'); + end + + %% TEST SIGNAL PSD (if sigHzHi > sigHzLo) + if sigHzHi > sigHzLo && sigDur > 0 + Nsig = round(Fs * sigDur); + t_sig = (0:Nsig-1)' / Fs; + sig = localChirp(t_sig, max(sigHzLo,1), sigDur, sigHzHi); + sig_gL = sig; + if glpf1t > 0 && glpf1f > 0, sig_gL = applyLPF(sig_gL, glpf1t, glpf1f, Fs); end + if glpf2t > 0 && glpf2f > 0, sig_gL = applyLPF(sig_gL, glpf2t, glpf2f, Fs); end + sig_gN = sig; + if gn1f > 0, sig_gN = applyNotch(sig_gN, gn1f, gn1c, Fs); end + if gn2f > 0, sig_gN = applyNotch(sig_gN, gn2f, gn2c, Fs); end + for ri = 1:rpmNharm + fc_rpm = rpmBase * ri; + if fc_rpm > 0 && fc_rpm < Fs/2 + for mi = 1:nMotors, sig_gN = applyNotch_Q(sig_gN, fc_rpm, rpmQ, Fs); end + end + end + end + + %% LOWPASS COLUMN + + % Individual filter responses for non-combined mode + gd_g1 = smooth(-gradient(unwrap(angle(H_lpf1))) ./ dw * 1000, 21, 'moving'); + gd_g2 = smooth(-gradient(unwrap(angle(H_lpf2))) ./ dw * 1000, 21, 'moving'); + gd_d1 = smooth(-gradient(unwrap(angle(H_dlpf1))) ./ dw * 1000, 21, 'moving'); + gd_d2 = smooth(-gradient(unwrap(angle(H_dlpf2))) ./ dw * 1000, 21, 'moving'); + ph_g1 = unwrap(angle(H_lpf1)) * 180/pi; + ph_g2 = unwrap(angle(H_lpf2)) * 180/pi; + ph_d1 = unwrap(angle(H_dlpf1)) * 180/pi; + ph_d2 = unwrap(angle(H_dlpf2)) * 180/pi; + sL_g1 = applyLPF(stepInL, glpf1t, glpf1f, Fs); + sL_g2 = applyLPF(stepInL, glpf2t, glpf2f, Fs); + sL_d1 = applyLPF(stepInL, dlpf1t, dlpf1f, Fs); + sL_d2 = applyLPF(stepInL, dlpf2t, dlpf2f, Fs); + + g1on = glpf1t > 0 && glpf1f > 0; + g2on = glpf2t > 0 && glpf2f > 0; + d1on = dlpf1t > 0 && dlpf1f > 0; + d2on = dlpf2t > 0 && dlpf2f > 0; + colNoise = [.9 .25 .25]; + + % Show Both: widen left col, hide right col + if showBoth + wideW = plotR - plotL; + set(axLmag, 'Position', [plotL get(axLmag,'Position')*[0;1;0;0] wideW get(axLmag,'Position')*[0;0;0;1]]); + set(axLdelay, 'Position', [plotL get(axLdelay,'Position')*[0;1;0;0] wideW get(axLdelay,'Position')*[0;0;0;1]]); + set(axLphase, 'Position', [plotL get(axLphase,'Position')*[0;1;0;0] wideW get(axLphase,'Position')*[0;0;0;1]]); + set(axLstep, 'Position', [plotL get(axLstep,'Position')*[0;1;0;0] wideW get(axLstep,'Position')*[0;0;0;1]]); + set(axNmag, 'Visible', 'off'); set(axNdelay, 'Visible', 'off'); + set(axNphase, 'Visible', 'off'); set(axNstep, 'Visible', 'off'); + set(hTitleN, 'Visible', 'off'); + else + set(axLmag, 'Position', [colL(1) get(axLmag,'Position')*[0;1;0;0] colW get(axLmag,'Position')*[0;0;0;1]]); + set(axLdelay, 'Position', [colL(1) get(axLdelay,'Position')*[0;1;0;0] colW get(axLdelay,'Position')*[0;0;0;1]]); + set(axLphase, 'Position', [colL(1) get(axLphase,'Position')*[0;1;0;0] colW get(axLphase,'Position')*[0;0;0;1]]); + set(axLstep, 'Position', [colL(1) get(axLstep,'Position')*[0;1;0;0] colW get(axLstep,'Position')*[0;0;0;1]]); + set(axNmag, 'Visible', 'on'); set(axNdelay, 'Visible', 'on'); + set(axNphase, 'Visible', 'on'); + if showStep, set(axNstep, 'Visible', 'on'); end + set(hTitleN, 'Visible', 'on'); + end + + %% LOWPASS COLUMN (+ notch overlay when showBoth) + + cla(axLmag); hold(axLmag, 'on'); + if combineLPF + plotFn(axLmag, fVec(fIdx), magY(H_gyroLPF(fIdx), usedB), 'Color', colG, 'LineWidth', 1.5); + plotFn(axLmag, fVec(fIdx), magY(H_dtermLPF(fIdx), usedB), 'Color', colD, 'LineWidth', 1.2); + else + if g1on, plotFn(axLmag, fVec(fIdx), magY(H_lpf1(fIdx), usedB), 'Color', colG, 'LineWidth', 1.2); end + if g2on, plotFn(axLmag, fVec(fIdx), magY(H_lpf2(fIdx), usedB), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end + if d1on, plotFn(axLmag, fVec(fIdx), magY(H_dlpf1(fIdx), usedB), 'Color', colD, 'LineWidth', 1.0); end + if d2on, plotFn(axLmag, fVec(fIdx), magY(H_dlpf2(fIdx), usedB), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + end + if showBoth + plotFn(axLmag, fVec(fIdx), magY(H_gyroN(fIdx), usedB), 'Color', colCombN, 'LineWidth', 1.2); + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + plotFn(axLmag, fVec(fIdx), magY(H_rpm{ri}(fIdx), usedB), 'Color', rpmCols{ci}, 'LineWidth', 0.7); + end + end + if usedB + line(axLmag, [0 fMax], [-3 -3], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + else + line(axLmag, [0 fMax], [0.707 0.707], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + end + hold(axLmag, 'off'); + PSstyleAxes(axLmag, thm); + if usedB + set(axLmag, 'XLim', xlimF(useLog, fMax), 'YLim', [-60 3], 'XTickLabel', {}); + set(get(axLmag, 'YLabel'), 'String', 'Magnitude (dB)'); + else + set(axLmag, 'XLim', xlimF(useLog, fMax), 'YLim', [0 1.1], 'XTickLabel', {}); + set(get(axLmag, 'YLabel'), 'String', 'Magnitude (abs)'); + end + annotateLPF(axLmag, glpf1t, glpf1f, glpf2t, glpf2f, types, colG, fontsz, usedB); + + cla(axLdelay); hold(axLdelay, 'on'); + if combineLPF + plotFn(axLdelay, fVec(fIdx), gd_gL(fIdx), 'Color', colG, 'LineWidth', 1.5); + plotFn(axLdelay, fVec(fIdx), gd_dL(fIdx), 'Color', colD, 'LineWidth', 1.2); + else + if g1on, plotFn(axLdelay, fVec(fIdx), gd_g1(fIdx), 'Color', colG, 'LineWidth', 1.2); end + if g2on, plotFn(axLdelay, fVec(fIdx), gd_g2(fIdx), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end + if d1on, plotFn(axLdelay, fVec(fIdx), gd_d1(fIdx), 'Color', colD, 'LineWidth', 1.0); end + if d2on, plotFn(axLdelay, fVec(fIdx), gd_d2(fIdx), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + end + if showBoth + plotFn(axLdelay, fVec(fIdx), gd_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.2); + end + hold(axLdelay, 'off'); + PSstyleAxes(axLdelay, thm); set(axLdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + if combineLPF + gdMax = max([max(gd_gL(fIdx)) max(gd_dL(fIdx)) 0.3]) * 1.3; + else + allGdL = [0.3]; + if g1on, allGdL(end+1) = max(gd_g1(fIdx)); end + if g2on, allGdL(end+1) = max(gd_g2(fIdx)); end + if d1on, allGdL(end+1) = max(gd_d1(fIdx)); end + if d2on, allGdL(end+1) = max(gd_d2(fIdx)); end + gdMax = max(allGdL) * 1.3; + end + if isfinite(gdMax) && gdMax > 0, set(axLdelay, 'YLim', [0 gdMax]); end + set(get(axLdelay, 'YLabel'), 'String', 'Filter Delay (ms)'); + yTxtL = gdMax * 0.92; + yTxtStp = gdMax * 0.17; + if combineLPF + text(fMax*0.02, yTxtL, sprintf('gyro lpf: %.3fms', gd_gL(2)), 'Parent', axLdelay, ... + 'Color', colG, 'FontSize', fontsz-1); + text(fMax*0.02, yTxtL - yTxtStp, sprintf('dterm lpf: %.3fms', gd_dL(2)), 'Parent', axLdelay, ... + 'Color', colD, 'FontSize', fontsz-1); + else + if g1on, text(fMax*0.02, yTxtL, sprintf('gyro lpf1: %.3fms', gd_g1(2)), 'Parent', axLdelay, 'Color', colG*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if g2on, text(fMax*0.02, yTxtL, sprintf('gyro lpf2: %.3fms', gd_g2(2)), 'Parent', axLdelay, 'Color', colG, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if d1on, text(fMax*0.02, yTxtL, sprintf('dterm lpf1: %.3fms', gd_d1(2)), 'Parent', axLdelay, 'Color', colD*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if d2on, text(fMax*0.02, yTxtL, sprintf('dterm lpf2: %.3fms', gd_d2(2)), 'Parent', axLdelay, 'Color', colD, 'FontSize', fontsz-1); end + end + + cla(axLphase); hold(axLphase, 'on'); + if combineLPF + ph_gL = unwrap(angle(H_gyroLPF)) * 180/pi; + ph_dL = unwrap(angle(H_dtermLPF)) * 180/pi; + plotFn(axLphase, fVec(fIdx), ph_gL(fIdx), 'Color', colG, 'LineWidth', 1.5); + plotFn(axLphase, fVec(fIdx), ph_dL(fIdx), 'Color', colD, 'LineWidth', 1.2); + else + if g1on, plotFn(axLphase, fVec(fIdx), ph_g1(fIdx), 'Color', colG, 'LineWidth', 1.2); end + if g2on, plotFn(axLphase, fVec(fIdx), ph_g2(fIdx), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end + if d1on, plotFn(axLphase, fVec(fIdx), ph_d1(fIdx), 'Color', colD, 'LineWidth', 1.0); end + if d2on, plotFn(axLphase, fVec(fIdx), ph_d2(fIdx), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + end + if showBoth + ph_gN = smooth(angle(H_gyroN) * 180/pi, 9, 'moving'); + plotFn(axLphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.2); + end + hold(axLphase, 'off'); + PSstyleAxes(axLphase, thm); set(axLphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + set(get(axLphase, 'YLabel'), 'String', 'Phase Delay (deg)'); + + if showStep + cla(axLstep); + line(axLstep, [tStepL(1) tStepL(end)], [1 1], 'Color', colRef, 'LineWidth', 0.5, 'LineStyle', '--'); hold(axLstep, 'on'); + if combineLPF + plot(axLstep, tStepL, sL_g, 'Color', colG, 'LineWidth', 1.5); + plot(axLstep, tStepL, sL_d, 'Color', colD, 'LineWidth', 1.2); + if addNoise + plot(axLstep, tStepL, nsL_g, 'Color', colNoise, 'LineWidth', 0.8); + plot(axLstep, tStepL, nsL_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); + end + else + if g1on, plot(axLstep, tStepL, sL_g1, 'Color', colG, 'LineWidth', 1.2); end + if g2on, plot(axLstep, tStepL, sL_g2, 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end + if d1on, plot(axLstep, tStepL, sL_d1, 'Color', colD, 'LineWidth', 1.0); end + if d2on, plot(axLstep, tStepL, sL_d2, 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + if addNoise + plot(axLstep, tStepL, nsL_g, 'Color', colNoise, 'LineWidth', 0.8); + plot(axLstep, tStepL, nsL_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); + end + end + hold(axLstep, 'off'); + PSstyleAxes(axLstep, thm); set(axLstep, 'XLim', [tStepL(1) tStepL(end)], 'YLim', [-0.05 1.15]); + xlabel(axLstep, 'Time (ms)', 'Color', thm.textPrimary); + set(get(axLstep, 'YLabel'), 'String', 'Step Resp.'); + end + + %% NOTCH COLUMN (skip when showBoth) + + if ~showBoth + cla(axNmag); hold(axNmag, 'on'); + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + plotFn(axNmag, fVec(fIdx), magY(H_rpm{ri}(fIdx), usedB), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + end + if gn1f > 0 + plotFn(axNmag, fVec(fIdx), magY(H_gn1(fIdx), usedB), 'Color', colStaticN, 'LineWidth', 0.8); + end + if gn2f > 0 + plotFn(axNmag, fVec(fIdx), magY(H_gn2(fIdx), usedB), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); + end + plotFn(axNmag, fVec(fIdx), magY(H_gyroN(fIdx), usedB), 'Color', colCombN, 'LineWidth', 1.5); + if dnf > 0 + plotFn(axNmag, fVec(fIdx), magY(H_dtermN(fIdx), usedB), 'Color', colD, 'LineWidth', 1.2); + end + hold(axNmag, 'off'); + PSstyleAxes(axNmag, thm); + if usedB + set(axNmag, 'XLim', xlimF(useLog, fMax), 'YLim', [-40 3], 'XTickLabel', {}); + set(get(axNmag, 'YLabel'), 'String', 'Magnitude (dB)'); + else + set(axNmag, 'XLim', xlimF(useLog, fMax), 'YLim', [0 1.1], 'XTickLabel', {}); + set(get(axNmag, 'YLabel'), 'String', 'Magnitude (abs)'); + end + annotateNotch(axNmag, gn1f, gn2f, dnf, rpmBase, rpmNharm, rpmCols, colStaticN, colCombN, colD, fontsz, usedB); + + cla(axNdelay); hold(axNdelay, 'on'); + rpmGd = cell(rpmNharm, 1); + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + rpmGd{ri} = smooth(-gradient(unwrap(angle(H_rpm{ri}))) ./ dw * 1000, 51, 'moving'); + plotFn(axNdelay, fVec(fIdx), rpmGd{ri}(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + end + plotFn(axNdelay, fVec(fIdx), gd_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); + if dnf > 0 + plotFn(axNdelay, fVec(fIdx), gd_dN(fIdx), 'Color', colD, 'LineWidth', 1.2); + end + hold(axNdelay, 'off'); + PSstyleAxes(axNdelay, thm); set(axNdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + gdMaxN = max(abs(gd_gN(2)) * 3, 0.5); + if isfinite(gdMaxN), set(axNdelay, 'YLim', [-gdMaxN gdMaxN]); end + set(get(axNdelay, 'YLabel'), 'String', 'Filter Delay (ms)'); + nAnnot = rpmNharm; + if gn1f > 0, nAnnot = nAnnot + 1; end + if gn2f > 0, nAnnot = nAnnot + 1; end + if dnf > 0, nAnnot = nAnnot + 1; end + nAnnot = max(nAnnot + 1, 2); + yTxt = gdMaxN * 0.9; + yStN = 2 * gdMaxN * 0.9 / nAnnot; + text(fMax*0.3, yTxt, sprintf('combined: %.4fms', gd_gN(2)), ... + 'Parent', axNdelay, 'Color', colCombN, 'FontSize', fontsz-1); + yTxt = yTxt - yStN; + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + fc_rpm = rpmBase * ri; + text(fMax*0.3, yTxt, sprintf('RPM %dHz: %.4fms', fc_rpm, rpmGd{ri}(2)), ... + 'Parent', axNdelay, 'Color', rpmCols{ci}, 'FontSize', fontsz-1); + yTxt = yTxt - yStN; + end + if gn1f > 0 + gd_gn1_v = smooth(-gradient(unwrap(angle(H_gn1))) ./ dw * 1000, 51, 'moving'); + text(fMax*0.3, yTxt, sprintf('N1 %dHz: %.4fms', gn1f, gd_gn1_v(2)), ... + 'Parent', axNdelay, 'Color', colStaticN, 'FontSize', fontsz-1); + yTxt = yTxt - yStN; + end + if gn2f > 0 + gd_gn2_v = smooth(-gradient(unwrap(angle(H_gn2))) ./ dw * 1000, 51, 'moving'); + text(fMax*0.3, yTxt, sprintf('N2 %dHz: %.4fms', gn2f, gd_gn2_v(2)), ... + 'Parent', axNdelay, 'Color', colStaticN*0.85, 'FontSize', fontsz-1); + yTxt = yTxt - yStN; + end + if dnf > 0 + text(fMax*0.3, yTxt, sprintf('D %dHz: %.4fms', dnf, gd_dN(2)), ... + 'Parent', axNdelay, 'Color', colD, 'FontSize', fontsz-1); + end + + cla(axNphase); hold(axNphase, 'on'); + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + ph_ri = smooth(angle(H_rpm{ri}) * 180/pi, 9, 'moving'); + plotFn(axNphase, fVec(fIdx), ph_ri(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + end + ph_gN = smooth(angle(H_gyroN) * 180/pi, 9, 'moving'); + ph_dN = smooth(angle(H_dtermN) * 180/pi, 9, 'moving'); + plotFn(axNphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); + if dnf > 0 + plotFn(axNphase, fVec(fIdx), ph_dN(fIdx), 'Color', colD, 'LineWidth', 1.2); + end + hold(axNphase, 'off'); + PSstyleAxes(axNphase, thm); set(axNphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + set(get(axNphase, 'YLabel'), 'String', 'Phase Delay (deg)'); + + if showStep + cla(axNstep); + line(axNstep, [tStepN(1) tStepN(end)], [1 1], 'Color', colRef, 'LineWidth', 0.5, 'LineStyle', '--'); hold(axNstep, 'on'); + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + plot(axNstep, tStepN, sN_rpm{ri}, 'Color', rpmCols{ci}, 'LineWidth', 0.8); + end + if gn1f > 0, plot(axNstep, tStepN, sN_gn1, 'Color', colStaticN, 'LineWidth', 0.8); end + if gn2f > 0, plot(axNstep, tStepN, sN_gn2, 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); end + plot(axNstep, tStepN, sN_g, 'Color', colCombN, 'LineWidth', 1.5); + if dnf > 0, plot(axNstep, tStepN, sN_d, 'Color', colD, 'LineWidth', 1.2); end + if addNoise + plot(axNstep, tStepN, nsN_g, 'Color', colNoise, 'LineWidth', 0.8); + if dnf > 0, plot(axNstep, tStepN, nsN_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); end + end + hold(axNstep, 'off'); + PSstyleAxes(axNstep, thm); set(axNstep, 'XLim', [tStepN(1) tStepN(end)], 'YLim', [0.8 1.2]); + xlabel(axNstep, 'Time (ms)', 'Color', thm.textPrimary); + set(get(axNstep, 'YLabel'), 'String', 'Step Resp.'); + end + end + end + +end + +%% helpers +function y = magY(H, usedB) + if usedB + y = 20*log10(abs(H) + 1e-12); + else + y = abs(H); end +end +function xl = xlimF(useLog, fMax) + if useLog, xl = [10 fMax]; else xl = [0 fMax]; end end -%% helpers (NOT nested - no closure issues) function y = applyLPF(x, type, hz, Fs) if type == 0 || hz == 0, y = x; return; end types = {'pt1', 'biquad', 'pt2', 'pt3'}; @@ -185,15 +759,96 @@ function doUpdate() y = filter(b, a, x); end +function y = applyNotch_Q(x, center_hz, Q, Fs) + if center_hz == 0, y = x; return; end + [b, a] = PSbfFilters('notch', center_hz, Fs, Q/100); + y = filter(b, a, x); +end + +function H = lpfH(type, hz, Fs, Nfft, types) + H = ones(Nfft, 1); + if type == 0 || hz == 0 || type > numel(types), return; end + [b, a] = PSbfFilters(types{type}, hz, Fs); + [H, ~] = freqz(b, a, Nfft, Fs); + H = H(:); +end + +function H = notchH(center_hz, cutoff_hz, Fs, Nfft) + H = ones(Nfft, 1); + if center_hz == 0, return; end + if cutoff_hz <= 0, cutoff_hz = center_hz * 0.7; end + Q = center_hz / (center_hz - cutoff_hz + 1); + [b, a] = PSbfFilters('notch', center_hz, Fs, Q); + [H, ~] = freqz(b, a, Nfft, Fs); + H = H(:); +end + +function H = notchH_Q(center_hz, Q, Fs, Nfft) + H = ones(Nfft, 1); + if center_hz == 0, return; end + [b, a] = PSbfFilters('notch', center_hz, Fs, Q/100); + [H, ~] = freqz(b, a, Nfft, Fs); + H = H(:); +end + +function annotateLPF(ax, t1, f1, t2, f2, ~, col, fsz, usedB) + names = {'off', 'pt1', 'biquad', 'pt2', 'pt3'}; + if usedB, yLim = [-60 3]; y = -3; yd = -6; + else yLim = [0 1.1]; y = 1.05; yd = -0.08; end + if t1 > 0 && f1 > 0 + text(f1+10, y, sprintf('%s: %dHz', names{t1+1}, f1), 'Parent', ax, ... + 'Color', col*0.7, 'FontSize', fsz-1); + line(ax, [f1 f1], yLim, 'Color', col*0.7, 'LineStyle', '--', 'LineWidth', 0.5); + y = y + yd; + end + if t2 > 0 && f2 > 0 + text(f2+10, y, sprintf('%s: %dHz', names{t2+1}, f2), 'Parent', ax, ... + 'Color', col, 'FontSize', fsz-1); + line(ax, [f2 f2], yLim, 'Color', col, 'LineStyle', '--', 'LineWidth', 0.5); + end +end + +function annotateNotch(ax, gn1f, gn2f, dnf, rpmBase, rpmNharm, rpmCols, colStaticN, colCombN, colD, fsz, usedB) + if usedB, y0 = -5; yd = -5; yLim = [-40 3]; + else y0 = 0.85; yd = -0.07; yLim = [0 1.1]; end + y = y0; + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); + fc = rpmBase * ri; + text(fc+10, y, sprintf('RPM: %dHz', fc), 'Parent', ax, ... + 'Color', rpmCols{ci}, 'FontSize', fsz-1); + line(ax, [fc fc], yLim, 'Color', rpmCols{ci}, 'LineStyle', '--', 'LineWidth', 0.5); + y = y + yd; + end + if gn1f > 0 + text(gn1f+10, y, sprintf('N1: %dHz', gn1f), 'Parent', ax, ... + 'Color', colStaticN, 'FontSize', fsz-1); + line(ax, [gn1f gn1f], yLim, 'Color', colStaticN, 'LineStyle', '--', 'LineWidth', 0.5); + y = y + yd; + end + if gn2f > 0 + text(gn2f+10, y, sprintf('N2: %dHz', gn2f), 'Parent', ax, ... + 'Color', colStaticN*0.85, 'FontSize', fsz-1); + line(ax, [gn2f gn2f], yLim, 'Color', colStaticN*0.85, 'LineStyle', '--', 'LineWidth', 0.5); + y = y + yd; + end + if dnf > 0 + text(dnf+10, y, sprintf('D: %dHz', dnf), 'Parent', ax, ... + 'Color', colD, 'FontSize', fsz-1); + line(ax, [dnf dnf], yLim, 'Color', colD, 'LineStyle', '--', 'LineWidth', 0.5); + end +end + function fp = parseFilterParams(si) - % BF 4.3+: gyro_lpf1_type / gyro_lpf1_static_hz - % BF 4.2: gyro_lowpass_type / gyro_lowpass_hz + % BF enum is same across all versions: 0=PT1, 1=BIQUAD, 2=PT2, 3=PT3 + % Filter disabled when hz == 0, NOT by type value + % Header key names differ: BF 4.3+ uses gyro_lpf1_*, BF 4.2 uses gyro_lowpass_* fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', hval(si, 'gyro_lowpass_type', 0)); fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', 0)); fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', hval(si, 'gyro_lowpass2_type', 0)); fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', 0)); fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', hval(si, 'dterm_lowpass_type', 0)); - fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 100)); + fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 0)); fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', hval(si, 'dterm_lowpass2_type', 0)); fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); @@ -229,45 +884,83 @@ function doUpdate() end end -function mkLabel(fig, txt, cpL, row, rh, bgc, fgc, fsz) - uicontrol(fig, 'Style', 'text', 'String', txt, ... - 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); -end - function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) uicontrol(fig, 'Style', 'text', 'String', txt, ... 'Units', 'normalized', 'Position', [cpL+.01 row cpW-.02 rh], ... 'FontSize', fsz, 'FontWeight', 'bold', 'BackgroundColor', bgc, 'ForegroundColor', col); end -function [hType, rowOut] = mkType(fig, cpL, row, rh, gap, bgc, fgc, initVal, initHz, cb, fsz) - uicontrol(fig, 'Style', 'text', 'String', 'Type:', ... - 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); - % BF header: 0=PT1, 1=Biquad, 2=PT2, 3=PT3; dropdown[1]=OFF - if initHz == 0 - ddVal = 1; % OFF - else - ddVal = min(initVal + 2, 5); - end +function [hType, hHz, rowOut] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, initType, initHz, cb, fsz) + if initHz == 0, ddVal = 1; + else ddVal = min(initType + 2, 5); end + ddW = cW * 0.48; edW = cW * 0.35; lblW = cW * 0.15; hType = uicontrol(fig, 'Style', 'popupmenu', ... 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', ddVal, ... - 'Units', 'normalized', 'Position', [cpL+.06 row .10 rh], 'FontSize', fsz, 'Callback', cb); + 'Units', 'normalized', 'Position', [x0 row ddW rh], 'FontSize', fsz, 'Callback', cb); + hHz = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(initHz)), ... + 'Units', 'normalized', 'Position', [x0+ddW+.005 row edW rh], ... + 'FontSize', fsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); + uicontrol(fig, 'Style', 'text', 'String', 'Hz', ... + 'Units', 'normalized', 'Position', [x0+ddW+edW+.008 row lblW rh], ... + 'FontSize', fsz-1, 'BackgroundColor', [.22 .22 .24], ... + 'ForegroundColor', lc, 'HorizontalAlignment', 'left'); rowOut = row - rh - gap; end -function [hSlider, hLbl, rowOut] = mkSlider(fig, label, cpL, row, rh, gap, bgc, fgc, mn, mx, initVal, cb, fsz) - initVal = max(mn, min(mx, initVal)); - uicontrol(fig, 'Style', 'text', 'String', label, ... - 'Units', 'normalized', 'Position', [cpL+.01 row .05 rh], ... - 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'HorizontalAlignment', 'left'); - step1 = 1/max(mx-mn, 1); step10 = 10/max(mx-mn, 1); - hSlider = uicontrol(fig, 'Style', 'slider', 'Min', mn, 'Max', mx, 'Value', initVal, ... - 'Units', 'normalized', 'Position', [cpL+.06 row .13 rh], ... - 'SliderStep', [step1 step10], 'Callback', cb); - hLbl = uicontrol(fig, 'Style', 'text', 'String', num2str(round(initVal)), ... - 'Units', 'normalized', 'Position', [cpL+.20 row .05 rh], ... - 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', [1 1 .6], 'HorizontalAlignment', 'left'); +function [hCenter, hCutoff, rowOut] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, initCenter, initCutoff, cb, fsz) + halfW = cW / 2; lblW = .03; edW = halfW - lblW - .005; + uicontrol(fig, 'Style', 'text', 'String', 'Ctr:', ... + 'Units', 'normalized', 'Position', [x0 row lblW rh], ... + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); + hCenter = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(initCenter)), ... + 'Units', 'normalized', 'Position', [x0+lblW+.005 row edW rh], ... + 'FontSize', fsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); + uicontrol(fig, 'Style', 'text', 'String', 'Cut:', ... + 'Units', 'normalized', 'Position', [x0+halfW row lblW rh], ... + 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); + hCutoff = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(initCutoff)), ... + 'Units', 'normalized', 'Position', [x0+halfW+lblW+.005 row edW rh], ... + 'FontSize', fsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); rowOut = row - rh - gap; end + +function v = readEdit(h) + v = round(str2double(get(h, 'String'))); + if isnan(v), v = 0; end +end + +function v = readEditF(h) + v = str2double(get(h, 'String')); + if isnan(v), v = 0; end +end + +function drawGradientBar(ax, fMax, gradCols, markerFreqs, markerCols, thm) + cla(ax); + Ngr = 256; + grad = zeros(1, Ngr, 3); + for ch = 1:3, grad(1,:,ch) = linspace(gradCols(1,ch), gradCols(2,ch), Ngr); end + imagesc(ax, [0 fMax], [0 1], grad); + set(ax, 'YTick', [], 'XTick', [], 'XLim', [0 fMax], 'YLim', [0 1], 'Box', 'on'); + set(ax, 'XColor', thm.axesFg, 'YColor', thm.axesFg); + hold(ax, 'on'); + for k = 1:numel(markerFreqs) + f = markerFreqs{k}; + if f > 0 && f <= fMax + line(ax, [f f], [0 1], 'Color', markerCols{k}, 'LineWidth', 2); + plot(ax, f, 0.5, 'v', 'Color', markerCols{k}, 'MarkerSize', 5, 'MarkerFaceColor', markerCols{k}); + end + end + hold(ax, 'off'); +end + +function y = localChirp(t, f0, dur, f1) + % log-sweep chirp + if f0 <= 0, f0 = 1; end + if f1 <= f0, f1 = f0 + 1; end + beta = (f1/f0)^(1/dur); + phase = 2*pi * f0 * (beta.^t - 1) / log(beta); + y = sin(phase); +end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 2c8e02d..560ee23 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -52,7 +52,6 @@ posInfo.resetSpec= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.spectrogramButton2= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.spectrogramButton3= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; -posInfo.filterSimButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.motorNoiseButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.chirpButton= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.saveFig2= [cpL+cpM yTop-rh hw rh]; @@ -137,19 +136,6 @@ 'callback','PSfreqTimeUIcontrol;'); set(guiHandlesSpec2.spectrogramButton3, 'ForegroundColor', colorB); -guiHandlesSpec2.filterSimButton = uicontrol(PSspecfig2,'string','Filter Sim','fontsize',fontsz,... - 'TooltipString','Simulate BF filter chain on gyro data','units','normalized',... - 'Position',[posInfo.filterSimButton],... - 'callback',['try,' ... - 'tmpFcnt=get(guiHandlesSpec2.FileSelect,''Value'');tmpFcnt=tmpFcnt(1);' ... - 'tmpGyro.r=T{tmpFcnt}.gyroADC_0_(tIND{tmpFcnt});' ... - 'tmpGyro.p=T{tmpFcnt}.gyroADC_1_(tIND{tmpFcnt});' ... - 'tmpGyro.y=T{tmpFcnt}.gyroADC_2_(tIND{tmpFcnt});' ... - 'PSfilterSim(tmpGyro,1000*A_lograte(tmpFcnt),SetupInfo{tmpFcnt});' ... - 'clear tmpGyro tmpFcnt;' ... - 'catch e,warndlg([''Filter Sim: '' e.message]),end']); -set(guiHandlesSpec2.filterSimButton, 'ForegroundColor', [.8 .5 0]); - guiHandlesSpec2.motorNoiseButton = uicontrol(PSspecfig2,'string','Motor Noise','fontsize',fontsz,... 'TooltipString','Per-motor spectral analysis and noise comparison','units','normalized',... 'Position',[posInfo.motorNoiseButton],... @@ -212,7 +198,6 @@ cpI{end+1} = struct('h', guiHandlesSpec2.resetSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton2, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton3, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesSpec2.filterSimButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.motorNoiseButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.chirpButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.saveFig2, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); From a1c702051a6429736f62345f69fc118657f8455d Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 10 Mar 2026 14:01:30 +0100 Subject: [PATCH 22/80] fix(filter-sim): log frequency toggle no longer freezes GUI --- src/plot/PSfilterSim.m | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index fb723e6..d445bc1 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -259,6 +259,8 @@ function toggleAutoUpdate() function doUpdate() if ~ishandle(fig), return; end + allFreqAx = [axLmag axLdelay axLphase axNmag axNdelay axNphase]; + glpf1t = get(h.glpf1_type, 'Value') - 1; glpf1f = readEdit(h.glpf1_hz); glpf2t = get(h.glpf2_type, 'Value') - 1; @@ -403,7 +405,6 @@ function doUpdate() colStaticN = [1 .65 .2]; colCombN = [.95 .85 .2]; plotFn = @plot; - if useLog, plotFn = @semilogx; end %% GRADIENT FREQUENCY BARS drawGradientBar(axBarL, fMax, [.2 .7 .8; .3 .9 .5], ... @@ -507,10 +508,11 @@ function doUpdate() plotFn(axLmag, fVec(fIdx), magY(H_rpm{ri}(fIdx), usedB), 'Color', rpmCols{ci}, 'LineWidth', 0.7); end end + fLo = xlimF(useLog, fMax); if usedB - line(axLmag, [0 fMax], [-3 -3], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + line(axLmag, [fLo(1) fMax], [-3 -3], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); else - line(axLmag, [0 fMax], [0.707 0.707], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + line(axLmag, [fLo(1) fMax], [0.707 0.707], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); end hold(axLmag, 'off'); PSstyleAxes(axLmag, thm); @@ -726,6 +728,16 @@ function doUpdate() set(get(axNstep, 'YLabel'), 'String', 'Step Resp.'); end end + + % Set XScale after all plotting to avoid log-axis warnings + if useLog + xsc = 'log'; + else + xsc = 'linear'; + end + for axi = 1:numel(allFreqAx) + if ishandle(allFreqAx(axi)), set(allFreqAx(axi), 'XScale', xsc); end + end end end From eb30d34a77a49fe97233ab1a05d5b55b43994452 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 10 Mar 2026 14:30:49 +0100 Subject: [PATCH 23/80] fix(filter-sim): Show Both hides right column properly --- src/plot/PSfilterSim.m | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index d445bc1..24c06e3 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -421,10 +421,8 @@ function doUpdate() drawGradientBar(axBarN, fMax, [.8 .3 .2; .9 .7 .2], notchFreqs, notchCols, thm); if showBoth set(axBarL, 'Position', [plotL barY plotR-plotL barH]); - set(axBarN, 'Visible', 'off'); else set(axBarL, 'Position', [colL(1) barY colW barH]); - set(axBarN, 'Visible', 'on'); end %% TEST SIGNAL PSD (if sigHzHi > sigHzLo) @@ -468,25 +466,31 @@ function doUpdate() d2on = dlpf2t > 0 && dlpf2f > 0; colNoise = [.9 .25 .25]; - % Show Both: widen left col, hide right col + % Show Both: widen left col, move right col offscreen + offscreen = [-2 -2 .01 .01]; if showBoth wideW = plotR - plotL; set(axLmag, 'Position', [plotL get(axLmag,'Position')*[0;1;0;0] wideW get(axLmag,'Position')*[0;0;0;1]]); set(axLdelay, 'Position', [plotL get(axLdelay,'Position')*[0;1;0;0] wideW get(axLdelay,'Position')*[0;0;0;1]]); set(axLphase, 'Position', [plotL get(axLphase,'Position')*[0;1;0;0] wideW get(axLphase,'Position')*[0;0;0;1]]); set(axLstep, 'Position', [plotL get(axLstep,'Position')*[0;1;0;0] wideW get(axLstep,'Position')*[0;0;0;1]]); - set(axNmag, 'Visible', 'off'); set(axNdelay, 'Visible', 'off'); - set(axNphase, 'Visible', 'off'); set(axNstep, 'Visible', 'off'); + set(axNmag, 'Position', offscreen); cla(axNmag); + set(axNdelay, 'Position', offscreen); cla(axNdelay); + set(axNphase, 'Position', offscreen); cla(axNphase); + set(axNstep, 'Position', offscreen); cla(axNstep); set(hTitleN, 'Visible', 'off'); + set(axBarN, 'Position', offscreen); else set(axLmag, 'Position', [colL(1) get(axLmag,'Position')*[0;1;0;0] colW get(axLmag,'Position')*[0;0;0;1]]); set(axLdelay, 'Position', [colL(1) get(axLdelay,'Position')*[0;1;0;0] colW get(axLdelay,'Position')*[0;0;0;1]]); set(axLphase, 'Position', [colL(1) get(axLphase,'Position')*[0;1;0;0] colW get(axLphase,'Position')*[0;0;0;1]]); set(axLstep, 'Position', [colL(1) get(axLstep,'Position')*[0;1;0;0] colW get(axLstep,'Position')*[0;0;0;1]]); - set(axNmag, 'Visible', 'on'); set(axNdelay, 'Visible', 'on'); - set(axNphase, 'Visible', 'on'); - if showStep, set(axNstep, 'Visible', 'on'); end + set(axNmag, 'Position', [colL(2) rowB1 colW rowH]); + set(axNdelay, 'Position', [colL(2) rowB2 colW rowH]); + set(axNphase, 'Position', [colL(2) rowB3 colW rowH]); + set(axNstep, 'Position', [colL(2) rowB4 colW rowH]); set(hTitleN, 'Visible', 'on'); + set(axBarN, 'Position', [colL(2) barY colW barH]); end %% LOWPASS COLUMN (+ notch overlay when showBoth) From ad473cbbc929f7edd5abd14efba98e41883f7c48 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 10 Mar 2026 15:20:57 +0100 Subject: [PATCH 24/80] fix(filter-sim): unify layout into applyLayout() for all toggle combos --- src/plot/PSfilterSim.m | 109 +++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 24c06e3..cee2fe0 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -218,33 +218,64 @@ function loopRateChanged() end function toggleStepRow() - showStep = get(h.showstep, 'Value'); - if showStep - set(axLstep, 'Visible', 'on'); - set(axNstep, 'Visible', 'on'); - rH = (plotTop - botMargin - 3*rowGap) / 4; - rB1 = plotTop - rH; rB2 = rB1 - rH - rowGap; rB3 = rB2 - rH - rowGap; rB4 = rB3 - rH - rowGap; - set(axLmag, 'Position', [colL(1) rB1 colW rH]); - set(axLdelay, 'Position', [colL(1) rB2 colW rH]); - set(axLphase, 'Position', [colL(1) rB3 colW rH]); - set(axLstep, 'Position', [colL(1) rB4 colW rH]); - set(axNmag, 'Position', [colL(2) rB1 colW rH]); - set(axNdelay, 'Position', [colL(2) rB2 colW rH]); - set(axNphase, 'Position', [colL(2) rB3 colW rH]); - set(axNstep, 'Position', [colL(2) rB4 colW rH]); + applyLayout(); + autoUpdate(); + end + + function applyLayout() + sStep = get(h.showstep, 'Value'); + sBoth = get(h.showboth, 'Value'); + offscr = [-2 -2 .01 .01]; + + if sStep + nRows = 4; else - set(axLstep, 'Visible', 'off'); - set(axNstep, 'Visible', 'off'); - rH = (plotTop - botMargin - 2*rowGap) / 3; - rB1 = plotTop - rH; rB2 = rB1 - rH - rowGap; rB3 = rB2 - rH - rowGap; - set(axLmag, 'Position', [colL(1) rB1 colW rH]); - set(axLdelay, 'Position', [colL(1) rB2 colW rH]); - set(axLphase, 'Position', [colL(1) rB3 colW rH]); - set(axNmag, 'Position', [colL(2) rB1 colW rH]); + nRows = 3; + end + rH = (plotTop - botMargin - (nRows-1)*rowGap) / nRows; + rB1 = plotTop - rH; + rB2 = rB1 - rH - rowGap; + rB3 = rB2 - rH - rowGap; + if nRows == 4 + rB4 = rB3 - rH - rowGap; + end + + if sBoth + w = plotR - plotL; xL = plotL; + else + w = colW; xL = colL(1); + end + + set(axLmag, 'Position', [xL rB1 w rH]); + set(axLdelay, 'Position', [xL rB2 w rH]); + set(axLphase, 'Position', [xL rB3 w rH]); + if sStep + set(axLstep, 'Position', [xL rB4 w rH], 'Visible', 'on'); + else + set(axLstep, 'Position', offscr, 'Visible', 'off'); + end + + if sBoth + set(axNmag, 'Position', offscr); cla(axNmag); + set(axNdelay, 'Position', offscr); cla(axNdelay); + set(axNphase, 'Position', offscr); cla(axNphase); + set(axNstep, 'Position', offscr); cla(axNstep); + set(hTitleN, 'Visible', 'off'); + set(axBarN, 'Position', offscr); + set(axBarL, 'Position', [plotL barY plotR-plotL barH]); + else + set(axNmag, 'Position', [colL(2) rB1 colW rH]); set(axNdelay, 'Position', [colL(2) rB2 colW rH]); set(axNphase, 'Position', [colL(2) rB3 colW rH]); + if sStep + set(axNstep, 'Position', [colL(2) rB4 colW rH], 'Visible', 'on'); + else + set(axNstep, 'Position', offscr, 'Visible', 'off'); + end + set(hTitleN, 'Visible', 'on'); + set(axBarN, 'Position', [colL(2) barY colW barH]); + set(axBarL, 'Position', [colL(1) barY colW barH]); end - autoUpdate(); end function toggleAutoUpdate() @@ -419,11 +450,6 @@ function doUpdate() if gn2f > 0, notchFreqs{end+1} = gn2f; notchCols{end+1} = colStaticN*0.85; end if dnf > 0, notchFreqs{end+1} = dnf; notchCols{end+1} = colD; end drawGradientBar(axBarN, fMax, [.8 .3 .2; .9 .7 .2], notchFreqs, notchCols, thm); - if showBoth - set(axBarL, 'Position', [plotL barY plotR-plotL barH]); - else - set(axBarL, 'Position', [colL(1) barY colW barH]); - end %% TEST SIGNAL PSD (if sigHzHi > sigHzLo) if sigHzHi > sigHzLo && sigDur > 0 @@ -466,32 +492,7 @@ function doUpdate() d2on = dlpf2t > 0 && dlpf2f > 0; colNoise = [.9 .25 .25]; - % Show Both: widen left col, move right col offscreen - offscreen = [-2 -2 .01 .01]; - if showBoth - wideW = plotR - plotL; - set(axLmag, 'Position', [plotL get(axLmag,'Position')*[0;1;0;0] wideW get(axLmag,'Position')*[0;0;0;1]]); - set(axLdelay, 'Position', [plotL get(axLdelay,'Position')*[0;1;0;0] wideW get(axLdelay,'Position')*[0;0;0;1]]); - set(axLphase, 'Position', [plotL get(axLphase,'Position')*[0;1;0;0] wideW get(axLphase,'Position')*[0;0;0;1]]); - set(axLstep, 'Position', [plotL get(axLstep,'Position')*[0;1;0;0] wideW get(axLstep,'Position')*[0;0;0;1]]); - set(axNmag, 'Position', offscreen); cla(axNmag); - set(axNdelay, 'Position', offscreen); cla(axNdelay); - set(axNphase, 'Position', offscreen); cla(axNphase); - set(axNstep, 'Position', offscreen); cla(axNstep); - set(hTitleN, 'Visible', 'off'); - set(axBarN, 'Position', offscreen); - else - set(axLmag, 'Position', [colL(1) get(axLmag,'Position')*[0;1;0;0] colW get(axLmag,'Position')*[0;0;0;1]]); - set(axLdelay, 'Position', [colL(1) get(axLdelay,'Position')*[0;1;0;0] colW get(axLdelay,'Position')*[0;0;0;1]]); - set(axLphase, 'Position', [colL(1) get(axLphase,'Position')*[0;1;0;0] colW get(axLphase,'Position')*[0;0;0;1]]); - set(axLstep, 'Position', [colL(1) get(axLstep,'Position')*[0;1;0;0] colW get(axLstep,'Position')*[0;0;0;1]]); - set(axNmag, 'Position', [colL(2) rowB1 colW rowH]); - set(axNdelay, 'Position', [colL(2) rowB2 colW rowH]); - set(axNphase, 'Position', [colL(2) rowB3 colW rowH]); - set(axNstep, 'Position', [colL(2) rowB4 colW rowH]); - set(hTitleN, 'Visible', 'on'); - set(axBarN, 'Position', [colL(2) barY colW barH]); - end + applyLayout(); %% LOWPASS COLUMN (+ notch overlay when showBoth) From 9c6760e9b3ce57cfe3318251fe897f19c9b16eb6 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 10 Mar 2026 18:30:47 +0100 Subject: [PATCH 25/80] fix(filter-sim): suppress log-axis warnings during redraw --- src/plot/PSfilterSim.m | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index cee2fe0..341f648 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -292,6 +292,10 @@ function doUpdate() allFreqAx = [axLmag axLdelay axLphase axNmag axNdelay axNphase]; + % Suppress "Non-positive limit for logarithmic axis" during redraw + wstate = warning('query', 'all'); + warning('off', 'all'); + glpf1t = get(h.glpf1_type, 'Value') - 1; glpf1f = readEdit(h.glpf1_hz); glpf2t = get(h.glpf2_type, 'Value') - 1; @@ -734,15 +738,14 @@ function doUpdate() end end - % Set XScale after all plotting to avoid log-axis warnings - if useLog - xsc = 'log'; - else - xsc = 'linear'; - end + % Set XScale+XLim atomically + xl = xlimF(useLog, fMax); + if useLog, xsc = 'log'; else xsc = 'linear'; end for axi = 1:numel(allFreqAx) - if ishandle(allFreqAx(axi)), set(allFreqAx(axi), 'XScale', xsc); end + if ishandle(allFreqAx(axi)), set(allFreqAx(axi), 'XLim', xl, 'XScale', xsc); end end + + warning(wstate); end end From 7dd446dc49f0b2fb3e171451bc8c7eb41b19e072 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Wed, 11 Mar 2026 14:22:07 +0100 Subject: [PATCH 26/80] fix(parser): handle BF 2025.12 headerless format in PSparseBFversion --- src/core/PSparseBFversion.m | 10 +++++++--- src/util/PSresizeCP.m | 4 ++++ tests/test_PSparseBFversion.m | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/PSparseBFversion.m b/src/core/PSparseBFversion.m index 5ee4042..d29b4a4 100644 --- a/src/core/PSparseBFversion.m +++ b/src/core/PSparseBFversion.m @@ -25,9 +25,13 @@ % Example: " Emuflight / STM32F405 0.4.1 ..." % Example: "QuickSilver" (from PSquicJson2csv synthetic header) - % Extract firmware type (first word before ' /' or end of string) - parts = strsplit(verStr, '/'); - fwType = strtrim(parts{1}); + % Extract firmware type (first word) + % Old format: " Betaflight / STM32F405 4.5.3 ..." (with /) + % New format: "Betaflight 2025.12.0-beta (hash) STM32F405" (no /) + words = strsplit(strtrim(verStr)); + if ~isempty(words) + fwType = words{1}; + end % Extract version: find pattern X.Y.Z or X.Y anywhere in string tok = regexp(verStr, '(\d+)\.(\d+)\.(\d+)', 'tokens'); diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m index 8714cab..7d72d03 100644 --- a/src/util/PSresizeCP.m +++ b/src/util/PSresizeCP.m @@ -52,6 +52,10 @@ function PSresizeCP(fig, ~) case 'text_right', pos = [cpL+cpW/2 yTop-h cpW/4 h]; yTop = yTop-h-gap; case 'input_left', pos = [cpL+cpM yTop-h cpW/4 h]; case 'input_right',pos = [cpL+cpW/2 yTop-h cpW/4 h]; yTop = yTop-h-gap; + case 'quarter1', pos = [cpL+cpM yTop-h fw/4 h]; + case 'quarter2', pos = [cpL+cpM+fw/4 yTop-h fw/4 h]; + case 'quarter3', pos = [cpL+cpM+fw/2 yTop-h fw/4 h]; + case 'quarter4', pos = [cpL+cpM+3*fw/4 yTop-h fw/4 h]; yTop = yTop-h-gap; case 'panel' continue; otherwise, continue; diff --git a/tests/test_PSparseBFversion.m b/tests/test_PSparseBFversion.m index c0cd2a9..39719c1 100644 --- a/tests/test_PSparseBFversion.m +++ b/tests/test_PSparseBFversion.m @@ -45,3 +45,11 @@ %! assert(strcmp(t, 'Betaflight')); %! assert(maj, 4); %! assert(mnr, 4); + +%!test +%! % BF 2025.12 new header format (no slash, from real BBL) +%! si = {'Firmware revision', 'Betaflight 2025.12.0-beta (080b2f5da) STM32F405'}; +%! [t, maj, mnr] = PSparseBFversion(si); +%! assert(strcmp(t, 'Betaflight'), true, 'fwType should be Betaflight'); +%! assert(maj, 2025); +%! assert(mnr, 12); From 7e1fb11dd2f74e9da73b0fabe644ca3c591df157 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Wed, 11 Mar 2026 14:22:11 +0100 Subject: [PATCH 27/80] =?UTF-8?q?feat(motor-noise):=20RPM=20overlay=20over?= =?UTF-8?q?haul=20for=20Freq=C3=97Time=20and=20Freq=C3=97Throttle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plot/PSfreqTime.m | 123 ++++++++++++++++++++++++++----- src/plot/PSplotDynNotchOverlay.m | 14 +++- src/plot/PSplotRPMOverlay.m | 45 +++++++---- src/plot/PSplotSpec.m | 94 ++++++++++++++++++----- src/ui/PSfreqTimeUIcontrol.m | 50 +++++++++++++ src/ui/PSspecUIcontrol.m | 60 +++++++++++---- 6 files changed, 320 insertions(+), 66 deletions(-) diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 316a572..6369171 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -30,6 +30,59 @@ tmpSpecVal3 = get(guiHandlesSpec3.SpecList, 'Value'); tmpSmoothVal3 = get(guiHandlesSpec3.smoothFactor_select, 'Value'); tmpSubVal3 = get(guiHandlesSpec3.subsampleFactor_select, 'Value'); + +%% Read RPM overlay controls once (before axis loop) +rpmShowDN = true; rpmMotors = [1 2 3 4]; rpmHarms = [1 2 3]; rpmLw = 1; rpmShowEst = false; +if exist('guiHandlesSpec3','var') + try rpmShowDN = get(guiHandlesSpec3.rpmDynNotch, 'Value'); catch, end + try rpmShowEst = get(guiHandlesSpec3.rpmEstChk, 'Value'); catch, end + try + rpmMotors = []; + if get(guiHandlesSpec3.rpmMotor1, 'Value'), rpmMotors(end+1) = 1; end + if get(guiHandlesSpec3.rpmMotor2, 'Value'), rpmMotors(end+1) = 2; end + if get(guiHandlesSpec3.rpmMotor3, 'Value'), rpmMotors(end+1) = 3; end + if get(guiHandlesSpec3.rpmMotor4, 'Value'), rpmMotors(end+1) = 4; end + catch, rpmMotors = [1 2 3 4]; end + try + harmSel = get(guiHandlesSpec3.rpmHarmDd, 'Value'); + harmMap = {[], [1], [2], [3], [1 2], [1 3], [2 3], [1 2 3]}; + rpmHarms = harmMap{harmSel}; + catch, rpmHarms = [1 2 3]; end + try + lwSel = get(guiHandlesSpec3.rpmLwDd, 'Value'); + lwMap = [0.5 1 1.5 2]; + rpmLw = lwMap(lwSel); + catch, rpmLw = 1; end +end + +%% Compute RPM/notch data on-demand (mirrors PSplotSpec2D logic) +if exist('debugmode','var') && exist('debugIdx','var') && exist('T','var') && exist('tIND','var') + if ~exist('notchData','var'), notchData = {}; end + if ~exist('rpmFilterData','var'), rpmFilterData = {}; end + k_ = tmpFileVal3; + if numel(notchData) < k_ || isempty(notchData{k_}) + tmpFFT_ = FFT_FREQ; + if numel(debugIdx) >= k_, tmpFFT_ = debugIdx{k_}.FFT_FREQ; end + if debugmode(k_) == tmpFFT_ + try + if exist('fwMajor','var') && numel(fwMajor) >= k_ && fwMajor(k_) >= 2025 + notchData{k_} = [T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; + else + notchData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_})]; + end + catch, notchData{k_} = []; end + end + end + if numel(rpmFilterData) < k_ || isempty(rpmFilterData{k_}) + tmpRPM_ = 46; + if numel(debugIdx) >= k_, tmpRPM_ = debugIdx{k_}.RPM_FILTER; end + if debugmode(k_) == tmpRPM_ + try rpmFilterData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; + catch, rpmFilterData{k_} = []; end + end + end +end + for i = 1 : 3 delete(subplot('position',posInfo.Spec3Pos(i,:))); try @@ -38,7 +91,7 @@ dat = T{tmpFileVal3}.(fld)(tIND{tmpFileVal3})'; [Tm F specMat{i}] = PStimeFreqCalc(dat', A_lograte(tmpFileVal3), specSmoothFactors(tmpSmoothVal3), timeSmoothFactors(tmpSubVal3)); end - + h2=subplot('position',posInfo.Spec3Pos(i,:)); set(h2, 'Tag', 'PSgrid'); h = imagesc(specMat{i}); @@ -46,16 +99,18 @@ set(gca,'Clim',[ClimScale3], 'fontsize',fontsz,'fontweight','bold') title(''); set(get(gca,'Ylabel'), 'String', ['Frequency (Hz) ' axisLabel{i}], 'Color', th.textPrimary); - set(get(gca,'Xlabel'), 'String', 'Time (sec)', 'Color', th.textPrimary); + if i == 3 + set(get(gca,'Xlabel'), 'String', 'Time (sec)', 'Color', th.textPrimary); + end F2 = F(F<=fLim_freqTime); freqStr = flipud(int2str((0: F2(end) / 5: F2(end))')); timeStr = int2str((0: round(Tm(end)) / 10: round(Tm(end)))'); - + st = find(fliplr(F<=fLim_freqTime),1, 'first'); nd = find(fliplr(F<=fLim_freqTime),1, 'last'); set(gca,'YLim', [st nd]) set(gca,'Ytick', [st : (nd-st) / 5: nd], 'YTickLabel',[freqStr], 'YMinorTick', 'on', 'Xtick', [0 : round( (size(specMat{i},2)-1) / 10) : size(specMat{i},2)],'XTickLabel',[timeStr], 'XMinorTick', 'on', 'TickDir', 'out'); - + try tmpCmapVal = get(guiHandlesSpec3.ColormapSelect, 'Value'); if tmpCmapVal <= 7 @@ -72,38 +127,72 @@ set(cbar, 'Tag', 'PScbar', 'UserData', 'east'); set(get(cbar, 'Label'), 'String', 'Power Spectral density (dB)', 'Color', th.textPrimary); try set(cbar, 'Color', th.axesFg); catch, end - + if i == 3 && (strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisD') || strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisDpf')) - delete(subplot('position',posInfo.Spec3Pos(i,:))); + delete(subplot('position',posInfo.Spec3Pos(i,:))); end box off %% Dynamic notch overlay for FFT_FREQ mode - if exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') + if rpmShowDN && exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') tmpFFTft = FFT_FREQ; if numel(debugIdx) >= tmpFileVal3 tmpFFTft = debugIdx{tmpFileVal3}.FFT_FREQ; end if debugmode(tmpFileVal3) == tmpFFTft && numel(notchData) >= tmpFileVal3 && ~isempty(notchData{tmpFileVal3}) - % match gyro_debug_axis - tmpGdaFt = 0; - if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileVal3 - tmpGdaFt = gyro_debug_axis(tmpFileVal3); - end - if (i - 1) == tmpGdaFt - PSplotDynNotchOverlay(gca, notchData{tmpFileVal3}, size(specMat{i}, 2), size(specMat{i}, 1), F(end), 'time'); - end + PSplotDynNotchOverlay(gca, notchData{tmpFileVal3}, size(specMat{i}, 2), size(specMat{i}, 1), F(end), 'time', rpmLw); end end %% RPM filter overlay (motor frequencies + harmonics) - if exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') + if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') tmpRPMft = 46; if numel(debugIdx) >= tmpFileVal3 tmpRPMft = debugIdx{tmpFileVal3}.RPM_FILTER; end if debugmode(tmpFileVal3) == tmpRPMft && numel(rpmFilterData) >= tmpFileVal3 && ~isempty(rpmFilterData{tmpFileVal3}) - PSplotRPMOverlay(gca, rpmFilterData{tmpFileVal3}, size(specMat{i}, 2), size(specMat{i}, 1), F(end), 'time'); + PSplotRPMOverlay(gca, rpmFilterData{tmpFileVal3}, size(specMat{i}, 2), size(specMat{i}, 1), F(end), 'time', 3, rpmMotors, rpmHarms, rpmLw); + end + end + + %% RPM estimator overlay (works on any log — estimates from spectrum peaks) + if rpmShowEst && ~isempty(rpmHarms) && ~isempty(specMat{i}) + ampForEst = flipud(specMat{i})'; + [~, estHarm] = PSestimateRPM(F, ampForEst, 3); + % smooth estimated harmonics (moving average, NaN-safe) + smK = 7; + for sc = 1:size(estHarm, 2) + col = estHarm(:, sc); + sm = col; + for sw = 1:numel(col) + lo = max(1, sw - floor(smK/2)); + hi = min(numel(col), sw + floor(smK/2)); + chunk = col(lo:hi); + chunk = chunk(~isnan(chunk) & chunk > 0); + if numel(chunk) >= 2, sm(sw) = mean(chunk); else sm(sw) = NaN; end + end + estHarm(:, sc) = sm; + end + hz_per_px = F(end) / size(specMat{i}, 1); + hold on; + estCol = [0 .9 .2; .9 .7 0; .9 .2 0]; + estStyle = {'-'; '--'; ':'}; + numWin = size(specMat{i}, 2); + for nh = rpmHarms + if nh > size(estHarm, 2), continue; end + xPts = []; yPts = []; + for w = 1:numWin + if ~isnan(estHarm(w, nh)) && estHarm(w, nh) > 0 && estHarm(w, nh) < F(end) + y_px = size(specMat{i}, 1) - round(estHarm(w, nh) / hz_per_px); + if y_px >= 1 && y_px <= size(specMat{i}, 1) + xPts(end+1) = w; + yPts(end+1) = y_px; + end + end + end + if ~isempty(xPts) + plot(gca, xPts, yPts, estStyle{nh}, 'LineWidth', rpmLw, 'Color', estCol(nh,:), 'HitTest', 'off'); + end end end diff --git a/src/plot/PSplotDynNotchOverlay.m b/src/plot/PSplotDynNotchOverlay.m index e805a11..ee81e77 100644 --- a/src/plot/PSplotDynNotchOverlay.m +++ b/src/plot/PSplotDynNotchOverlay.m @@ -1,4 +1,4 @@ -function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) +function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode, lw) %% PSplotDynNotchOverlay - overlay dynamic notch frequencies on spectrogram % ax - axes handle % notchMat - Nx3 matrix [notch1_Hz, notch2_Hz, notch3_Hz] (same length as xData) @@ -6,7 +6,9 @@ function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) % imgHeight - number of pixel rows in image (size(img,1)) % freqMax - max frequency in Hz (Nyquist) % mode - 'throttle' or 'time' +% lw - line width (default 1.5) +if nargin < 7 || isempty(lw), lw = 1.5; end if isempty(notchMat) || imgHeight < 2 || freqMax <= 0 return; end @@ -36,8 +38,11 @@ function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) end end end + if numel(yPts) >= 5 + yPts = round(smooth(yPts(:), 5))'; + end if ~isempty(xPts) - h = plot(ax, xPts, yPts, '.', 'MarkerSize', 4); + h = plot(ax, xPts, yPts, '-', 'LineWidth', lw); set(h, 'Color', colors(n,:), 'HitTest', 'off'); end end @@ -68,8 +73,11 @@ function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode) end end end + if numel(yPts) >= 5 + yPts = round(smooth(yPts(:), 5))'; + end if ~isempty(xPts) - h = plot(ax, xPts, yPts, '.', 'MarkerSize', 3); + h = plot(ax, xPts, yPts, '-', 'LineWidth', lw); set(h, 'Color', colors(n,:), 'HitTest', 'off'); end end diff --git a/src/plot/PSplotRPMOverlay.m b/src/plot/PSplotRPMOverlay.m index 34907ab..19fbd1a 100644 --- a/src/plot/PSplotRPMOverlay.m +++ b/src/plot/PSplotRPMOverlay.m @@ -1,4 +1,4 @@ -function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonics) +function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonics, motors, harmonics, lw) %% PSplotRPMOverlay - overlay RPM filter motor frequencies on spectrogram % ax - axes handle % rpmMat - Nx4 matrix [motor1_Hz, motor2_Hz, motor3_Hz, motor4_Hz] @@ -6,22 +6,31 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic % imgHeight - number of pixel rows in image % freqMax - max frequency in Hz % mode - 'throttle' or 'time' -% nHarmonics - number of harmonics to draw (default 3) +% nHarmonics - max harmonics (default 3) +% motors - vector of motor indices to draw, e.g. [1 3] (default all) +% harmonics - vector of harmonic indices to draw, e.g. [1 3] (default 1:nHarmonics) +% lw - line width (default 1) -if nargin < 7, nHarmonics = 3; end +if nargin < 7 || isempty(nHarmonics), nHarmonics = 3; end +if nargin < 8 || isempty(motors), motors = 1:min(4, size(rpmMat, 2)); end +if nargin < 9 || isempty(harmonics), harmonics = 1:nHarmonics; end +if nargin < 10 || isempty(lw), lw = 1; end if isempty(rpmMat) || imgHeight < 2 || freqMax <= 0 return; end hold(ax, 'on'); -motorCol = [0 .9 0; .9 .9 0; .9 0 0; .3 .5 1]; % green yellow red blue +motorCol = [.9 0 0; .9 .6 0; 0 .8 .8; 0 .9 0]; % M1=red M2=orange M3=cyan M4=green lineStyles = {'-'; '--'; ':'}; hz_per_pixel = freqMax / imgHeight; -nMotors = min(4, size(rpmMat, 2)); if strcmp(mode, 'throttle') - for m = 1:nMotors - for harm = 1:nHarmonics + for mi = 1:numel(motors) + m = motors(mi); + if m > size(rpmMat, 2), continue; end + ci = min(m, size(motorCol, 1)); + for hi = 1:numel(harmonics) + harm = harmonics(hi); xPts = []; yPts = []; for bin = 1:100 @@ -39,10 +48,11 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic end end end + if numel(yPts) >= 5, yPts = round(smooth(yPts(:), 5))'; end if ~isempty(xPts) lstyle = lineStyles{min(harm, numel(lineStyles))}; - h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); - set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); + plot(ax, xPts, yPts, lstyle, 'LineWidth', lw, ... + 'Color', motorCol(ci,:), 'HitTest', 'off'); end end end @@ -53,14 +63,18 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic if numSamples == 0 || numWindows == 0, return; end samplesPerWindow = max(1, round(numSamples / numWindows)); - for m = 1:nMotors - for harm = 1:nHarmonics + for mi = 1:numel(motors) + m = motors(mi); + if m > size(rpmMat, 2), continue; end + ci = min(m, size(motorCol, 1)); + for hi = 1:numel(harmonics) + harm = harmonics(hi); xPts = []; yPts = []; for w = 1:numWindows lo = max(1, round((w-1) * samplesPerWindow) + 1); - hi = min(numSamples, round(w * samplesPerWindow)); - vals = rpmMat(lo:hi, m) * harm; + hi2 = min(numSamples, round(w * samplesPerWindow)); + vals = rpmMat(lo:hi2, m) * harm; vals = vals(vals > 0 & vals < freqMax); if ~isempty(vals) avgHz = nanmean(vals); @@ -71,10 +85,11 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic end end end + if numel(yPts) >= 5, yPts = round(smooth(yPts(:), 5))'; end if ~isempty(xPts) lstyle = lineStyles{min(harm, numel(lineStyles))}; - h = plot(ax, xPts, yPts, lstyle, 'LineWidth', 1); - set(h, 'Color', motorCol(m,:), 'HitTest', 'off'); + plot(ax, xPts, yPts, lstyle, 'LineWidth', lw, ... + 'Color', motorCol(ci,:), 'HitTest', 'off'); end end end diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 31d93c6..c95af49 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -12,6 +12,59 @@ th = PStheme(); +%% Read RPM overlay controls +rpmShowDN = true; rpmMotors = [1 2 3 4]; rpmHarms = [1 2 3]; rpmLw = 1; rpmShowEst = false; +if exist('guiHandlesSpec','var') + try rpmShowDN = get(guiHandlesSpec.rpmDynNotch, 'Value'); catch, end + try + rpmMotors = []; + if get(guiHandlesSpec.rpmMotor1, 'Value'), rpmMotors(end+1) = 1; end + if get(guiHandlesSpec.rpmMotor2, 'Value'), rpmMotors(end+1) = 2; end + if get(guiHandlesSpec.rpmMotor3, 'Value'), rpmMotors(end+1) = 3; end + if get(guiHandlesSpec.rpmMotor4, 'Value'), rpmMotors(end+1) = 4; end + catch, rpmMotors = [1 2 3 4]; end + try + harmSel = get(guiHandlesSpec.rpmHarmDd, 'Value'); + harmMap = {[], [1], [2], [3], [1 2], [1 3], [2 3], [1 2 3]}; + rpmHarms = harmMap{harmSel}; + catch, rpmHarms = [1 2 3]; end + try + lwSel = get(guiHandlesSpec.rpmLwDd, 'Value'); + lwMap = [0.5 1 1.5 2]; + rpmLw = lwMap(lwSel); + catch, rpmLw = 1; end + try rpmShowEst = get(guiHandlesSpec.rpmEstChk, 'Value'); catch, end +end + +%% Compute RPM/notch data on-demand (mirrors PSplotSpec2D logic) +if exist('debugmode','var') && exist('debugIdx','var') && exist('T','var') && exist('tIND','var') + if ~exist('notchData','var'), notchData = {}; end + if ~exist('rpmFilterData','var'), rpmFilterData = {}; end + for k_ = 1:numel(T) + if numel(notchData) < k_ || isempty(notchData{k_}) + tmpFFT_ = FFT_FREQ; + if numel(debugIdx) >= k_, tmpFFT_ = debugIdx{k_}.FFT_FREQ; end + if debugmode(k_) == tmpFFT_ + try + if exist('fwMajor','var') && numel(fwMajor) >= k_ && fwMajor(k_) >= 2025 + notchData{k_} = [T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; + else + notchData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_})]; + end + catch, notchData{k_} = []; end + end + end + if numel(rpmFilterData) < k_ || isempty(rpmFilterData{k_}) + tmpRPM_ = 46; + if numel(debugIdx) >= k_, tmpRPM_ = debugIdx{k_}.RPM_FILTER; end + if debugmode(k_) == tmpRPM_ + try rpmFilterData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; + catch, rpmFilterData{k_} = []; end + end + end + end +end + psdIdx = get(guiHandlesSpec.checkboxPSD, 'Value') + 1; set(guiHandlesSpec.climMax_input, 'String', num2str(climScale(psdIdx, 1))); set(guiHandlesSpec.climMax_input2, 'String', num2str(climScale(psdIdx, 2))); @@ -165,27 +218,20 @@ xlabel('% Throttle','fontweight','bold','Color',th.textPrimary) %% Dynamic notch overlay for FFT_FREQ mode - if exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') + if rpmShowDN && exist('notchData','var') && exist('debugmode','var') && exist('debugIdx','var') tmpFileK = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); tmpFFTk = FFT_FREQ; if numel(debugIdx) >= tmpFileK tmpFFTk = debugIdx{tmpFileK}.FFT_FREQ; end if debugmode(tmpFileK) == tmpFFTk && numel(notchData) >= tmpFileK && ~isempty(notchData{tmpFileK}) - % match gyro_debug_axis - tmpGdaK = 0; - if exist('gyro_debug_axis','var') && numel(gyro_debug_axis) >= tmpFileK - tmpGdaK = gyro_debug_axis(tmpFileK); - end - if (c2(p) - 1) == tmpGdaK - maxHzOverlay = (A_lograte(tmpFileK) / 2) * 1000; - PSplotDynNotchOverlay(gca, notchData{tmpFileK}, T{tmpFileK}.setpoint_3_(tIND{tmpFileK}) / 10, size(img, 1), maxHzOverlay, 'throttle'); - end + maxHzOverlay = (A_lograte(tmpFileK) / 2) * 1000; + PSplotDynNotchOverlay(gca, notchData{tmpFileK}, T{tmpFileK}.setpoint_3_(tIND{tmpFileK}) / 10, size(img, 1), maxHzOverlay, 'throttle', rpmLw); end end %% RPM filter overlay (motor frequencies + harmonics) - if exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') + if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') tmpFileK2 = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); tmpRPMk = 46; if numel(debugIdx) >= tmpFileK2 @@ -193,12 +239,12 @@ end if debugmode(tmpFileK2) == tmpRPMk && numel(rpmFilterData) >= tmpFileK2 && ~isempty(rpmFilterData{tmpFileK2}) maxHzOverlay2 = (A_lograte(tmpFileK2) / 2) * 1000; - PSplotRPMOverlay(gca, rpmFilterData{tmpFileK2}, T{tmpFileK2}.setpoint_3_(tIND{tmpFileK2}) / 10, size(img, 1), maxHzOverlay2, 'throttle'); + PSplotRPMOverlay(gca, rpmFilterData{tmpFileK2}, T{tmpFileK2}.setpoint_3_(tIND{tmpFileK2}) / 10, size(img, 1), maxHzOverlay2, 'throttle', 3, rpmMotors, rpmHarms, rpmLw); end end %% Estimated RPM overlay from spectrum peak detection - if get(guiHandlesSpec.checkboxEstRPM, 'Value') && ~isempty(ampmat{p}) && ~isempty(freq{p}) + if rpmShowEst && ~isempty(rpmHarms) && ~isempty(ampmat{p}) && ~isempty(freq{p}) % find a throttle row that has freq data (not all zeros) freqAx = []; for rr = 1:size(freq{p}, 1) @@ -206,13 +252,27 @@ end if ~isempty(freqAx) [estFund, estHarm] = PSestimateRPM(freqAx, ampmat{p}, 3); - % convert Hz to image pixel coords (img is flipped: row 1 = max Hz) + % smooth estimated harmonics (moving average, NaN-safe) + smK = 7; + for sc = 1:size(estHarm, 2) + col = estHarm(:, sc); + sm = col; + for sw = 1:numel(col) + lo = max(1, sw - floor(smK/2)); + hi = min(numel(col), sw + floor(smK/2)); + chunk = col(lo:hi); + chunk = chunk(~isnan(chunk) & chunk > 0); + if numel(chunk) >= 2, sm(sw) = mean(chunk); else sm(sw) = NaN; end + end + estHarm(:, sc) = sm; + end maxHzEst = (A_lograte(get(guiHandlesSpec.FileSelect{c1(p)}, 'Value')) / 2) * 1000; hz_per_px = maxHzEst / size(img, 1); hold on; - estCol = [0 .9 .2; .9 .7 0; .9 .2 0]; % green, orange, red for 1st/2nd/3rd + estCol = [0 .9 .2; .9 .7 0; .9 .2 0]; estStyle = {'-'; '--'; ':'}; - for nh = 1:3 + for nh = rpmHarms + if nh > size(estHarm, 2), continue; end xPts = []; yPts = []; for tb = 1:100 if ~isnan(estHarm(tb, nh)) && estHarm(tb, nh) > 0 && estHarm(tb, nh) < maxHzEst @@ -224,7 +284,7 @@ end end if ~isempty(xPts) - h = plot(xPts, yPts, estStyle{nh}, 'LineWidth', 1.5); + h = plot(xPts, yPts, estStyle{nh}, 'LineWidth', rpmLw); set(h, 'Color', estCol(nh,:), 'HitTest', 'off'); end end diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 43f8095..491af63 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -56,6 +56,16 @@ posInfo.clim3Max2_input = [cpL+cpW/2 yTop-rh cpW/4 rh]; yTop=yTop-rh-gap; ClimScale3 = [-30 10]; posInfo.sub100HzfreqTime = [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +% RPM overlay controls +qw = fw/4; +posInfo.rpmMotor1 = [cpL+cpM yTop-rh qw rh]; +posInfo.rpmMotor2 = [cpL+cpM+qw yTop-rh qw rh]; +posInfo.rpmMotor3 = [cpL+cpM+2*qw yTop-rh qw rh]; +posInfo.rpmMotor4 = [cpL+cpM+3*qw yTop-rh qw rh]; yTop=yTop-rh-gap; +posInfo.rpmHarmDd = [cpL+cpM yTop-ddh hw ddh]; +posInfo.rpmLwDd = [cpL+cpW/2 yTop-ddh hw ddh]; yTop=yTop-ddh-gap; +posInfo.rpmDynNotch = [cpL+cpM yTop-rh hw rh]; +posInfo.rpmEstChk = [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.playerBtn3 = [cpL+cpM yTop-rh fw rh]; if exist('PSspecfig3','var') && ishandle(PSspecfig3) @@ -127,6 +137,38 @@ guiHandlesSpec3.sub100HzfreqTime = uicontrol(PSspecfig3,'Style','checkbox','String','sub 100Hz','fontsize',fontsz,'ForegroundColor',panelFg,'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.sub100HzfreqTime],'callback','@selection2;updateSpec=1; PSfreqTime;'); +% RPM overlay motor checkboxes +motorCols = {[.9 0 0], [.9 .6 0], [0 .8 .8], [0 .9 0]}; +motorNames = {'M1','M2','M3','M4'}; +rpmCb = 'updateSpec=1; PSfreqTime;'; +for mi = 1:4 + fld = sprintf('rpmMotor%d', mi); + guiHandlesSpec3.(fld) = uicontrol(PSspecfig3, 'Style','checkbox', 'String', motorNames{mi}, ... + 'fontsize', fontsz-1, 'Value', 0, ... + 'ForegroundColor', motorCols{mi}, 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.(fld), 'callback', rpmCb); +end + +guiHandlesSpec3.rpmHarmDd = uicontrol(PSspecfig3, 'Style','popupmenu', ... + 'String', {'RPM off','1st','2nd','3rd','1st & 2nd','1st & 3rd','2nd & 3rd','All harm.'}, ... + 'fontsize', fontsz, 'Value', 2, ... + 'units','normalized', 'Position', posInfo.rpmHarmDd, 'callback', rpmCb); + +guiHandlesSpec3.rpmLwDd = uicontrol(PSspecfig3, 'Style','popupmenu', ... + 'String', {'lw 0.5','lw 1','lw 1.5','lw 2'}, ... + 'fontsize', fontsz, 'Value', 2, ... + 'units','normalized', 'Position', posInfo.rpmLwDd, 'callback', rpmCb); + +guiHandlesSpec3.rpmDynNotch = uicontrol(PSspecfig3, 'Style','checkbox', 'String','Dyn Notch', ... + 'fontsize', fontsz, 'Value', 0, ... + 'ForegroundColor', [0 .8 .8], 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.rpmDynNotch, 'callback', rpmCb); + +guiHandlesSpec3.rpmEstChk = uicontrol(PSspecfig3, 'Style','checkbox', 'String','RPM est.', ... + 'fontsize', fontsz, 'Value', 0, ... + 'ForegroundColor', [.6 .9 .6], 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.rpmEstChk, 'callback', rpmCb); + guiHandlesSpec3.playerBtn = uicontrol(PSspecfig3,'string','Player','fontsize',fontsz,... 'TooltipString','Animated spectrum playback over time','units','normalized',... 'Position',[posInfo.playerBtn3],... @@ -156,6 +198,14 @@ cpI{end+1} = struct('h', guiHandlesSpec3.climMax1_input, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec3.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec3.sub100HzfreqTime, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmMotor1, 'type','quarter1', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmMotor2, 'type','quarter2', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmMotor3, 'type','quarter3', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmMotor4, 'type','quarter4', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmHarmDd, 'type','dd_left', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmLwDd, 'type','dd_right', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmDynNotch, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec3.rpmEstChk, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec3.playerBtn, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); setappdata(PSspecfig3, 'PSplotGrid', struct('plotL',plotL1, 'colGap',0, ... 'ncols',1, 'rows',rows, 'rowH',0.255, 'margin',0.10)); diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index a4113fc..bc0a0f0 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -78,10 +78,15 @@ posInfo.controlFreq2Cutoff = [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.checkbox2d= [cpL+cpM yTop-rh hw rh]; posInfo.checkboxPSD= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; -posInfo.checkboxEstRPM= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; -posInfo.rpmLegend1= [cpL+cpM yTop-rhs fw rhs]; yTop=yTop-rhs-cpMv; -posInfo.rpmLegend2= [cpL+cpM yTop-rhs fw rhs]; yTop=yTop-rhs-cpMv; -posInfo.rpmLegend3= [cpL+cpM yTop-rhs fw rhs]; +qw = fw/4; +posInfo.rpmMotor1 = [cpL+cpM yTop-rh qw rh]; +posInfo.rpmMotor2 = [cpL+cpM+qw yTop-rh qw rh]; +posInfo.rpmMotor3 = [cpL+cpM+2*qw yTop-rh qw rh]; +posInfo.rpmMotor4 = [cpL+cpM+3*qw yTop-rh qw rh]; yTop=yTop-rh-gap; +posInfo.rpmHarmDd = [cpL+cpM yTop-ddh hw ddh]; +posInfo.rpmLwDd = [cpL+cpW/2 yTop-ddh hw ddh]; yTop=yTop-ddh-gap; +posInfo.rpmDynNotch = [cpL+cpM yTop-rh hw rh]; +posInfo.rpmEstChk = [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.AphasedelayText1=[cols(1)+0.02 .984-tbOff_spec colW4*0.7 .02]; posInfo.AphasedelayText2=[cols(2)+0.02 .984-tbOff_spec colW4*0.7 .02]; @@ -173,13 +178,36 @@ 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec;'); set(guiHandlesSpec.checkboxPSD, 'Value', 0); -guiHandlesSpec.checkboxEstRPM =uicontrol(PSspecfig,'Style','checkbox','String','Est. RPM','fontsize',fontsz,'TooltipString', ['Estimate motor RPM from spectrum (2D heatmap only)'],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxEstRPM],'callback','updateSpec=1;PSplotSpec;'); -set(guiHandlesSpec.checkboxEstRPM, 'Value', 0); +motorCols = {[.9 0 0], [.9 .6 0], [0 .8 .8], [0 .9 0]}; +motorNames = {'M1','M2','M3','M4'}; +rpmCb = 'updateSpec=1;PSplotSpec;'; +for mi = 1:4 + fld = sprintf('rpmMotor%d', mi); + guiHandlesSpec.(fld) = uicontrol(PSspecfig, 'Style','checkbox', 'String', motorNames{mi}, ... + 'fontsize', fontsz-1, 'Value', 0, ... + 'ForegroundColor', motorCols{mi}, 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.(fld), 'callback', rpmCb); +end + +guiHandlesSpec.rpmHarmDd = uicontrol(PSspecfig, 'Style','popupmenu', ... + 'String', {'RPM off','1st','2nd','3rd','1st & 2nd','1st & 3rd','2nd & 3rd','All harm.'}, ... + 'fontsize', fontsz, 'Value', 2, ... + 'units','normalized', 'Position', posInfo.rpmHarmDd, 'callback', rpmCb); + +guiHandlesSpec.rpmLwDd = uicontrol(PSspecfig, 'Style','popupmenu', ... + 'String', {'lw 0.5','lw 1','lw 1.5','lw 2'}, ... + 'fontsize', fontsz, 'Value', 2, ... + 'units','normalized', 'Position', posInfo.rpmLwDd, 'callback', rpmCb); + +guiHandlesSpec.rpmDynNotch = uicontrol(PSspecfig, 'Style','checkbox', 'String','Dyn Notch', ... + 'fontsize', fontsz, 'Value', 0, ... + 'ForegroundColor', [0 .8 .8], 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.rpmDynNotch, 'callback', rpmCb); -guiHandlesSpec.rpmLegend1 = uicontrol(PSspecfig,'style','text','string','--- 1st (fund)','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[0 .7 .15],'FontWeight','bold','Position',[posInfo.rpmLegend1]); -guiHandlesSpec.rpmLegend2 = uicontrol(PSspecfig,'style','text','string','-- 2nd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .7 0],'FontWeight','bold','Position',[posInfo.rpmLegend2]); -guiHandlesSpec.rpmLegend3 = uicontrol(PSspecfig,'style','text','string','... 3rd harm','fontsize',fontsz-1,'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',[.9 .2 0],'FontWeight','bold','Position',[posInfo.rpmLegend3]); +guiHandlesSpec.rpmEstChk = uicontrol(PSspecfig, 'Style','checkbox', 'String','RPM est.', ... + 'fontsize', fontsz, 'Value', 0, ... + 'ForegroundColor', [.6 .9 .6], 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.rpmEstChk, 'callback', rpmCb); guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); guiHandlesSpec.controlFreq1Cutoff = uicontrol(PSspecfig,'style','edit','string',[num2str(round(Flim1))],'fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','Position',[posInfo.controlFreq1Cutoff],... @@ -253,10 +281,14 @@ cpI{end+1} = struct('h', guiHandlesSpec.controlFreq2Cutoff, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec.checkbox2d, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec.checkboxPSD, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesSpec.checkboxEstRPM, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend1, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); -cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend2, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); -cpI{end+1} = struct('h', guiHandlesSpec.rpmLegend3, 'type','full', 'row',0, 'col',0, 'hpx',rhs_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmMotor1, 'type','quarter1', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmMotor2, 'type','quarter2', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmMotor3, 'type','quarter3', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmMotor4, 'type','quarter4', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmHarmDd, 'type','dd_left', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmLwDd, 'type','dd_right', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmDynNotch, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec.rpmEstChk, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); setappdata(PSspecfig, 'PSplotGrid', struct('plotL',plotL4, 'colGap',colGap4, ... 'ncols',4, 'rows',rows, 'rowH',0.21, 'margin',0.04)); % Per-column top-bar widgets: {handle, col_index, xOffset_from_col_start} From e0ad651db49da1ed2c2c1acffb920150973de20f Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Wed, 11 Mar 2026 18:04:34 +0100 Subject: [PATCH 28/80] feat(ui): PID Slider Tool with PTB/BF math toggle --- PIDscope.m | 30 ++-- src/ui/PSsliderTool.m | 257 ++++++++++++++++++++++++++++++++++ tests/test_PSsliderToolMath.m | 100 +++++++++++++ 3 files changed, 379 insertions(+), 8 deletions(-) create mode 100644 src/ui/PSsliderTool.m create mode 100644 tests/test_PSsliderToolMath.m diff --git a/PIDscope.m b/PIDscope.m index a3bd02b..eefaaed 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -197,6 +197,7 @@ posInfo.linewidth = [cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; posInfo.spectrogramButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.TuningButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.PIDsliderButton=[cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.filterSimButton=[cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.PIDErrorButton = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; posInfo.FlightStatsButton=[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; @@ -293,6 +294,18 @@ 'callback','PStuneUIcontrol'); set(guiHandles.TuningButton, 'ForegroundColor', colorB); +guiHandles.PIDsliderButton = uicontrol(PSfig,'string','PID Slider Tool','fontsize',fontsz,... + 'TooltipString','Interactive PID ratio calculator','units','normalized',... + 'Position',[posInfo.PIDsliderButton],... + 'callback',['try,' ... + 'tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... + 'PSsliderTool(rollPIDF{tmpFcnt},pitchPIDF{tmpFcnt},yawPIDF{tmpFcnt});' ... + 'clear tmpFcnt;' ... + 'catch,' ... + 'PSsliderTool();' ... + 'end']); +set(guiHandles.PIDsliderButton, 'ForegroundColor', [.40 .80 1.0]); + guiHandles.filterSimButton = uicontrol(PSfig,'string','Filter Sim','fontsize',fontsz,... 'TooltipString','Simulate BF filter chain (theoretical response)','units','normalized',... 'Position',[posInfo.filterSimButton],... @@ -418,14 +431,15 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para cpItems{end+1} = struct('h', guiHandles.linewidth, 'type','right', 'row',6, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.spectrogramButton, 'type','full', 'row',7, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.TuningButton, 'type','full', 'row',8, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.filterSimButton, 'type','full', 'row',9, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',10, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',10, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',11, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.DispInfoButton, 'type','right', 'row',11, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.saveFig, 'type','left', 'row',12, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.saveSettings, 'type','right', 'row',12, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.PIDtuningService, 'type','full', 'row',13, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDsliderButton, 'type','full', 'row',9, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.filterSimButton, 'type','full', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',11, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',12, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.DispInfoButton, 'type','right', 'row',12, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveFig, 'type','left', 'row',13, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.saveSettings, 'type','right', 'row',13, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.PIDtuningService, 'type','full', 'row',14, 'col',0, 'nrows',0); nrows = max(cellfun(@(x) x.row, cpItems)); cpItems = [{struct('h', controlpanel, 'type','panel', 'row',0, 'col',0, 'nrows',nrows)}, cpItems]; PSregisterResize(PSfig, cpPx, cpItems, 'rows'); diff --git a/src/ui/PSsliderTool.m b/src/ui/PSsliderTool.m new file mode 100644 index 0000000..3722d0a --- /dev/null +++ b/src/ui/PSsliderTool.m @@ -0,0 +1,257 @@ +function PSsliderTool(rollPID_str, pitchPID_str, yawPID_str) +%% PSsliderTool - interactive PID ratio calculator +% Popup window with firmware presets and ratio-based sliders. +% Two math modes: PTB (P-anchor, ratio-based) and BF (simplified_tuning.c style). + +thm = PStheme(); +fontsz = thm.fontsz; + +fig = findobj('Type', 'figure', 'Name', 'PID Slider Tool'); +if ~isempty(fig), close(fig); end + +screensz = get(0, 'ScreenSize'); +figW = round(min(720, screensz(3) * 0.55)); +figH = round(min(520, screensz(4) * 0.55)); +figX = round((screensz(3) - figW) / 2); +figY = round((screensz(4) - figH) / 2); +fig = figure('Name', 'PID Slider Tool', 'NumberTitle', 'off', ... + 'Color', thm.figBg, ... + 'Position', [figX figY figW figH], ... + 'MenuBar', 'none', 'ToolBar', 'none', 'Resize', 'off'); + +% Firmware presets: {name, P, I, D, yawDfactor} +presets = { ... + 'Betaflight', NaN, NaN, NaN, 1.0; + 'INAV', 40, 75, 20, 0.0; + 'FETTEC', 1.00, 0.01, 2.50, 0.08; + 'KISS_ULTRA', 1.5, 0.05, 5.0, 0.04; + 'QuickSilver', 42, 85, 30, 1.0; + 'ArduPilot', 0.135,0.090,0.0036,5.0; +}; + +hasLogPID = nargin >= 3 && ~isempty(rollPID_str); +if hasLogPID + rp = sscanf(strrep(rollPID_str, ' ', ''), '%f,%f,%f'); + yp = sscanf(strrep(yawPID_str, ' ', ''), '%f,%f,%f'); + if numel(rp) >= 3 && numel(yp) >= 3 + ydf = 1.0; + if rp(3) > 0, ydf = yp(3) / rp(3); end + presets(end+1,:) = {'From Log', rp(1), rp(2), rp(3), ydf}; + end +end + +fwNames = presets(:,1); +setappdata(fig, 'presets', presets); + +% Slider names per math mode +slNamesPTB = {'PD Ratio', 'PI Ratio', 'Roll-Pitch Ratio', 'Yaw Ratio', 'Master Multiplier'}; +slNamesBF = {'D Gain', 'PI Gain', 'Roll-Pitch Ratio', 'Yaw Ratio', 'Master Multiplier'}; +setappdata(fig, 'slNamesPTB', slNamesPTB); +setappdata(fig, 'slNamesBF', slNamesBF); + +% --- Controls --- +h = struct(); + +% Row 0: Reset + firmware + math mode +h.resetBtn = uicontrol(fig, 'Style', 'pushbutton', 'String', 'Reset', ... + 'Position', [20 figH-50 70 30], ... + 'FontSize', fontsz, 'FontWeight', 'bold', ... + 'BackgroundColor', [.85 .55 .15], 'ForegroundColor', [0 0 0], ... + 'Callback', @cb_reset); + +h.fwDd = uicontrol(fig, 'Style', 'popupmenu', 'String', fwNames, ... + 'Position', [100 figH-50 130 28], ... + 'FontSize', fontsz, ... + 'Callback', @cb_fwChanged); + +uicontrol(fig, 'Style', 'text', 'String', 'Math:', ... + 'Position', [240 figH-50 40 22], 'FontSize', fontsz, ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'right'); + +h.mathDd = uicontrol(fig, 'Style', 'popupmenu', ... + 'String', {'PTB (ratio)', 'Betaflight'}, ... + 'Position', [285 figH-50 120 28], ... + 'FontSize', fontsz, ... + 'Callback', @cb_mathChanged); + +% Row 1: headers +hdrY = figH - 90; +uicontrol(fig, 'Style', 'text', 'String', 'PID start values', ... + 'Position', [140 hdrY 170 22], 'FontSize', fontsz+1, 'FontWeight', 'bold', ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'center'); +uicontrol(fig, 'Style', 'text', 'String', 'PID test values', ... + 'Position', [390 hdrY 300 22], 'FontSize', fontsz+1, 'FontWeight', 'bold', ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'center'); + +% Row 2-4: P/I/D edit boxes + test value labels +labels = {'P', 'I', 'D'}; +axNames = {'Roll', 'Pitch', 'Yaw'}; +for k = 1:3 + yy = hdrY - 28*k; + uicontrol(fig, 'Style', 'text', 'String', labels{k}, ... + 'Position', [170 yy 20 22], ... + 'FontSize', fontsz, 'FontWeight', 'bold', ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg); + h.startEdit(k) = uicontrol(fig, 'Style', 'edit', 'String', '', ... + 'Position', [195 yy 90 24], 'FontSize', fontsz, ... + 'Callback', @cb_recalc); +end +for k = 1:3 + yy = hdrY - 28*k; + h.testLbl(k) = uicontrol(fig, 'Style', 'text', ... + 'String', [axNames{k} ' PID:'], ... + 'Position', [390 yy 300 22], 'FontSize', fontsz, ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'left'); +end + +% Math mode description label +h.mathDesc = uicontrol(fig, 'Style', 'text', 'String', '', ... + 'Position', [420 figH-50 290 22], 'FontSize', fontsz-1, ... + 'ForegroundColor', [.65 .65 .65], 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'left'); + +% Sliders +slY0 = hdrY - 28*3 - 45; +slGap = 55; +slLblW = 200; +slX = 210; +slW = figW - slX - 30; + +for k = 1:5 + yy = slY0 - (k-1)*slGap; + h.slLbl(k) = uicontrol(fig, 'Style', 'text', ... + 'String', sprintf('%s: 1.00', slNamesPTB{k}), ... + 'Position', [5 yy slLblW 22], 'FontSize', fontsz, ... + 'ForegroundColor', thm.textPrimary, 'BackgroundColor', thm.figBg, ... + 'HorizontalAlignment', 'right'); + h.sl(k) = uicontrol(fig, 'Style', 'slider', ... + 'Position', [slX yy slW 20], ... + 'Min', 0.25, 'Max', 2.00, 'Value', 1.00, ... + 'SliderStep', [0.01/1.75, 0.05/1.75], ... + 'Callback', @cb_sliderMoved); +end + +setappdata(fig, 'h', h); + +% Init +if hasLogPID, set(h.fwDd, 'Value', numel(fwNames)); end +cb_mathChanged(h.mathDd, []); +cb_fwChanged(h.fwDd, []); + +% ---- Nested callbacks ---- + function cb_fwChanged(src, ~) + idx = get(src, 'Value'); + pr = getappdata(fig, 'presets'); + for e = 1:3 + v = pr{idx, e+1}; + if isnan(v), set(h.startEdit(e), 'String', ''); + else set(h.startEdit(e), 'String', num2str(v)); end + end + cb_reset([], []); + end + + function cb_mathChanged(~, ~) + mode = get(h.mathDd, 'Value'); + if mode == 1 + sn = getappdata(fig, 'slNamesPTB'); + set(h.mathDesc, 'String', 'P=anchor, ratios adjust I/D'); + else + sn = getappdata(fig, 'slNamesBF'); + set(h.mathDesc, 'String', 'PI Gain scales P+I together'); + end + setappdata(fig, 'slNames', sn); + for s = 1:5 + v = get(h.sl(s), 'Value'); + set(h.slLbl(s), 'String', sprintf('%s: %.2f', sn{s}, v)); + end + recalc(); + end + + function cb_reset(~, ~) + sn = getappdata(fig, 'slNames'); + for s = 1:5 + set(h.sl(s), 'Value', 1.00); + set(h.slLbl(s), 'String', sprintf('%s: 1.00', sn{s})); + end + recalc(); + end + + function cb_sliderMoved(~, ~) + sn = getappdata(fig, 'slNames'); + for s = 1:5 + v = get(h.sl(s), 'Value'); + set(h.slLbl(s), 'String', sprintf('%s: %.2f', sn{s}, v)); + end + recalc(); + end + + function cb_recalc(~, ~), recalc(); end + + function recalc() + P0 = str2double(get(h.startEdit(1), 'String')); + I0 = str2double(get(h.startEdit(2), 'String')); + D0 = str2double(get(h.startEdit(3), 'String')); + if isnan(P0) || isnan(I0) || isnan(D0), return; end + + idx = get(h.fwDd, 'Value'); + pr = getappdata(fig, 'presets'); + ydf = pr{idx, 5}; + yawD0 = D0 * ydf; + + sl1 = get(h.sl(1), 'Value'); % PD Ratio / D Gain + sl2 = get(h.sl(2), 'Value'); % PI Ratio / PI Gain + rpr = get(h.sl(3), 'Value'); % Roll-Pitch Ratio + ywr = get(h.sl(4), 'Value'); % Yaw Ratio + mst = get(h.sl(5), 'Value'); % Master Multiplier + + mode = get(h.mathDd, 'Value'); + + if mode == 1 + % PTB math: P is anchor, PI ratio affects I only, Yaw I excludes PI + rP = P0 * mst; + rI = I0 * sl2 * mst; + rD = D0 * sl1 * mst; + + pP = rP * rpr; + pI = rI * rpr; + pD = rD * rpr; + + yP = rP * ywr; + yI = I0 * ywr * mst; + yD = yawD0 * sl1 * ywr * mst; + else + % BF math: PI Gain scales P AND I together, Yaw I follows PI Gain + rP = P0 * sl2 * mst; + rI = I0 * sl2 * mst; + rD = D0 * sl1 * mst; + + pP = rP * rpr; + pI = rI * rpr; + pD = rD * rpr; + + yP = rP * ywr; + yI = rI * ywr; + yD = yawD0 * sl1 * ywr * mst; + end + + set(h.testLbl(1), 'String', sprintf('Roll PID: %s %s %s', fp(rP), fp(rI), fp(rD))); + set(h.testLbl(2), 'String', sprintf('Pitch PID: %s %s %s', fp(pP), fp(pI), fp(pD))); + set(h.testLbl(3), 'String', sprintf('Yaw PID: %s %s %s', fp(yP), fp(yI), fp(yD))); + end + + function s = fp(v) + if abs(v) < 0.001 + s = sprintf('%.4f', v); + elseif abs(v) < 1 + s = sprintf('%.3f', v); + elseif abs(v - round(v)) < 0.005 + s = sprintf('%.0f', v); + else + s = sprintf('%.2f', v); + end + end +end diff --git a/tests/test_PSsliderToolMath.m b/tests/test_PSsliderToolMath.m new file mode 100644 index 0000000..b3a1d89 --- /dev/null +++ b/tests/test_PSsliderToolMath.m @@ -0,0 +1,100 @@ +% test_PSsliderToolMath.m — verify PTB and BF slider math formulas + +%!function [rP,rI,rD, pP,pI,pD, yP,yI,yD] = ptb_calc(P0,I0,D0,yawD0, pd,pir,rpr,ywr,mst) +%! rP = P0 * mst; +%! rI = I0 * pir * mst; +%! rD = D0 * pd * mst; +%! pP = rP * rpr; +%! pI = rI * rpr; +%! pD = rD * rpr; +%! yP = P0 * mst * ywr; +%! yI = I0 * mst * ywr; +%! yD = yawD0 * pd * mst * ywr; +%!endfunction + +%!function [rP,rI,rD, pP,pI,pD, yP,yI,yD] = bf_calc(P0,I0,D0,yawD0, dg,pig,rpr,ywr,mst) +%! rP = P0 * pig * mst; +%! rI = I0 * pig * mst; +%! rD = D0 * dg * mst; +%! pP = rP * rpr; +%! pI = rI * rpr; +%! pD = rD * rpr; +%! yP = rP * ywr; +%! yI = rI * ywr; +%! yD = yawD0 * dg * mst * ywr; +%!endfunction + +%!test +%! % PTB: PD=0.75, all others default — only D changes +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = ptb_calc(4,0.05,15,2, 0.75,1.00,1.00,1.00,1.00); +%! assert(rP, 4, 0.01); +%! assert(rI, 0.050, 0.001); +%! assert(rD, 11.25, 0.01); +%! assert(pP, 4, 0.01); +%! assert(pI, 0.050, 0.001); +%! assert(pD, 11.25, 0.01); +%! assert(yP, 4, 0.01); +%! assert(yI, 0.050, 0.001); +%! assert(yD, 1.50, 0.01); + +%!test +%! % PTB: PD=1.35, PI=0.90 — Yaw I = 0.050 (PI ratio excluded from Yaw) +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = ptb_calc(4,0.05,15,2, 1.35,0.90,1.00,1.00,1.00); +%! assert(rP, 4, 0.01); +%! assert(rI, 0.045, 0.001); +%! assert(rD, 20.25, 0.01); +%! assert(yI, 0.050, 0.001); +%! assert(yD, 2.70, 0.01); + +%!test +%! % PTB: PD=1.35, PI=1.20, RP=1.40 — Pitch P round(5.6)=6 +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = ptb_calc(4,0.05,15,2, 1.35,1.20,1.40,1.00,1.00); +%! assert(rP, 4, 0.01); +%! assert(rI, 0.060, 0.001); +%! assert(round(pP), 6); +%! assert(pI, 0.084, 0.001); +%! assert(pD, 28.35, 0.01); +%! assert(yI, 0.050, 0.001); + +%!test +%! % PTB: all sliders non-default — P rounding: 5.4→5, 7.02→7, 7.29→7 +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = ptb_calc(4,0.05,15,2, 1.35,1.20,1.30,1.35,1.35); +%! assert(round(rP), 5); +%! assert(rI, 0.081, 0.001); +%! assert(rD, 27.34, 0.02); +%! assert(round(pP), 7); +%! assert(pI, 0.105, 0.002); +%! assert(pD, 35.54, 0.02); +%! assert(round(yP), 7); +%! assert(yI, 0.091, 0.001); +%! assert(yD, 4.92, 0.02); + +%!test +%! % PTB: PD=1.35, PI=1.20, RP=1.30, Yaw=1.05, Master=1.45 +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = ptb_calc(4,0.05,15,2, 1.35,1.20,1.30,1.05,1.45); +%! assert(round(rP), 6); +%! assert(rI, 0.087, 0.001); +%! assert(rD, 29.36, 0.02); +%! assert(round(pP), 8); +%! assert(pI, 0.113, 0.002); +%! assert(pD, 38.17, 0.02); +%! assert(round(yP), 6); +%! assert(yI, 0.076, 0.001); +%! assert(yD, 4.11, 0.02); + +%!test +%! % BF: PI Gain=0.90 scales P AND I together, Yaw I follows PI Gain +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = bf_calc(4,0.05,15,2, 1.35,0.90,1.00,1.00,1.00); +%! assert(rP, 3.6, 0.01); +%! assert(rI, 0.045, 0.001); +%! assert(rD, 20.25, 0.01); +%! assert(yI, 0.045, 0.001); + +%!test +%! % BF: full combination — PD=1.35, PI=1.20, RP=1.30, Yaw=1.05, Master=1.45 +%! [rP,rI,rD,pP,pI,pD,yP,yI,yD] = bf_calc(4,0.05,15,2, 1.35,1.20,1.30,1.05,1.45); +%! assert(rP, 6.96, 0.01); +%! assert(rI, 0.087, 0.001); +%! assert(rD, 29.3625, 0.01); +%! assert(pP, 9.048, 0.01); +%! assert(yI, 0.09135, 0.001); From 2ae95705dd70d30a59c2566f0e53a52f8aa0fa01 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 16:23:15 +0100 Subject: [PATCH 29/80] fix(ui): stick overlay, KISS parsing, firmware switch cleanup --- src/core/PSgetcsv.m | 5 +- src/core/PSimport.m | 22 ++++-- src/core/PSload.m | 98 ++++++++++++++++---------- src/core/PSparseBFversion.m | 23 ++++--- src/plot/PSplotLogViewer.m | 12 ++-- src/ui/PSslider1Actions.m | 133 +++++++++++++++++++----------------- src/ui/PSspec2DUIcontrol.m | 1 + src/ui/PSviewerUIcontrol.m | 91 +++++++++++++++++++++--- src/util/PSresetData.m | 16 +++-- src/util/PSresizeCP.m | 31 +++++++++ src/util/PSstyleControls.m | 3 + 11 files changed, 297 insertions(+), 138 deletions(-) diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index f5b00e9..f34be09 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -36,7 +36,10 @@ decoder_inav = getappdata(0, 'PSdecoderPathINAV'); if firmware_flag == 3 && ~isempty(decoder_inav) - cmd = ['"' decoder_inav '" --output-dir "' outdir '" "' filename '" 2>&1']; + % INAV decoder has no --output-dir; copy input to workdir first + tmpSrc = fullfile(outdir, [fname fext]); + copyfile(filename, tmpSrc); + cmd = ['"' decoder_inav '" "' tmpSrc '" 2>&1']; else cmd = ['"' decoder_path '" --output-dir "' outdir '" "' filename '" 2>&1']; end diff --git a/src/core/PSimport.m b/src/core/PSimport.m index 5b0a42e..5fc88d8 100644 --- a/src/core/PSimport.m +++ b/src/core/PSimport.m @@ -33,13 +33,23 @@ logStartPoints=find(cellfun(@(a)~isempty(a)&&a>0,a)); end -endStr={'throttle_boost_cutoff'; 'debug_mode' ; 'motor_pwm_rate' ; 'use_unsynced_pwm' ; 'gyro_32khz_hardware_lpf' ; 'gyro_hardware_lpf' ; 'yaw_deadband' ; 'deadband' ; 'pidsum_limit_yaw' ; 'pidsum_limit' ; 'energyCumulative (mAh)'; 'looptime'};% strings not in all revisions, try a few -m=1; a=strfind(c{1},endStr{m}); -while isempty(find(cellfun(@(a)~isempty(a)&&a>0,a))) - a=strfind(c{1},endStr{m}); - m=m+1; +endStr={'throttle_boost_cutoff'; 'debug_mode' ; 'motor_pwm_rate' ; 'use_unsynced_pwm' ; 'gyro_32khz_hardware_lpf' ; 'gyro_hardware_lpf' ; 'yaw_deadband' ; 'deadband' ; 'pidsum_limit_yaw' ; 'pidsum_limit' ; 'energyCumulative (mAh)'; 'looptime'}; +logEndPoints = []; +for m = 1:numel(endStr) + a = strfind(c{1}, endStr{m}); + hits = find(cellfun(@(a) ~isempty(a) && a > 0, a)); + if ~isempty(hits), logEndPoints = hits; break; end +end +if isempty(logEndPoints) + % fallback: last "H " line per log section (KISS, etc.) + hLines = find(strncmp(c{1}, 'H ', 2)); + if ~isempty(hLines) + logEndPoints = zeros(size(logStartPoints)); + for k = 1:numel(logStartPoints) + logEndPoints(k) = max(hLines(hLines >= logStartPoints(k))); + end + end end -logEndPoints = find(cellfun(@(a)~isempty(a)&&a>0,a)); relevantLogNum=str2double(csvFname(end-5:end-4)); s=c{1}(logStartPoints(relevantLogNum):logEndPoints(relevantLogNum)); diff --git a/src/core/PSload.m b/src/core/PSload.m index 7080991..504c0a1 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -28,10 +28,17 @@ clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData rpmFilterData ampmat freq2d2 amp2d2 specMat delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02 gyro_phase_shift_deg dterm_phase_shift_deg tuneCrtlpanel_init setupInfoWidgets_init; fcnt = 0; fnameMaster = {}; Nfiles = 0; try, delete(checkpanel); clear checkpanel; catch, end - try, delete(subplot('position',posInfo.linepos1)); catch, end - try, delete(subplot('position',posInfo.linepos2)); catch, end - try, delete(subplot('position',posInfo.linepos3)); catch, end - try, delete(subplot('position',posInfo.linepos4)); catch, end + % Delete tagged plot axes (never use subplot — creates blank axes) + try delete(findobj(PSfig,'Tag','PSrpy')); catch, end + try delete(findobj(PSfig,'Tag','PSmotor')); catch, end + try delete(findobj(PSfig,'Tag','PScombo')); catch, end + % Delete overlay widgets + ov = getappdata(PSfig, 'PSoverlay'); + if ~isempty(ov) + flds = fieldnames(ov); + for fi=1:numel(flds), try delete(ov.(flds{fi})); catch, end; end + setappdata(PSfig, 'PSoverlay', []); + end figs=findobj('Type','figure'); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; set(guiHandles.FileNum, 'String', ' '); @@ -114,9 +121,6 @@ epoch2_A(fcnt)=round(((tta{fcnt}(end)/us2sec)-LogNdDefault)*10) / 10; clear a b r p y dm ff - r = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'rollPID')),2)); - p = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'pitchPID')),2)); - y = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'yawPID')),2)); %%%%%%%%%% parse firmware version for per-file debug mode indices %%%%%%%%%% [fwType{fcnt}, fwMajor(fcnt), fwMinor(fcnt)] = PSparseBFversion(SetupInfo{fcnt}); @@ -126,51 +130,65 @@ try debugmode(fcnt) = str2double(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'debug_mode')),2))); catch - % BF 2025.12+: GYRO_SCALED removed, use GYRO_FILTERED as default if debugIdx{fcnt}.GYRO_SCALED == -1 debugmode(fcnt) = debugIdx{fcnt}.GYRO_FILTERED; else - debugmode(fcnt) = 6;% default to gyro_scaled + debugmode(fcnt) = 6; end end - %%%%%%%%%% parse gyro_debug_axis (BF 2025.12+, for FFT_FREQ axis) %%%%%%%%%% try gyro_debug_axis(fcnt) = str2double(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'gyro_debug_axis')),2))); catch - gyro_debug_axis(fcnt) = 0; % default Roll + gyro_debug_axis(fcnt) = 0; end - dm = {}; - % Try d_max first (BF 2025.12+), fallback to d_min (older) - dm_idx = find(strcmp(SetupInfo{fcnt}(:,1), 'd_max')); - if isempty(dm_idx) - dm_idx = find(strcmp(SetupInfo{fcnt}(:,1), 'd_min')); - end - if ~isempty(dm_idx) && ~isempty(SetupInfo{fcnt}(dm_idx,2)) - dm = SetupInfo{fcnt}(dm_idx, 2); - else - dm = {' , , '}; - end - ff = {}; - if ~isempty(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'feedforward_weight') | strcmp(SetupInfo{fcnt}(:,1), 'ff_weight')),2)) - ff = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'feedforward_weight') | strcmp(SetupInfo{fcnt}(:,1), 'ff_weight')),2)); - else - ff = {' , , '}; + try + r = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'rollPID')),2)); + p = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'pitchPID')),2)); + y = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'yawPID')),2)); + dm_idx = find(strcmp(SetupInfo{fcnt}(:,1), 'd_max')); + if isempty(dm_idx), dm_idx = find(strcmp(SetupInfo{fcnt}(:,1), 'd_min')); end + if ~isempty(dm_idx) && ~isempty(SetupInfo{fcnt}(dm_idx,2)) + dm = SetupInfo{fcnt}(dm_idx, 2); + else + dm = {' , , '}; + end + ff_idx = find(strcmp(SetupInfo{fcnt}(:,1), 'feedforward_weight') | strcmp(SetupInfo{fcnt}(:,1), 'ff_weight')); + if ~isempty(ff_idx) && ~isempty(SetupInfo{fcnt}(ff_idx,2)) + ff = SetupInfo{fcnt}(ff_idx, 2); + else + ff = {' , , '}; + end + a=strfind(char(dm),','); + b=strfind(char(ff),','); + rollPIDF{fcnt} = [char(r) ',' dm{1}(1:a(1)-1) ',' ff{1}(1:b(1)-1)]; + pitchPIDF{fcnt} = [char(p) ',' dm{1}(a(1)+1:a(2)-1) ',' ff{1}(b(1)+1:b(2)-1)]; + yawPIDF{fcnt} = [char(y) ',' dm{1}(a(2)+1:end) ',' ff{1}(b(2)+1:end)]; + catch + rollPIDF{fcnt} = '0,0,0,0,0'; + pitchPIDF{fcnt} = '0,0,0,0,0'; + yawPIDF{fcnt} = '0,0,0,0,0'; end - a=strfind(char(dm),','); - b=strfind(char(ff),','); - rollPIDF{fcnt} = [char(r) ',' dm{1}(1:a(1)-1) ',' ff{1}(1:b(1)-1)]; - pitchPIDF{fcnt} = [char(p) ',' dm{1}(a(1)+1:a(2)-1) ',' ff{1}(b(1)+1:b(2)-1)]; - yawPIDF{fcnt} = [char(y) ',' dm{1}(a(2)+1:end) ',' ff{1}(b(2)+1:end)]; - - if get(guiHandles.Firmware, 'Value') == 3 % INAV + fwSel = get(guiHandles.Firmware, 'Value'); + if fwSel == 3 % INAV T{fcnt}.setpoint_0_ = T{fcnt}.axisRate_0_; T{fcnt}.setpoint_1_ = T{fcnt}.axisRate_1_; T{fcnt}.setpoint_2_ = T{fcnt}.axisRate_2_; T{fcnt}.setpoint_3_ = (T{fcnt}.rcData_3_ - 1000); end + % KISS/FETTEC: synthesize setpoint from rcCommand if missing + if ~isfield(T{fcnt}, 'setpoint_0_') && isfield(T{fcnt}, 'rcCommand_0_') + for ax = 0:2 + T{fcnt}.(['setpoint_' int2str(ax) '_']) = T{fcnt}.(['rcCommand_' int2str(ax) '_']); + end + T{fcnt}.setpoint_3_ = (T{fcnt}.rcCommand_3_ - 1000) / 10; + elseif ~isfield(T{fcnt}, 'setpoint_0_') + for ax = 0:3 + T{fcnt}.(['setpoint_' int2str(ax) '_']) = zeros(length(T{fcnt}.loopIteration), 1); + end + end isArduPilot = strcmpi(sfext, '.bin'); @@ -219,11 +237,19 @@ catch, end end - T{fcnt}.(['piderr_' ks '_']) = T{fcnt}.(['gyroADC_' ks '_']) - T{fcnt}.(['setpoint_' ks '_']); + try + T{fcnt}.(['piderr_' ks '_']) = T{fcnt}.(['gyroADC_' ks '_']) - T{fcnt}.(['setpoint_' ks '_']); + catch + T{fcnt}.(['piderr_' ks '_']) = zeros(Nsamples, 1); + end try T{fcnt}.(['pidsum_' ks '_']) = T{fcnt}.(['axisP_' ks '_']) + T{fcnt}.(['axisI_' ks '_']) + T{fcnt}.(['axisD_' ks '_']) + T{fcnt}.(['axisF_' ks '_']); catch - T{fcnt}.(['pidsum_' ks '_']) = T{fcnt}.(['axisP_' ks '_']) + T{fcnt}.(['axisI_' ks '_']) + T{fcnt}.(['axisF_' ks '_']); + try + T{fcnt}.(['pidsum_' ks '_']) = T{fcnt}.(['axisP_' ks '_']) + T{fcnt}.(['axisI_' ks '_']) + T{fcnt}.(['axisF_' ks '_']); + catch + T{fcnt}.(['pidsum_' ks '_']) = zeros(Nsamples, 1); + end end end end diff --git a/src/core/PSparseBFversion.m b/src/core/PSparseBFversion.m index d29b4a4..393e1f4 100644 --- a/src/core/PSparseBFversion.m +++ b/src/core/PSparseBFversion.m @@ -19,22 +19,23 @@ end verStr = strtrim(char(setupInfo(idx(1), 2))); - % Example: " Betaflight / STM32F405 4.5.3 Dec 14 2024 / 11:27:01" - % Example: " Betaflight / STM32F405 2025.12.2 Jan 5 2026 / 09:15:00" - % Example: " INAV / STM32F405 7.1.0 ..." - % Example: " Emuflight / STM32F405 0.4.1 ..." - % Example: "QuickSilver" (from PSquicJson2csv synthetic header) - % Extract firmware type (first word) - % Old format: " Betaflight / STM32F405 4.5.3 ..." (with /) - % New format: "Betaflight 2025.12.0-beta (hash) STM32F405" (no /) - words = strsplit(strtrim(verStr)); - if ~isempty(words) - fwType = words{1}; + % Check for separate 'Firmware type' header (KISS, etc.) + ftIdx = find(strcmp(setupInfo(:,1), 'Firmware type')); + if ~isempty(ftIdx) + fwType = strtrim(char(setupInfo(ftIdx(1), 2))); + else + words = strsplit(strtrim(verStr)); + if ~isempty(words) + fwType = words{1}; + end end % Extract version: find pattern X.Y.Z or X.Y anywhere in string tok = regexp(verStr, '(\d+)\.(\d+)\.(\d+)', 'tokens'); + if isempty(tok) + tok = regexp(verStr, '(\d+)\.(\d+)', 'tokens'); + end if ~isempty(tok) % Could have multiple matches (e.g. board name with numbers) - use last one % Actually first match after '/' is the version diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 59bc65d..593b758 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -16,7 +16,7 @@ global logviewerYscale logviewerYscale = str2double(get(guiHandles.maxY_input, 'String')); - figure(PSfig); + set(0, 'CurrentFigure', PSfig); maxY=str2double(get(guiHandles.maxY_input, 'String')); @@ -132,12 +132,10 @@ PSfig; try zoomOn = strcmp(get(zoom(PSfig), 'Enable'),'on'); catch, zoomOn = 0; end - if ~zoomOn && ~expandON % - delete(subplot('position' ,fullszPlot)); - delete(subplot('position',posInfo.linepos1)); - delete(subplot('position',posInfo.linepos2)); - delete(subplot('position',posInfo.linepos3)); - delete(subplot('position',posInfo.linepos4)); + if ~zoomOn && ~expandON + try delete(findobj(PSfig,'Tag','PSrpy')); catch, end + try delete(findobj(PSfig,'Tag','PSmotor')); catch, end + try delete(findobj(PSfig,'Tag','PScombo')); catch, end end try delete(hch1); catch, end, try delete(hch2); catch, end diff --git a/src/ui/PSslider1Actions.m b/src/ui/PSslider1Actions.m index 8f78134..b500350 100644 --- a/src/ui/PSslider1Actions.m +++ b/src/ui/PSslider1Actions.m @@ -7,69 +7,78 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- - -try - a1 = axis(LVpanel4); a = [a1(1) a1(2)]; - catch - a = [0 tta{get(guiHandles.FileNum, 'Value')}(end) / us2sec]; - end - adiff = a(2)-a(1); - - x1 = a(1) + (get(guiHandles.slider, 'Value')*adiff) ; - try - delete(hslider1); - delete(hslider2); - delete(hslider3); - delete(hslider5); - catch - end - try - delete(hslider4);, - catch - end - -if ~get(guiHandles.RPYcomboLV, 'Value') - if get(guiHandles.plotR, 'Value'), - LVpanel1=subplot('position',posInfo.linepos1); - hslider1=plot([x1 x1],[-(maxY) maxY],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - end - if get(guiHandles.plotP, 'Value'), LVpanel2=subplot('position',posInfo.linepos2); - hslider2=plot([x1 x1],[-(maxY) maxY],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - end - if get(guiHandles.plotY, 'Value') - LVpanel3=subplot('position',posInfo.linepos3); - hslider3=plot([x1 x1],[-(maxY) maxY],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - end - if get(guiHandles.plotR, 'Value') || get(guiHandles.plotP, 'Value') || get(guiHandles.plotY, 'Value') - LVpanel5=subplot('position',posInfo.linepos4); - hslider5=plot([x1 x1],[0 100],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - axis([0 xmax 0 100]), - grid on - end +th = PStheme(); +fileIdx = get(guiHandles.FileNum, 'Value'); + +% Get time range from visible axes (by Tag, not position) +motorAx = findobj(PSfig, 'Type', 'axes', 'Tag', 'PSmotor'); +comboAx = findobj(PSfig, 'Type', 'axes', 'Tag', 'PScombo'); +try + if ~isempty(comboAx), a = xlim(comboAx(1)); + elseif ~isempty(motorAx), a = xlim(motorAx(1)); + else a = [0 tta{fileIdx}(end) / us2sec]; + end +catch + a = [0 tta{fileIdx}(end) / us2sec]; +end +adiff = a(2) - a(1); +x1 = a(1) + get(guiHandles.slider, 'Value') * adiff; + +% Delete old cursor lines +try delete(hslider1); catch, end +try delete(hslider2); catch, end +try delete(hslider3); catch, end +try delete(hslider4); catch, end +try delete(hslider5); catch, end +hslider1=[]; hslider2=[]; hslider3=[]; hslider4=[]; hslider5=[]; + +lwVal = get(guiHandles.linewidth, 'Value') / 2; + +% Draw cursor lines on TAGGED axes (never subplot — avoids position mismatch) +rpyAxes = findobj(PSfig, 'Type', 'axes', 'Tag', 'PSrpy'); +if ~isempty(comboAx) && ishandle(comboAx(1)) + set(PSfig, 'CurrentAxes', comboAx(1)); hold on; + hslider4 = plot([x1 x1], [-maxY maxY], '-', 'color', th.textPrimary, 'linewidth', lwVal); else - LVpanel4=subplot('position' ,[fullszPlot]); - hslider4=plot([x1 x1],[-(maxY) maxY],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - LVpanel5=subplot('position',posInfo.linepos4); - hslider5=plot([x1 x1],[0 100],'-k','linewidth',get(guiHandles.linewidth, 'Value')/2); - axis([0 xmax 0 100]) - grid on + if numel(rpyAxes) > 1 + yy = zeros(numel(rpyAxes),1); + for k=1:numel(rpyAxes), p=get(rpyAxes(k),'Position'); yy(k)=p(2); end + [~,si] = sort(yy,'descend'); rpyAxes = rpyAxes(si); + end + for k = 1:min(3, numel(rpyAxes)) + set(PSfig, 'CurrentAxes', rpyAxes(k)); hold on; + h = plot([x1 x1], [-maxY maxY], '-', 'color', th.textPrimary, 'linewidth', lwVal); + if k==1, hslider1=h; elseif k==2, hslider2=h; else hslider3=h; end + end +end +if ~isempty(motorAx) && ishandle(motorAx(1)) + set(PSfig, 'CurrentAxes', motorAx(1)); hold on; + hslider5 = plot([x1 x1], [0 100], '-', 'color', th.textPrimary, 'linewidth', lwVal); end -h=subplot('position',[posInfo.YTstick]); -x2=find(tta{get(guiHandles.FileNum, 'Value')}/us2sec>=x1,1); -plot(-T{get(guiHandles.FileNum, 'Value')}.rcCommand_2_(x2) , (T{get(guiHandles.FileNum, 'Value')}.rcCommand_3_(x2) - 1000)/10,'ko'); -set(h, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',['Y'], 'yticklabel',['T'],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on', 'fontweight','bold','FontSize', fontsz); -h=subplot('position',[posInfo.RPstick]); -plot(T{get(guiHandles.FileNum, 'Value')}.rcCommand_0_(x2) , T{get(guiHandles.FileNum, 'Value')}.rcCommand_1_(x2),'ko'); -set(h, 'xlim', [-500 500], 'ylim', [-500 500], 'xticklabel',['R'], 'yticklabel',['P'],'xtick',[0], 'ytick',[0], 'xgrid', 'on', 'ygrid', 'on', 'fontweight','bold','FontSize', fontsz); -subplot('position',[posInfo.YTstick]); h=text(0,110, ['time: ' num2str(tta{(get(guiHandles.FileNum, 'Value'))}(x2) / us2sec) ' sec']); set(h,'FontSize', fontsz); -try h=text(-450,-60, ['M3: ' int2str(T{get(guiHandles.FileNum, 'Value')}.motor_2_(x2)) '%']); set(h,'FontSize', fontsz, 'color', [ColorSet(13,:)]); catch, end -try h=text(-450,-40, ['M4: ' int2str(T{get(guiHandles.FileNum, 'Value')}.motor_3_(x2)) '%']); set(h,'FontSize', fontsz, 'color', [ColorSet(14,:)]); catch, end -subplot('position',[posInfo.RPstick]); -try h=text(-450,-1100, ['M1: ' int2str(T{get(guiHandles.FileNum, 'Value')}.motor_0_(x2)) '%']); set(h,'FontSize', fontsz, 'color', [ColorSet(11,:)]); catch, end -try h=text(-450,-900, ['M2: ' int2str(T{get(guiHandles.FileNum, 'Value')}.motor_1_(x2)) '%']); set(h,'FontSize', fontsz, 'color', [ColorSet(12,:)]); catch, end -subplot('position',[posInfo.YTstick]); -h=text(-450,-80, ['gyro R: ' int2str(T{get(guiHandles.FileNum, 'Value')}.gyroADC_0_(x2)) ' deg/s']); set(h,'FontSize', fontsz, 'color', [ColorSet(2,:)]); -h=text(-450,-100, ['gyro P: ' int2str(T{get(guiHandles.FileNum, 'Value')}.gyroADC_1_(x2)) ' deg/s']); set(h,'FontSize', fontsz, 'color', [ColorSet(2,:)]); -h=text(-450,-120, ['gyro Y: ' int2str(T{get(guiHandles.FileNum, 'Value')}.gyroADC_2_(x2)) ' deg/s']); set(h,'FontSize', fontsz, 'color', [ColorSet(2,:)]); +% Stick overlay — update persistent handles (created by PSviewerUIcontrol) +x2 = find(tta{fileIdx}/us2sec >= x1, 1); +if ~isempty(x2) && isfield(guiHandles, 'stickDotYT') + T_f = T{fileIdx}; + if isfield(T_f, 'rcCommand_0_') + try set(guiHandles.stickDotYT, 'XData', -T_f.rcCommand_2_(x2), ... + 'YData', (T_f.rcCommand_3_(x2)-1000)/10); catch, end + try set(guiHandles.stickDotRP, 'XData', T_f.rcCommand_0_(x2), ... + 'YData', T_f.rcCommand_1_(x2)); catch, end + elseif isfield(T_f, 'setpoint_0_') + try set(guiHandles.stickDotYT, 'XData', -T_f.setpoint_2_(x2), ... + 'YData', T_f.setpoint_3_(x2)); catch, end + try set(guiHandles.stickDotRP, 'XData', T_f.setpoint_0_(x2), ... + 'YData', T_f.setpoint_1_(x2)); catch, end + end + + set(guiHandles.overlayTime, 'String', sprintf('time: %.4f sec', tta{fileIdx}(x2)/us2sec)); + try set(guiHandles.overlayM1, 'String', sprintf('M1: %.0f%%', T_f.motor_0_(x2))); catch, end + try set(guiHandles.overlayM2, 'String', sprintf('M2: %.0f%%', T_f.motor_1_(x2))); catch, end + try set(guiHandles.overlayM3, 'String', sprintf('M3: %.0f%%', T_f.motor_2_(x2))); catch, end + try set(guiHandles.overlayM4, 'String', sprintf('M4: %.0f%%', T_f.motor_3_(x2))); catch, end + try set(guiHandles.overlayGR, 'String', sprintf('gyro R: %.0f deg/s', T_f.gyroADC_0_(x2))); catch, end + try set(guiHandles.overlayGP, 'String', sprintf('gyro P: %.0f deg/s', T_f.gyroADC_1_(x2))); catch, end + try set(guiHandles.overlayGY, 'String', sprintf('gyro Y: %.0f deg/s', T_f.gyroADC_2_(x2))); catch, end +end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 560ee23..2d0ccc3 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -184,6 +184,7 @@ guiHandlesSpec2.climMax2_text = uicontrol(PSspecfig2,'style','text','string','Y max','fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.climMax2_text]); guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2double(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); + end % ishandle(spec2Crtlpanel) % Register CP for fixed-pixel resize diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 17d775b..1941a8a 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -39,13 +39,18 @@ posInfo.nCols_text = [chkX chkRow2 chkTxtW chkH]; posInfo.nCols_input = [chkX+chkTxtW chkRow2 chkEdtW chkH]; -posInfo.YTstick = [cpL+.005 vPos-0.39 .05 .085]; -posInfo.RPstick = [cpL+cpW/2 vPos-0.39 .05 .085]; - % Plot positions — right edge stops at CP left edge plotL = 0.095; plotGap = 0.01; dynCpL = getappdata(PSfig, 'PScpL'); if isempty(dynCpL), dynCpL = cpL; end plotW = dynCpL - plotL - plotGap; + +% Stick overlay — directly below Control Panel +stickGap = cpM; +stickW = (cpW - stickGap) / 2; stickH = stickW * 1.3; +try cpBot = get(controlpanel,'Position'); cpBot = cpBot(2); catch, cpBot = vPos - rs*14 - cpMv; end +stickY = max(0.01, cpBot - stickH - cpMv); +posInfo.YTstick = [cpL stickY stickW stickH]; +posInfo.RPstick = [cpL+stickW+stickGap stickY stickW stickH]; sliderW = dynCpL - 0.0826 - 0.005; posInfo.slider = [0.0826 chkRow2-2*cpMv-0.02 sliderW 0.02]; plotTop = posInfo.slider(2) - 0.005; @@ -149,12 +154,80 @@ end setappdata(PSfig, 'PScheckboxBar', chkBarData); -subplot('position',[posInfo.YTstick]); -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on -subplot('position',[posInfo.RPstick]) -set(gca, 'xlim', [-500 500], 'ylim', [0 100], 'xticklabel',[], 'yticklabel',[],'xtick',[0], 'ytick',[50], 'xgrid', 'on', 'ygrid', 'on'); -box on +% Stick overlay — persistent axes + uicontrol labels +th_ = PStheme(); +bCol = th_.axesFg; +guiHandles.stickAxYT = axes('Parent', PSfig, 'Position', posInfo.YTstick, ... + 'xlim',[-500 500], 'ylim',[0 100], ... + 'xtick',[], 'ytick',[], 'Color',th_.axesBg, ... + 'XColor',th_.axesBg, 'YColor',th_.axesBg, ... + 'TickLength',[0 0], 'Box','off'); +line([0 0],[-500 600],'Parent',guiHandles.stickAxYT,'Color',th_.gridColor,'HitTest','off'); +line([-500 500],[50 50],'Parent',guiHandles.stickAxYT,'Color',th_.gridColor,'HitTest','off'); +line([-500 500 500 -500 -500],[0 0 100 100 0],'Parent',guiHandles.stickAxYT, ... + 'Color',bCol,'LineWidth',1,'HitTest','off'); +text(-470, 90, 'T/Y', 'Parent',guiHandles.stickAxYT, 'Color',th_.textSecondary, ... + 'FontSize',max(6,fontsz-2), 'FontWeight','bold', 'HitTest','off'); +guiHandles.stickDotYT = line(NaN,NaN, 'Parent',guiHandles.stickAxYT, ... + 'Marker','o', 'Color',th_.textPrimary, 'MarkerFaceColor',th_.textPrimary, ... + 'MarkerSize',6, 'LineStyle','none', 'HitTest','off'); + +guiHandles.stickAxRP = axes('Parent', PSfig, 'Position', posInfo.RPstick, ... + 'xlim',[-500 500], 'ylim',[-500 500], ... + 'xtick',[], 'ytick',[], 'Color',th_.axesBg, ... + 'XColor',th_.axesBg, 'YColor',th_.axesBg, ... + 'TickLength',[0 0], 'Box','off'); +line([0 0],[-500 500],'Parent',guiHandles.stickAxRP,'Color',th_.gridColor,'HitTest','off'); +line([-500 500],[0 0],'Parent',guiHandles.stickAxRP,'Color',th_.gridColor,'HitTest','off'); +line([-500 500 500 -500 -500],[-500 -500 500 500 -500],'Parent',guiHandles.stickAxRP, ... + 'Color',bCol,'LineWidth',1,'HitTest','off'); +text(-470, 420, 'R/P', 'Parent',guiHandles.stickAxRP, 'Color',th_.textSecondary, ... + 'FontSize',max(6,fontsz-2), 'FontWeight','bold', 'HitTest','off'); +guiHandles.stickDotRP = line(NaN,NaN, 'Parent',guiHandles.stickAxRP, ... + 'Marker','o', 'Color',th_.textPrimary, 'MarkerFaceColor',th_.textPrimary, ... + 'MarkerSize',6, 'LineStyle','none', 'HitTest','off'); + +% Text labels below sticks (uicontrol — exact pixel positioning, no overlap) +oFsz = max(6, fontsz-1); +oH = rhs; halfW = cpW/2; fullW = cpW; +oY = stickY - oH - 2*cpMv; +oBg = th_.figBg; +guiHandles.overlayTime = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',th_.textPrimary,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY fullW oH]); +oY = oY - oH; +guiHandles.overlayM4 = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col13,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY halfW oH]); +guiHandles.overlayM1 = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col10,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL+halfW oY halfW oH]); +oY = oY - oH; +guiHandles.overlayM3 = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col12,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY halfW oH]); +guiHandles.overlayM2 = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col11,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL+halfW oY halfW oH]); +oY = oY - oH; +guiHandles.overlayGR = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY fullW oH]); +oY = oY - oH; +guiHandles.overlayGP = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY fullW oH]); +oY = oY - oH; +guiHandles.overlayGY = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... + 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'units','normalized','Position',[cpL oY fullW oH]); +% Store overlay handles for PSresizeCP +setappdata(PSfig, 'PSoverlay', struct( ... + 'axYT',guiHandles.stickAxYT, 'axRP',guiHandles.stickAxRP, ... + 'time',guiHandles.overlayTime, ... + 'M4',guiHandles.overlayM4, 'M1',guiHandles.overlayM1, ... + 'M3',guiHandles.overlayM3, 'M2',guiHandles.overlayM2, ... + 'GR',guiHandles.overlayGR, 'GP',guiHandles.overlayGP, 'GY',guiHandles.overlayGY)); end % ishandle(checkpanel) fileIdx = get(guiHandles.FileNum, 'Value'); diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m index 52adec3..ec01f96 100644 --- a/src/util/PSresetData.m +++ b/src/util/PSresetData.m @@ -13,12 +13,16 @@ try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end try, delete(checkpanel); clear checkpanel; catch, end -try - delete(subplot('position', posInfo.linepos1)); - delete(subplot('position', posInfo.linepos2)); - delete(subplot('position', posInfo.linepos3)); - delete(subplot('position', posInfo.linepos4)); -catch, end +try delete(findobj(PSfig,'Tag','PSrpy')); catch, end +try delete(findobj(PSfig,'Tag','PSmotor')); catch, end +try delete(findobj(PSfig,'Tag','PScombo')); catch, end +% Delete overlay widgets +ov = getappdata(PSfig, 'PSoverlay'); +if ~isempty(ov) + flds = fieldnames(ov); + for fi=1:numel(flds), try delete(ov.(flds{fi})); catch, end; end + setappdata(PSfig, 'PSoverlay', []); +end % close all secondary figures figs = findobj('Type', 'figure'); diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m index 7d72d03..2b58337 100644 --- a/src/util/PSresizeCP.m +++ b/src/util/PSresizeCP.m @@ -158,6 +158,37 @@ function PSresizeCP(fig, ~) end end +% Reposition stick overlay below CP panel +ov = getappdata(fig, 'PSoverlay'); +if ~isempty(ov) + stickGap = cpM; + stickW = (cpW - stickGap) / 2; stickH = stickW * 1.3; + cpPanel = findobj(fig, 'Type', 'uipanel', 'Title', 'Control Panel'); + if ~isempty(cpPanel) + pp = get(cpPanel(1), 'Position'); cpBot = pp(2); + else + cpBot = vPos - rs*14 - cpMv; + end + stickY = max(0.01, cpBot - stickH - cpMv); + oH = rhs; + try set(ov.axYT, 'Position', [cpL stickY stickW stickH]); catch, end + try set(ov.axRP, 'Position', [cpL+stickW+stickGap stickY stickW stickH]); catch, end + oY = max(0.001, stickY - oH - 2*cpMv); halfW = cpW/2; fullW = cpW; + try set(ov.time, 'Position', [cpL oY fullW oH]); catch, end + oY = oY - oH; + try set(ov.M4, 'Position', [cpL oY halfW oH]); catch, end + try set(ov.M1, 'Position', [cpL+halfW oY halfW oH]); catch, end + oY = oY - oH; + try set(ov.M3, 'Position', [cpL oY halfW oH]); catch, end + try set(ov.M2, 'Position', [cpL+halfW oY halfW oH]); catch, end + oY = oY - oH; + try set(ov.GR, 'Position', [cpL oY fullW oH]); catch, end + oY = oY - oH; + try set(ov.GP, 'Position', [cpL oY fullW oH]); catch, end + oY = oY - oH; + try set(ov.GY, 'Position', [cpL oY fullW oH]); catch, end +end + % Reposition Log Viewer checkbox bar (fixed pixel sizes) chkBar = getappdata(fig, 'PScheckboxBar'); if ~isempty(chkBar) diff --git a/src/util/PSstyleControls.m b/src/util/PSstyleControls.m index e108212..d59695a 100644 --- a/src/util/PSstyleControls.m +++ b/src/util/PSstyleControls.m @@ -8,6 +8,9 @@ function PSstyleControls(fig, th) set(controls(i), 'BackgroundColor', th.btnBg); continue; end + if strcmp(style, 'checkbox') || strcmp(style, 'radiobutton') + try set(controls(i), 'BackgroundColor', th.figBg); catch, end + end fg = get(controls(i), 'ForegroundColor'); % fix controls still at default black [0 0 0] if all(abs(fg) < 0.01) From 10edfa955f7c069e34855a1c68f0faabf68a0af3 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 16:58:31 +0100 Subject: [PATCH 30/80] fix(ui): preset header no longer resets dropdowns, colorbar orphan cleanup --- src/plot/PSfreqTime.m | 1 + src/plot/PSplotSpec.m | 7 +------ src/util/PSapplySpecPreset.m | 27 ++++++++++++--------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 6369171..7ef48d8 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -83,6 +83,7 @@ end end +try delete(findobj(PSspecfig3, 'Tag', 'PScbar')); catch, end for i = 1 : 3 delete(subplot('position',posInfo.Spec3Pos(i,:))); try diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index c95af49..f89d257 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -125,12 +125,7 @@ close(hw) end else - hwarn=warndlg({'Dropdowns set to ''NONE''.'; 'Please select a preset or specific variables to analyze.'}); - pause(3); - try - close(hwarn); - catch - end + warndlg({'Dropdowns set to ''NONE''.'; 'Please select a preset or specific variables to analyze.'}); end if get(guiHandlesSpec.checkbox2d, 'Value')==0 && ~isempty(ampmat) diff --git a/src/util/PSapplySpecPreset.m b/src/util/PSapplySpecPreset.m index fe9115c..8063bd0 100644 --- a/src/util/PSapplySpecPreset.m +++ b/src/util/PSapplySpecPreset.m @@ -1,20 +1,17 @@ function PSapplySpecPreset(pv, guiHandlesSpec) % Apply Freq x Throttle preset to SpecSelect and Sub100Hz checkboxes -if pv < 2 - vals = [1 1 1 1]; sub = [0 0 0 0]; -else - switch pv - case 2, vals = [3 2 8 7]; sub = [0 0 0 0]; - case 3, vals = [3 2 6 7]; sub = [0 0 0 0]; - case 4, vals = [2 7 5 4]; sub = [0 0 1 1]; - case 5, vals = [3 2 3 2]; sub = [0 0 0 0]; - case 6, vals = [8 7 8 7]; sub = [0 0 0 0]; - case 7, vals = [3 3 3 3]; sub = [0 0 0 0]; - case 8, vals = [2 2 2 2]; sub = [0 0 0 0]; - case 9, vals = [7 7 7 7]; sub = [0 0 0 0]; - case 10, vals = [4 4 4 4]; sub = [0 0 0 0]; - otherwise, return; - end +if pv < 2, return; end +switch pv + case 2, vals = [3 2 8 7]; sub = [0 0 0 0]; + case 3, vals = [3 2 6 7]; sub = [0 0 0 0]; + case 4, vals = [2 7 5 4]; sub = [0 0 1 1]; + case 5, vals = [3 2 3 2]; sub = [0 0 0 0]; + case 6, vals = [8 7 8 7]; sub = [0 0 0 0]; + case 7, vals = [3 3 3 3]; sub = [0 0 0 0]; + case 8, vals = [2 2 2 2]; sub = [0 0 0 0]; + case 9, vals = [7 7 7 7]; sub = [0 0 0 0]; + case 10, vals = [4 4 4 4]; sub = [0 0 0 0]; + otherwise, return; end for k = 1:4 set(guiHandlesSpec.SpecSelect{k}, 'Value', vals(k)); From f0b6bc82d5804124648e157094a6accf4026f26b Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 17:36:27 +0100 Subject: [PATCH 31/80] fix(ui): clear smoothCacheLV on firmware switch, defensive length check --- src/core/PSload.m | 1 + src/util/PSsmoothLV.m | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index 504c0a1..62fb3ac 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -43,6 +43,7 @@ clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; set(guiHandles.FileNum, 'String', ' '); try, set(guiHandles.Epoch1_A_Input, 'String', ' '); set(guiHandles.Epoch2_A_Input, 'String', ' '); catch, end + try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end else return; end diff --git a/src/util/PSsmoothLV.m b/src/util/PSsmoothLV.m index f456995..73a7b25 100644 --- a/src/util/PSsmoothLV.m +++ b/src/util/PSsmoothLV.m @@ -12,7 +12,8 @@ cacheKey = [fieldName '_s' int2str(sFactor)]; if isfield(sc, cacheKey) y = sc.(cacheKey); - return; + if numel(y) == numel(Tfile.(fieldName)), return; end + sc = struct('fIdx', fileIdx); % stale cache — different data length end raw = Tfile.(fieldName); From 09fca6fdf8c96b861baafdac87280dcc139c89a5 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 17:47:28 +0100 Subject: [PATCH 32/80] fix(ui): unify motor colors to FPV standard from PStheme --- src/plot/PSplotMotorNoise.m | 2 +- src/plot/PSplotRPMOverlay.m | 3 ++- src/ui/PSfreqTimeUIcontrol.m | 2 +- src/ui/PSspecUIcontrol.m | 2 +- src/util/PStheme.m | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index 800ad87..87b28cd 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -18,7 +18,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) F_kHz = Fs / 1000; motorCol = {'motor_0_', 'motor_1_', 'motor_2_', 'motor_3_'}; motorLbl = {'Motor 1', 'Motor 2', 'Motor 3', 'Motor 4'}; -mCol = {[0 .85 .3], [.85 .85 0], [.85 .2 .2], [.3 .5 1]}; +mCol = th.sigMotor; gyroAxLbl = {'Roll', 'Pitch', 'Yaw'}; gyroCol = {'gyroADC_0_', 'gyroADC_1_', 'gyroADC_2_'}; diff --git a/src/plot/PSplotRPMOverlay.m b/src/plot/PSplotRPMOverlay.m index 19fbd1a..85d5586 100644 --- a/src/plot/PSplotRPMOverlay.m +++ b/src/plot/PSplotRPMOverlay.m @@ -20,7 +20,8 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic end hold(ax, 'on'); -motorCol = [.9 0 0; .9 .6 0; 0 .8 .8; 0 .9 0]; % M1=red M2=orange M3=cyan M4=green +th = PStheme(); +motorCol = cell2mat(th.sigMotor(:)); lineStyles = {'-'; '--'; ':'}; hz_per_pixel = freqMax / imgHeight; diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 491af63..52c2f3c 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -138,7 +138,7 @@ 'units','normalized','Position',[posInfo.sub100HzfreqTime],'callback','@selection2;updateSpec=1; PSfreqTime;'); % RPM overlay motor checkboxes -motorCols = {[.9 0 0], [.9 .6 0], [0 .8 .8], [0 .9 0]}; +motorCols = PStheme().sigMotor; motorNames = {'M1','M2','M3','M4'}; rpmCb = 'updateSpec=1; PSfreqTime;'; for mi = 1:4 diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index bc0a0f0..c5e53a8 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -178,7 +178,7 @@ 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec;'); set(guiHandlesSpec.checkboxPSD, 'Value', 0); -motorCols = {[.9 0 0], [.9 .6 0], [0 .8 .8], [0 .9 0]}; +motorCols = PStheme().sigMotor; motorNames = {'M1','M2','M3','M4'}; rpmCb = 'updateSpec=1;PSplotSpec;'; for mi = 1:4 diff --git a/src/util/PStheme.m b/src/util/PStheme.m index bdb40ac..8b4a525 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -65,7 +65,7 @@ th.sigSetpoint = [.90 .25 .35]; th.sigPIDsum = [1.0 .35 .90]; th.sigPIDerr = [.55 .20 .95]; -th.sigMotor = {[.20 .85 .30], [.85 .70 .15], [.20 .40 .95], [.30 .95 .85]}; +th.sigMotor = {[.95 .20 .20], [.95 .65 .10], [.20 .85 .25], [.20 .45 .95]}; th.sigThrottle = [.85 .85 .85]; end From 8fb359688e6788023adac305f40be32a8d2ee16a Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 19:57:39 +0100 Subject: [PATCH 33/80] fix(firmware): Step Response crash for RF/KISS/AP + Stats crash for ArduPilot --- src/plot/PSplotStats.m | 34 ++++++++++++++++++++++------------ src/plot/PStuningParams.m | 6 ++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index 90ab8ac..be36990 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -44,8 +44,11 @@ % File 1 if ~updateStats for q = 1:3 - rcRaw = T{fA}.(rcFields{q})(tIND{fA}); - Rpct_A{q} = PSPercent(rcRaw); + if isfield(T{fA}, rcFields{q}) + Rpct_A{q} = PSPercent(T{fA}.(rcFields{q})(tIND{fA})); + else + Rpct_A{q} = []; + end end Tpct_A = T{fA}.setpoint_3_(tIND{fA}) / 10; end @@ -58,10 +61,12 @@ else pctData = Tpct_A; end - [nn, xx] = hist(pctData, 0:1:100); - nn = nn / sum(nn); - hb = bar(xx, nn, 1); - set(hb, 'FaceColor', colorA, 'EdgeColor', colorA); + if ~isempty(pctData) + [nn, xx] = hist(pctData, 0:1:100); + nn = nn / sum(nn); + hb = bar(xx, nn, 1); + set(hb, 'FaceColor', colorA, 'EdgeColor', colorA); + end y = xlabel(axLbl{sp}, 'fontweight', 'bold'); set(y, 'Units', 'normalized', 'position', [.5 -.1 1], 'color', th.textPrimary); ylabel('% of flight', 'fontweight', 'bold'); @@ -77,8 +82,11 @@ if ~isempty(fB) if ~updateStats for q = 1:3 - rcRaw = T{fB}.(rcFields{q})(tIND{fB}); - Rpct_B{q} = PSPercent(rcRaw); + if isfield(T{fB}, rcFields{q}) + Rpct_B{q} = PSPercent(T{fB}.(rcFields{q})(tIND{fB})); + else + Rpct_B{q} = []; + end end Tpct_B = T{fB}.setpoint_3_(tIND{fB}) / 10; end @@ -91,10 +99,12 @@ else pctData = Tpct_B; end - [nn, xx] = hist(pctData, 0:1:100); - nn = nn / sum(nn); - hb = bar(xx, nn, 1); - set(hb, 'FaceColor', colorB, 'EdgeColor', colorB); + if ~isempty(pctData) + [nn, xx] = hist(pctData, 0:1:100); + nn = nn / sum(nn); + hb = bar(xx, nn, 1); + set(hb, 'FaceColor', colorB, 'EdgeColor', colorB); + end y = xlabel(axLbl{sp}, 'fontweight', 'bold'); set(y, 'Units', 'normalized', 'position', [.5 -.1 1], 'color', th.textPrimary); ylabel('% of flight', 'fontweight', 'bold'); diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index e45b236..1be323d 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -19,8 +19,10 @@ axesOptions = find([get(guiHandlesTune.plotR, 'Value') get(guiHandlesTune.plotP, 'Value') get(guiHandlesTune.plotY, 'Value')]); lineStyle = {'-' ; '--' ; ':'}; lnLabels = {'solid' ; 'dashed'; 'dotted'}; -p = {' P, I, D, Dm, F'; ' P, I, D, Dm, F'; ' P, I, D, cD'}; -pidlabels = p{get(guiHandles.Firmware, 'Value')}; +p = {' P, I, D, Dm, F'; ' P, I, D, Dm, F'; ' P, I, D, cD'; ... + ' P, I, D'; ' P, I, D'; ' P, I, D, F'; ' P, I, D'; ' P, I, D'}; +fwIdx = min(get(guiHandles.Firmware, 'Value'), numel(p)); +pidlabels = p{fwIdx}; %%%%%%%%%%%%% step resp %%%%%%%%%%%%% figure(PStunefig) From 9b37c14395adb008f101d3c4de073e1e63cc6ff0 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 20:03:26 +0100 Subject: [PATCH 34/80] =?UTF-8?q?fix(ui):=20Rotorflight=20stick=20overlay?= =?UTF-8?q?=20=E2=80=94=20collective=20is=20-500..500=20not=201000..2000?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/PSslider1Actions.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ui/PSslider1Actions.m b/src/ui/PSslider1Actions.m index b500350..5f26ae7 100644 --- a/src/ui/PSslider1Actions.m +++ b/src/ui/PSslider1Actions.m @@ -62,8 +62,14 @@ T_f = T{fileIdx}; if isfield(T_f, 'rcCommand_0_') + fwSel = get(guiHandles.Firmware, 'Value'); + if fwSel == 6 % Rotorflight: collective is -500..500 + thrPct = (T_f.rcCommand_3_(x2) + 500) / 10; + else + thrPct = (T_f.rcCommand_3_(x2) - 1000) / 10; + end try set(guiHandles.stickDotYT, 'XData', -T_f.rcCommand_2_(x2), ... - 'YData', (T_f.rcCommand_3_(x2)-1000)/10); catch, end + 'YData', thrPct); catch, end try set(guiHandles.stickDotRP, 'XData', T_f.rcCommand_0_(x2), ... 'YData', T_f.rcCommand_1_(x2)); catch, end elseif isfield(T_f, 'setpoint_0_') From 43eac43fccacbd76d102092d204abef8a79516db Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 21:35:42 +0100 Subject: [PATCH 35/80] fix(rotorflight): override setpoint_3_ with governor throttle (rcCommand[4]) --- src/core/PSload.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/PSload.m b/src/core/PSload.m index 62fb3ac..16fa3b9 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -179,6 +179,11 @@ T{fcnt}.setpoint_2_ = T{fcnt}.axisRate_2_; T{fcnt}.setpoint_3_ = (T{fcnt}.rcData_3_ - 1000); end + if fwSel == 6 % Rotorflight: setpoint_3_ is collective, use rcCommand[4] as throttle + if isfield(T{fcnt}, 'rcCommand_4_') + T{fcnt}.setpoint_3_ = T{fcnt}.rcCommand_4_; + end + end % KISS/FETTEC: synthesize setpoint from rcCommand if missing if ~isfield(T{fcnt}, 'setpoint_0_') && isfield(T{fcnt}, 'rcCommand_0_') for ax = 0:2 From 88d22ca0a4d5bfb4b19341814300fab2e56e3125 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 12 Mar 2026 21:43:39 +0100 Subject: [PATCH 36/80] feat(rotorflight): show servo data in Motor slots + dynamic labels based on motor count --- src/core/PSload.m | 18 ++++++++++++++++++ src/ui/PSslider1Actions.m | 20 +++++++++++++++++--- src/ui/PSviewerUIcontrol.m | 14 +++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index 16fa3b9..55a5e97 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -229,6 +229,23 @@ end end end % ~isArduPilot + % Rotorflight: fill empty motor slots with servo data + if fwSel == 6 && k == 3 + nMotReal = 0; + for mm = 0:3 + if isfield(T{fcnt}, ['motor_' int2str(mm) '_']), nMotReal = mm+1; end + end + si = 0; + for mm = nMotReal:3 + sf = ['servo_' int2str(si) '_']; + mf = ['motor_' int2str(mm) '_']; + if isfield(T{fcnt}, sf) + T{fcnt}.(mf) = (T{fcnt}.(sf) - 1000) / 10; + end + si = si + 1; + end + try setappdata(PSfig, 'rfMotorCount', nMotReal); catch, end + end if k < 3 ks = int2str(k); if k < 2 % compute prefiltered dterm @@ -266,6 +283,7 @@ end try close(waitbarFid), catch, end + catch ME try if ispc(), system(['rmdir /s /q "' workdir '"']); else system(['rm -rf ' workdir]); end; catch, end try close(waitbarFid); catch, end diff --git a/src/ui/PSslider1Actions.m b/src/ui/PSslider1Actions.m index 5f26ae7..5c7fc1c 100644 --- a/src/ui/PSslider1Actions.m +++ b/src/ui/PSslider1Actions.m @@ -81,9 +81,23 @@ try set(guiHandles.stickDotRP, 'XData', T_f.setpoint_0_(x2), ... set(guiHandles.overlayTime, 'String', sprintf('time: %.4f sec', tta{fileIdx}(x2)/us2sec)); try set(guiHandles.overlayM1, 'String', sprintf('M1: %.0f%%', T_f.motor_0_(x2))); catch, end - try set(guiHandles.overlayM2, 'String', sprintf('M2: %.0f%%', T_f.motor_1_(x2))); catch, end - try set(guiHandles.overlayM3, 'String', sprintf('M3: %.0f%%', T_f.motor_2_(x2))); catch, end - try set(guiHandles.overlayM4, 'String', sprintf('M4: %.0f%%', T_f.motor_3_(x2))); catch, end + rfMot = getappdata(PSfig, 'rfMotorCount'); + if ~isempty(rfMot) + si = 1; + if rfMot >= 2 + try set(guiHandles.overlayM2, 'String', sprintf('M2: %.0f%%', T_f.motor_1_(x2))); catch, end + else + try set(guiHandles.overlayM2, 'String', sprintf('S%d: %.0f%%', si, T_f.motor_1_(x2))); catch, end + si = si+1; + end + try set(guiHandles.overlayM3, 'String', sprintf('S%d: %.0f%%', si, T_f.motor_2_(x2))); catch, end + si = si+1; + try set(guiHandles.overlayM4, 'String', sprintf('S%d: %.0f%%', si, T_f.motor_3_(x2))); catch, end + else + try set(guiHandles.overlayM2, 'String', sprintf('M2: %.0f%%', T_f.motor_1_(x2))); catch, end + try set(guiHandles.overlayM3, 'String', sprintf('M3: %.0f%%', T_f.motor_2_(x2))); catch, end + try set(guiHandles.overlayM4, 'String', sprintf('M4: %.0f%%', T_f.motor_3_(x2))); catch, end + end try set(guiHandles.overlayGR, 'String', sprintf('gyro R: %.0f deg/s', T_f.gyroADC_0_(x2))); catch, end try set(guiHandles.overlayGP, 'String', sprintf('gyro P: %.0f deg/s', T_f.gyroADC_1_(x2))); catch, end try set(guiHandles.overlayGY, 'String', sprintf('gyro Y: %.0f deg/s', T_f.gyroADC_2_(x2))); catch, end diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 1941a8a..0333a3e 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -94,11 +94,19 @@ 'units','normalized','Position',[posInfo.checkbox9],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.checkbox10=uicontrol(PSfig,'Style','checkbox','String','Motor 1','fontsize',fontsz,'ForegroundColor',[linec.col10],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox10],'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSplotLogViewer; end'); -guiHandles.checkbox11=uicontrol(PSfig,'Style','checkbox','String','Motor 2','fontsize',fontsz,'ForegroundColor',[linec.col11],'BackgroundColor',bgcolor,... +m2lbl='Motor 2'; m3lbl='Motor 3'; m4lbl='Motor 4'; +rfMot = getappdata(PSfig, 'rfMotorCount'); +if ~isempty(rfMot) + si = 1; + if rfMot < 2, m2lbl = ['Servo ' int2str(si)]; si=si+1; end + if rfMot < 3, m3lbl = ['Servo ' int2str(si)]; si=si+1; end + if rfMot < 4, m4lbl = ['Servo ' int2str(si)]; end +end +guiHandles.checkbox11=uicontrol(PSfig,'Style','checkbox','String',m2lbl,'fontsize',fontsz,'ForegroundColor',[linec.col11],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox11],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox12=uicontrol(PSfig,'Style','checkbox','String','Motor 3','fontsize',fontsz,'ForegroundColor',[linec.col12],'BackgroundColor',bgcolor,... +guiHandles.checkbox12=uicontrol(PSfig,'Style','checkbox','String',m3lbl,'fontsize',fontsz,'ForegroundColor',[linec.col12],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox12],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); -guiHandles.checkbox13=uicontrol(PSfig,'Style','checkbox','String','Motor 4','fontsize',fontsz,'ForegroundColor',[linec.col13],'BackgroundColor',bgcolor,... +guiHandles.checkbox13=uicontrol(PSfig,'Style','checkbox','String',m4lbl,'fontsize',fontsz,'ForegroundColor',[linec.col13],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox13],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.checkbox14=uicontrol(PSfig,'Style','checkbox','String','Throttle','fontsize',fontsz,'ForegroundColor',[linec.col14],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox14],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); From c2ce675860afea1e9ec14a51471ab85d52990f45 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 13 Mar 2026 10:14:35 +0100 Subject: [PATCH 37/80] fix(firmware): auto-detect firmware from log headers instead of dropdown --- PIDscope.m | 4 ++-- src/core/PSload.m | 24 +++++++++++++++++++----- src/ui/PSslider1Actions.m | 4 ++-- src/util/PSresetData.m | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index eefaaed..5d04c4f 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -306,14 +306,14 @@ 'end']); set(guiHandles.PIDsliderButton, 'ForegroundColor', [.40 .80 1.0]); -guiHandles.filterSimButton = uicontrol(PSfig,'string','Filter Sim','fontsize',fontsz,... +guiHandles.filterSimButton = uicontrol(PSfig,'string','Filter Simulator','fontsize',fontsz,... 'TooltipString','Simulate BF filter chain (theoretical response)','units','normalized',... 'Position',[posInfo.filterSimButton],... 'callback',['try,' ... 'tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... 'PSfilterSim([],1000*A_lograte(tmpFcnt),SetupInfo{tmpFcnt});' ... 'clear tmpFcnt;' ... - 'catch e,warndlg([''Filter Sim: '' e.message]),end']); + 'catch e,warndlg([''Filter Simulator: '' e.message]),end']); set(guiHandles.filterSimButton, 'ForegroundColor', [.85 .55 .15]); guiHandles.PIDErrorButton = uicontrol(PSfig,'string','PID Error','fontsize',fontsz,'TooltipString', ['PID error distribution analysis'],'units','normalized','Position',[posInfo.PIDErrorButton],... diff --git a/src/core/PSload.m b/src/core/PSload.m index 55a5e97..60f4c18 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -51,6 +51,7 @@ loaded_firmware = current_fw; logfile_directory=filepathA; + try setappdata(PSfig, 'rfMotorCount', []); catch, end us2sec=1000000; maxMotorOutput=2000; @@ -127,6 +128,19 @@ [fwType{fcnt}, fwMajor(fcnt), fwMinor(fcnt)] = PSparseBFversion(SetupInfo{fcnt}); debugIdx{fcnt} = PSdebugModeIndices(fwType{fcnt}, fwMajor(fcnt), fwMinor(fcnt)); + % Auto-switch firmware dropdown if detected type differs + detFwIdx = 0; + ft = fwType{fcnt}; + if strcmpi(ft,'Betaflight') || strcmpi(ft,'Cleanflight'), detFwIdx = 1; + elseif strcmpi(ft,'Emuflight'), detFwIdx = 2; + elseif strcmpi(ft,'INAV'), detFwIdx = 3; + elseif strcmpi(ft,'Rotorflight'), detFwIdx = 6; + elseif strcmpi(ft,'KISS'), detFwIdx = 7; + end + if detFwIdx > 0 && detFwIdx ~= get(guiHandles.Firmware, 'Value') + set(guiHandles.Firmware, 'Value', detFwIdx); + end + %%%%%%%%%% collect debug mode info %%%%%%%%%% try debugmode(fcnt) = str2double(char(SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'debug_mode')),2))); @@ -172,14 +186,14 @@ yawPIDF{fcnt} = '0,0,0,0,0'; end - fwSel = get(guiHandles.Firmware, 'Value'); - if fwSel == 3 % INAV + isRF = strcmpi(fwType{fcnt}, 'Rotorflight'); + if strcmpi(fwType{fcnt}, 'INAV') T{fcnt}.setpoint_0_ = T{fcnt}.axisRate_0_; T{fcnt}.setpoint_1_ = T{fcnt}.axisRate_1_; T{fcnt}.setpoint_2_ = T{fcnt}.axisRate_2_; T{fcnt}.setpoint_3_ = (T{fcnt}.rcData_3_ - 1000); end - if fwSel == 6 % Rotorflight: setpoint_3_ is collective, use rcCommand[4] as throttle + if isRF % setpoint_3_ is collective, use rcCommand[4] as throttle if isfield(T{fcnt}, 'rcCommand_4_') T{fcnt}.setpoint_3_ = T{fcnt}.rcCommand_4_; end @@ -199,7 +213,7 @@ isArduPilot = strcmpi(sfext, '.bin'); Nsamples = length(T{fcnt}.loopIteration); - isINAV = (get(guiHandles.Firmware, 'Value') == 3); + isINAV = strcmpi(fwType{fcnt}, 'INAV'); for k = 0 : 3 if ~isArduPilot dbg_f = ['debug_' int2str(k) '_']; @@ -230,7 +244,7 @@ end end % ~isArduPilot % Rotorflight: fill empty motor slots with servo data - if fwSel == 6 && k == 3 + if isRF && k == 3 nMotReal = 0; for mm = 0:3 if isfield(T{fcnt}, ['motor_' int2str(mm) '_']), nMotReal = mm+1; end diff --git a/src/ui/PSslider1Actions.m b/src/ui/PSslider1Actions.m index 5c7fc1c..e58d1fb 100644 --- a/src/ui/PSslider1Actions.m +++ b/src/ui/PSslider1Actions.m @@ -62,8 +62,8 @@ T_f = T{fileIdx}; if isfield(T_f, 'rcCommand_0_') - fwSel = get(guiHandles.Firmware, 'Value'); - if fwSel == 6 % Rotorflight: collective is -500..500 + rfMot = getappdata(PSfig, 'rfMotorCount'); + if ~isempty(rfMot) % Rotorflight: collective is -500..500 thrPct = (T_f.rcCommand_3_(x2) + 500) / 10; else thrPct = (T_f.rcCommand_3_(x2) - 1000) / 10; diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m index ec01f96..5e512ce 100644 --- a/src/util/PSresetData.m +++ b/src/util/PSresetData.m @@ -11,6 +11,7 @@ fcnt = 0; filenameA = {}; fnameMaster = {}; Nfiles = 0; expandON = 0; try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end +try setappdata(PSfig, 'rfMotorCount', []); catch, end try, delete(checkpanel); clear checkpanel; catch, end try delete(findobj(PSfig,'Tag','PSrpy')); catch, end From 7be3151483eb353cb7472eee3546cbc09d8770e6 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 14 Mar 2026 19:29:48 +0100 Subject: [PATCH 38/80] fix(compat): replace subplot() with tag-based axes + CI/build/macOS updates --- .github/workflows/ci.yml | 26 ++++++ .github/workflows/release.yml | 106 ++++++++++++++++------ Makefile | 120 ++++++++++++------------- packaging/macos/Dockerfile | 51 +++-------- packaging/macos/build-macos.sh | 38 ++++---- packaging/macos/fix-quarantine.command | 4 +- packaging/macos/pidscope.command | 25 ++++-- src/core/PSgetcsv.m | 5 ++ src/core/PSload.m | 42 +++++++++ src/plot/PSfreqTime.m | 13 +-- src/plot/PSplotDynNotchOverlay.m | 14 ++- src/plot/PSplotLogViewer.m | 17 ++-- src/plot/PSplotPIDerror.m | 16 ++-- src/plot/PSplotSpec.m | 15 ++-- src/plot/PSplotSpec2D.m | 27 +++--- src/plot/PSplotStats.m | 18 ++-- src/plot/PStuningParams.m | 41 ++++++--- src/ui/PSspec2DUIcontrol.m | 6 +- src/ui/PStuneUIcontrol.m | 14 +-- tests/run_tests.m | 1 + tests/test_PSfilterSim.m | 117 ++++++++++++++++++++++++ 21 files changed, 500 insertions(+), 216 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/test_PSfilterSim.m diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f52b439 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main, pidscope] + pull_request: + branches: [main] + +permissions: {} + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + container: + image: ghcr.io/${{ github.repository_owner }}/pidscope-builder-appimage:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Run unit tests + run: octave --no-gui --eval "run('tests/run_tests.m')" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c1bb53..2ba9b66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,26 @@ env: permissions: {} jobs: + # Stage 0: Run tests before building anything + test: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + container: + image: ghcr.io/${{ github.repository_owner }}/pidscope-builder-appimage:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Run unit tests + run: octave --no-gui --eval "run('tests/run_tests.m')" + # Stage 1: Build Docker builder images (3 parallel) build-appimage-builder: + needs: test runs-on: ubuntu-latest permissions: contents: read @@ -33,6 +51,7 @@ jobs: docker push ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-appimage:latest build-windows-builder: + needs: test runs-on: ubuntu-latest permissions: contents: read @@ -51,24 +70,31 @@ jobs: docker build -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-windows:latest ./packaging/windows docker push ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-windows:latest - build-macos-builder: - runs-on: ubuntu-latest + build-macos-blackbox: + needs: test + strategy: + matrix: + include: + - runner: macos-14 + arch_name: arm64 + - runner: macos-13 + arch_name: x86_64 + runs-on: ${{ matrix.runner }} permissions: contents: read - packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Build blackbox_decode + run: | + git clone --depth 1 https://github.com/betaflight/blackbox-tools.git + cd blackbox-tools + make DEBUG= obj/blackbox_decode + cp obj/blackbox_decode ../blackbox_decode.${{ matrix.arch_name }} + codesign --force --sign - ../blackbox_decode.${{ matrix.arch_name }} - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push builder image - run: | - docker build -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-macos:latest ./packaging/macos - docker push ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-macos:latest + name: blackbox-decode-${{ matrix.arch_name }} + path: blackbox_decode.${{ matrix.arch_name }} # Stage 2: Build packages (each depends only on its own builder) build-appimage: @@ -130,28 +156,56 @@ jobs: path: dist/PIDscope-*-windows-x86_64.zip build-macos: - needs: build-macos-builder - runs-on: ubuntu-latest + needs: build-macos-blackbox + runs-on: macos-14 permissions: contents: read - packages: read + env: + INAV_BB_VERSION: "9.0.0" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + pattern: blackbox-decode-* + merge-multiple: true + + - name: Download INAV blackbox_decode + run: | + brew install zstd + wget -q "https://github.com/iNavFlight/blackbox-tools/releases/download/v${INAV_BB_VERSION}/blackbox-tools-${INAV_BB_VERSION}_macos-aarch64.tar.zst" -O inav-arm64.tar.zst + tar --zstd -xf inav-arm64.tar.zst && cp bin/blackbox_decode blackbox_decode_INAV.arm64 && rm -rf bin share inav-arm64.tar.zst + wget -q "https://github.com/iNavFlight/blackbox-tools/releases/download/v${INAV_BB_VERSION}/blackbox-tools-${INAV_BB_VERSION}_macos-x86_64.tar.zst" -O inav-x86.tar.zst + tar --zstd -xf inav-x86.tar.zst && cp bin/blackbox_decode blackbox_decode_INAV.x86_64 && rm -rf bin share inav-x86.tar.zst - - name: Build macOS ZIP + - name: Package macOS ZIP run: | + VERSION=$(tr -d '[:space:]' < VERSION) + STAGING="/tmp/PIDscope-${VERSION}-macos" + mkdir -p "$STAGING" + cp PIDscope.m "$STAGING/" + cp -r src "$STAGING/" + cp VERSION "$STAGING/" + cp packaging/macos/pidscope.command "$STAGING/" + chmod +x "$STAGING/pidscope.command" + cp blackbox_decode.arm64 blackbox_decode.x86_64 "$STAGING/" + cp blackbox_decode_INAV.arm64 blackbox_decode_INAV.x86_64 "$STAGING/" + chmod +x "$STAGING"/blackbox_decode* + if [ -f packaging/com.pidscope.PIDscope.png ]; then cp packaging/com.pidscope.PIDscope.png "$STAGING/PIDscope.png"; fi + cat > "$STAGING/README-macOS.txt" <<'HEREDOC' + PIDscope - Blackbox Flight Log Analyzer + + SETUP: + 1. brew install octave + 2. xattr -cr ~/Downloads/PIDscope-*-macos + 3. Double-click pidscope.command + (Octave packages are installed automatically on first launch) + + Project: https://buymeacoffee.com/dzikus + License: GPL v3 + HEREDOC mkdir -p dist - docker pull ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-macos:latest - docker run --rm \ - -v ${{ github.workspace }}:/src \ - -v ${{ github.workspace }}/dist:/dist \ - ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-macos:latest + cd /tmp && zip -r -q "$GITHUB_WORKSPACE/dist/PIDscope-${VERSION}-macos-universal.zip" "PIDscope-${VERSION}-macos" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/Makefile b/Makefile index 8de9e1e..522dc9b 100644 --- a/Makefile +++ b/Makefile @@ -1,60 +1,60 @@ -PREFIX ?= /usr/local -INSTALL_DIR = $(PREFIX)/share/pidscope -BIN_DIR = $(PREFIX)/bin -OCTAVE ?= octave - -.PHONY: run install install-deps fetch-blackbox test clean - -run: - $(OCTAVE) --gui --persist --eval "cd('$(CURDIR)'); PIDscope" - -install-deps: - @echo "Installing Octave and required packages..." - @if command -v apt-get >/dev/null 2>&1; then \ - sudo apt-get install -y octave octave-signal octave-statistics octave-control octave-image build-essential; \ - elif command -v dnf >/dev/null 2>&1; then \ - sudo dnf install -y octave octave-signal octave-statistics octave-control octave-image gcc make; \ - elif command -v pacman >/dev/null 2>&1; then \ - sudo pacman -S --noconfirm octave base-devel; \ - else \ - echo "Unknown package manager. Install octave and a C compiler manually."; exit 1; \ - fi - -fetch-blackbox: blackbox_decode blackbox_decode_INAV - -blackbox_decode: - @echo "Building blackbox_decode (betaflight)..." - @rm -rf /tmp/bf-blackbox-tools - git clone --depth 1 https://github.com/betaflight/blackbox-tools.git /tmp/bf-blackbox-tools - make -C /tmp/bf-blackbox-tools obj/blackbox_decode - cp /tmp/bf-blackbox-tools/obj/blackbox_decode . - chmod +x blackbox_decode - rm -rf /tmp/bf-blackbox-tools - @echo "blackbox_decode ready." - -blackbox_decode_INAV: - @echo "Building blackbox_decode_INAV (inav)..." - @rm -rf /tmp/inav-blackbox-tools - git clone --depth 1 https://github.com/iNavFlight/blackbox-tools.git /tmp/inav-blackbox-tools - make -C /tmp/inav-blackbox-tools obj/blackbox_decode - cp /tmp/inav-blackbox-tools/obj/blackbox_decode blackbox_decode_INAV - chmod +x blackbox_decode_INAV - rm -rf /tmp/inav-blackbox-tools - @echo "blackbox_decode_INAV ready." - -install: fetch-blackbox - install -d $(INSTALL_DIR) - install -d $(BIN_DIR) - cp PIDscope.m VERSION $(INSTALL_DIR)/ - cp -r src/ $(INSTALL_DIR)/ - cp blackbox_decode blackbox_decode_INAV $(INSTALL_DIR)/ - @printf '#!/bin/sh\nexec $(OCTAVE) --gui --eval "cd(\\\"$(INSTALL_DIR)\\\"); PIDscope"\n' > $(BIN_DIR)/pidscope - chmod +x $(BIN_DIR)/pidscope - @echo "Installed. Run with: pidscope" - -test: - $(OCTAVE) --no-gui --eval "addpath(genpath('src')); addpath('tests'); run_tests" - -clean: - rm -f blackbox_decode blackbox_decode_INAV - rm -f *.csv *.bbl *.bfl +PREFIX ?= /usr/local +INSTALL_DIR = $(PREFIX)/share/pidscope +BIN_DIR = $(PREFIX)/bin +OCTAVE ?= octave + +.PHONY: run install install-deps fetch-blackbox test clean + +run: + $(OCTAVE) --gui --persist --eval "cd('$(CURDIR)'); PIDscope" + +install-deps: + @echo "Installing Octave and required packages..." + @if command -v apt-get >/dev/null 2>&1; then \ + sudo apt-get install -y octave octave-signal octave-statistics octave-control octave-image build-essential; \ + elif command -v dnf >/dev/null 2>&1; then \ + sudo dnf install -y octave octave-signal octave-statistics octave-control octave-image gcc make; \ + elif command -v pacman >/dev/null 2>&1; then \ + sudo pacman -S --noconfirm octave base-devel; \ + else \ + echo "Unknown package manager. Install octave and a C compiler manually."; exit 1; \ + fi + +fetch-blackbox: blackbox_decode blackbox_decode_INAV + +blackbox_decode: + @echo "Building blackbox_decode (betaflight)..." + @rm -rf /tmp/bf-blackbox-tools + git clone --depth 1 https://github.com/betaflight/blackbox-tools.git /tmp/bf-blackbox-tools + make -C /tmp/bf-blackbox-tools obj/blackbox_decode + cp /tmp/bf-blackbox-tools/obj/blackbox_decode . + chmod +x blackbox_decode + rm -rf /tmp/bf-blackbox-tools + @echo "blackbox_decode ready." + +blackbox_decode_INAV: + @echo "Building blackbox_decode_INAV (inav)..." + @rm -rf /tmp/inav-blackbox-tools + git clone --depth 1 https://github.com/iNavFlight/blackbox-tools.git /tmp/inav-blackbox-tools + make -C /tmp/inav-blackbox-tools obj/blackbox_decode + cp /tmp/inav-blackbox-tools/obj/blackbox_decode blackbox_decode_INAV + chmod +x blackbox_decode_INAV + rm -rf /tmp/inav-blackbox-tools + @echo "blackbox_decode_INAV ready." + +install: fetch-blackbox + install -d $(INSTALL_DIR) + install -d $(BIN_DIR) + cp PIDscope.m VERSION $(INSTALL_DIR)/ + cp -r src/ $(INSTALL_DIR)/ + cp blackbox_decode blackbox_decode_INAV $(INSTALL_DIR)/ + @printf '#!/bin/sh\nexec $(OCTAVE) --gui --eval "cd(\\\"$(INSTALL_DIR)\\\"); PIDscope"\n' > $(BIN_DIR)/pidscope + chmod +x $(BIN_DIR)/pidscope + @echo "Installed. Run with: pidscope" + +test: + $(OCTAVE) --no-gui --eval "addpath(genpath('src')); addpath('tests'); run_tests" + +clean: + rm -f blackbox_decode blackbox_decode_INAV + rm -f *.csv *.bbl *.bfl diff --git a/packaging/macos/Dockerfile b/packaging/macos/Dockerfile index a5eb996..6d2c682 100644 --- a/packaging/macos/Dockerfile +++ b/packaging/macos/Dockerfile @@ -1,6 +1,4 @@ -# PIDscope macOS build environment -# Cross-compiles blackbox_decode as universal binary (arm64 + x86_64) -# using zig cc (no Apple SDK required). +# PIDscope macOS packaging (no compilation, binaries come from GH Actions or /cache) # # docker build -t pidscope-macos packaging/macos/ # docker run --rm -v $(pwd):/src -v $(pwd)/dist:/dist pidscope-macos @@ -9,43 +7,22 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ - zip wget git make xz-utils llvm \ + zip wget zstd \ && rm -rf /var/lib/apt/lists/* -# Install zig (for macOS cross-compilation without Apple SDK) -RUN wget -q "https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz" \ - -O /tmp/zig.tar.xz && \ - tar -xf /tmp/zig.tar.xz -C /opt && \ - rm /tmp/zig.tar.xz -ENV PATH="/opt/zig-linux-x86_64-0.13.0:${PATH}" +ARG INAV_BB_VERSION=9.0.0 -# Cross-compile blackbox_decode for macOS arm64 + x86_64 -# Betaflight -RUN git clone --depth=1 https://github.com/betaflight/blackbox-tools.git /tmp/bb-bf && \ - cd /tmp/bb-bf && \ - mkdir -p /cache && \ - CC="zig cc -target aarch64-macos" make -j$(nproc) obj/blackbox_decode && \ - zig cc -target aarch64-macos -o /cache/blackbox_decode.arm64 obj/*.o -lc -lm && \ - make clean && \ - CC="zig cc -target x86_64-macos" make -j$(nproc) obj/blackbox_decode && \ - zig cc -target x86_64-macos -o /cache/blackbox_decode.x86_64 obj/*.o -lc -lm && \ - llvm-lipo-14 -create /cache/blackbox_decode.arm64 /cache/blackbox_decode.x86_64 \ - -output /cache/blackbox_decode && \ - rm -f /cache/blackbox_decode.arm64 /cache/blackbox_decode.x86_64 && \ - rm -rf /tmp/bb-bf - -# iNav (LTO_FLAGS= and LDFLAGS= disable -flto which zig cc doesn't support for cross-targets) -RUN git clone --depth=1 https://github.com/iNavFlight/blackbox-tools.git /tmp/bb-inav && \ - cd /tmp/bb-inav && \ - CC="zig cc -target aarch64-macos" make -j$(nproc) LTO_FLAGS= LDFLAGS="-lm" obj/blackbox_decode && \ - cp obj/blackbox_decode /cache/blackbox_decode_INAV.arm64 && \ - make clean && \ - CC="zig cc -target x86_64-macos" make -j$(nproc) LTO_FLAGS= LDFLAGS="-lm" obj/blackbox_decode && \ - cp obj/blackbox_decode /cache/blackbox_decode_INAV.x86_64 && \ - llvm-lipo-14 -create /cache/blackbox_decode_INAV.arm64 /cache/blackbox_decode_INAV.x86_64 \ - -output /cache/blackbox_decode_INAV && \ - rm -f /cache/blackbox_decode_INAV.arm64 /cache/blackbox_decode_INAV.x86_64 && \ - rm -rf /tmp/bb-inav +RUN mkdir -p /cache && \ + wget -q "https://github.com/iNavFlight/blackbox-tools/releases/download/v${INAV_BB_VERSION}/blackbox-tools-${INAV_BB_VERSION}_macos-aarch64.tar.zst" \ + -O /tmp/inav-arm64.tar.zst && \ + tar --zstd -xf /tmp/inav-arm64.tar.zst -C /tmp && \ + cp /tmp/bin/blackbox_decode /cache/blackbox_decode_INAV.arm64 && \ + rm -rf /tmp/bin /tmp/share /tmp/inav-arm64.tar.zst && \ + wget -q "https://github.com/iNavFlight/blackbox-tools/releases/download/v${INAV_BB_VERSION}/blackbox-tools-${INAV_BB_VERSION}_macos-x86_64.tar.zst" \ + -O /tmp/inav-x86.tar.zst && \ + tar --zstd -xf /tmp/inav-x86.tar.zst -C /tmp && \ + cp /tmp/bin/blackbox_decode /cache/blackbox_decode_INAV.x86_64 && \ + rm -rf /tmp/bin /tmp/share /tmp/inav-x86.tar.zst WORKDIR /build COPY build-macos.sh /build/ diff --git a/packaging/macos/build-macos.sh b/packaging/macos/build-macos.sh index e03b48e..3a3abfc 100644 --- a/packaging/macos/build-macos.sh +++ b/packaging/macos/build-macos.sh @@ -27,16 +27,25 @@ echo "Copying PIDscope files..." cp "${SRC_DIR}"/PIDscope.m "${STAGING}/" cp -r "${SRC_DIR}/src" "${STAGING}/" -# 2. Copy blackbox_decode universal binaries +# 2. Copy blackbox_decode binaries +# BF: from GH Actions artifacts (mounted) or /cache; INAV: from /cache (Dockerfile) echo "Copying blackbox_decode binaries..." -cp /cache/blackbox_decode "${STAGING}/" -cp /cache/blackbox_decode_INAV "${STAGING}/" - -# 3. Copy launcher and helper scripts +for bin in blackbox_decode.arm64 blackbox_decode.x86_64 blackbox_decode_INAV.arm64 blackbox_decode_INAV.x86_64; do + if [ -f "/cache/${bin}" ]; then + cp "/cache/${bin}" "${STAGING}/" + elif [ -f "${SRC_DIR}/${bin}" ]; then + cp "${SRC_DIR}/${bin}" "${STAGING}/" + else + echo "WARNING: ${bin} not found, skipping (BF binaries come from GH Actions)" + fi +done + +# 3. Copy launcher cp "${SRC_DIR}/packaging/macos/pidscope.command" "${STAGING}/" -cp "${SRC_DIR}/packaging/macos/fix-quarantine.command" "${STAGING}/" -chmod +x "${STAGING}/pidscope.command" "${STAGING}/fix-quarantine.command" -chmod +x "${STAGING}/blackbox_decode" "${STAGING}/blackbox_decode_INAV" +chmod +x "${STAGING}/pidscope.command" +for bin in "${STAGING}"/blackbox_decode*; do + [ -f "$bin" ] && chmod +x "$bin" +done # 4. Copy icon if [ -f "${SRC_DIR}/packaging/com.pidscope.PIDscope.png" ]; then @@ -58,15 +67,12 @@ FIRST TIME SETUP: brew install octave (If you don't have Homebrew: https://brew.sh) -2. Install required Octave packages (first time only): - octave --eval "pkg install -forge signal statistics control image" - -3. Remove macOS quarantine (required after download): - Double-click "fix-quarantine.command" - (or run in Terminal: xattr -cr /path/to/PIDscope-folder) +2. Remove macOS quarantine - run this in Terminal: + xattr -cr ~/Downloads/PIDscope-*-macos -4. Launch PIDscope: +3. Launch PIDscope: Double-click "pidscope.command" + (Octave packages are installed automatically on first launch) ALTERNATIVE (Terminal): cd /path/to/PIDscope-folder @@ -74,7 +80,7 @@ ALTERNATIVE (Terminal): REQUIREMENTS: - macOS 12+ (Monterey or later) -- GNU Octave 9.x or 10.x (via Homebrew) +- GNU Octave 9.x, 10.x, or 11.x (via Homebrew) - Apple Silicon (M1/M2/M3/M4/M5) or Intel Mac Project: https://buymeacoffee.com/dzikus diff --git a/packaging/macos/fix-quarantine.command b/packaging/macos/fix-quarantine.command index 99b3fd5..38dcdba 100644 --- a/packaging/macos/fix-quarantine.command +++ b/packaging/macos/fix-quarantine.command @@ -1,12 +1,12 @@ #!/bin/bash # Remove macOS quarantine flags from PIDscope files -# Double-click this file in Finder after downloading PIDscope +# Run in Terminal: bash fix-quarantine.command DIR="$(cd "$(dirname "$0")" && pwd)" echo "Removing quarantine flags from PIDscope..." xattr -r -d com.apple.quarantine "$DIR" 2>/dev/null -chmod +x "$DIR/blackbox_decode" "$DIR/blackbox_decode_INAV" "$DIR/pidscope.command" 2>/dev/null +chmod +x "$DIR"/blackbox_decode.* "$DIR"/blackbox_decode_INAV.* "$DIR/pidscope.command" 2>/dev/null echo "" echo "Done! You can now launch PIDscope by double-clicking pidscope.command" diff --git a/packaging/macos/pidscope.command b/packaging/macos/pidscope.command index 29dc735..a5e3bcd 100644 --- a/packaging/macos/pidscope.command +++ b/packaging/macos/pidscope.command @@ -5,6 +5,16 @@ DIR="$(cd "$(dirname "$0")" && pwd)" cd "$DIR" +# Set up arch-specific blackbox_decode symlinks +ARCH=$(uname -m) +if [ "$ARCH" = "arm64" ]; then + ln -sf blackbox_decode.arm64 "$DIR/blackbox_decode" + ln -sf blackbox_decode_INAV.arm64 "$DIR/blackbox_decode_INAV" +else + ln -sf blackbox_decode.x86_64 "$DIR/blackbox_decode" + ln -sf blackbox_decode_INAV.x86_64 "$DIR/blackbox_decode_INAV" +fi + # Find Octave if command -v octave >/dev/null 2>&1; then OCTAVE=octave @@ -15,14 +25,19 @@ elif [ -x "/usr/local/bin/octave" ]; then elif [ -d "/Applications/Octave-9.2.app" ]; then OCTAVE="/Applications/Octave-9.2.app/Contents/Resources/usr/bin/octave" else - osascript -e 'display dialog "GNU Octave not found.\n\nInstall with:\n brew install octave\n\nThen install packages:\n octave --eval \"pkg install -forge signal statistics control image\"" with title "PIDscope" buttons {"OK"} default button "OK" with icon stop' + osascript -e 'display dialog "GNU Octave not found.\n\nInstall with:\n brew install octave\n\nThen install packages:\n octave --eval \"pkg install -forge datatypes signal statistics control image\"" with title "PIDscope" buttons {"OK"} default button "OK" with icon stop' exit 1 fi -# Check if Octave packages are installed (quick check) -if ! "$OCTAVE" --no-gui --eval "pkg load signal" 2>/dev/null; then - osascript -e 'display dialog "Required Octave packages not found.\n\nRun in Terminal:\n octave --eval \"pkg install -forge signal statistics control image\"" with title "PIDscope" buttons {"OK"} default button "OK" with icon caution' - exit 1 +# Check and auto-install required Octave Forge packages +PKGMISSING=$("$OCTAVE" --no-gui --eval "for p={'signal','control','image','statistics'}; if isempty(pkg('list',p{1})), fprintf('MISSING\n'); break; end; end" 2>/dev/null) +if echo "$PKGMISSING" | grep -q MISSING; then + osascript -e 'display notification "Installing required Octave packages (first launch only)..." with title "PIDscope"' + "$OCTAVE" --no-gui --eval "pkg install -forge datatypes signal statistics control image" 2>&1 | tee /tmp/pidscope-pkg-install.log + if [ $? -ne 0 ]; then + osascript -e 'display dialog "Package installation failed.\n\nCheck /tmp/pidscope-pkg-install.log for details.\n\nOr install manually:\n octave --eval \"pkg install -forge datatypes signal statistics control image\"" with title "PIDscope" buttons {"OK"} default button "OK" with icon stop' + exit 1 + fi fi exec "$OCTAVE" --gui --persist --eval "cd('$DIR'); PIDscope" diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index f34be09..172a06a 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -44,6 +44,11 @@ cmd = ['"' decoder_path '" --output-dir "' outdir '" "' filename '" 2>&1']; end [status, result] = system(cmd); + if status ~= 0 + warning('blackbox_decode failed (exit %d): %s', status, result); + csvFnames = {}; + return; + end fbase = fullfile(outdir, fname); files = dir([fbase '*.csv']); diff --git a/src/core/PSload.m b/src/core/PSload.m index 60f4c18..89bb313 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -140,6 +140,48 @@ if detFwIdx > 0 && detFwIdx ~= get(guiHandles.Firmware, 'Value') set(guiHandles.Firmware, 'Value', detFwIdx); end + if detFwIdx > 0 && detFwIdx ~= loaded_firmware && fcnt > 1 + % stash current file (already imported above) + stash_T_ = T{fcnt}; stash_SI_ = SetupInfo{fcnt}; + stash_LR_ = A_lograte(fcnt); stash_TTA_ = tta{fcnt}; + stash_E1_ = epoch1_A(fcnt); stash_E2_ = epoch2_A(fcnt); + stash_FN_ = fnameMaster{fcnt}; + stash_FwT_ = fwType{fcnt}; stash_FwMaj_ = fwMajor(fcnt); + stash_FwMin_ = fwMinor(fcnt); stash_DbgIdx_ = debugIdx{fcnt}; + + clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo rollPIDF pitchPIDF yawPIDF debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis notchData rpmFilterData ampmat freq2d2 amp2d2 specMat delayDataReady FilterDelayDterm SPGyroDelay Debug01 Debug02 gyro_phase_shift_deg dterm_phase_shift_deg tuneCrtlpanel_init setupInfoWidgets_init; + fnameMaster = {}; + try, delete(checkpanel); clear checkpanel; catch, end + try delete(findobj(PSfig,'Tag','PSrpy')); catch, end + try delete(findobj(PSfig,'Tag','PSmotor')); catch, end + try delete(findobj(PSfig,'Tag','PScombo')); catch, end + ov = getappdata(PSfig, 'PSoverlay'); + if ~isempty(ov) + flds = fieldnames(ov); + for fi=1:numel(flds), try delete(ov.(flds{fi})); catch, end; end + setappdata(PSfig, 'PSoverlay', []); + end + figs=findobj('Type','figure'); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end + clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; + set(guiHandles.FileNum, 'String', ' '); + try, set(guiHandles.Epoch1_A_Input, 'String', ' '); set(guiHandles.Epoch2_A_Input, 'String', ' '); catch, end + try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end + try setappdata(PSfig, 'rfMotorCount', []); catch, end + + % restore as file #1 and continue processing + fcnt = 1; Nfiles = 1; + T{1} = stash_T_; SetupInfo{1} = stash_SI_; + A_lograte(1) = stash_LR_; tta{1} = stash_TTA_; + epoch1_A(1) = stash_E1_; epoch2_A(1) = stash_E2_; + fnameMaster{1} = stash_FN_; + fwType{1} = stash_FwT_; fwMajor(1) = stash_FwMaj_; + fwMinor(1) = stash_FwMin_; debugIdx{1} = stash_DbgIdx_; + clear stash_T_ stash_SI_ stash_LR_ stash_TTA_ stash_E1_ stash_E2_ stash_FN_ stash_FwT_ stash_FwMaj_ stash_FwMin_ stash_DbgIdx_; + loaded_firmware = detFwIdx; + end + if detFwIdx > 0 + loaded_firmware = detFwIdx; + end %%%%%%%%%% collect debug mode info %%%%%%%%%% try diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 7ef48d8..0dbcad7 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -85,7 +85,9 @@ try delete(findobj(PSspecfig3, 'Tag', 'PScbar')); catch, end for i = 1 : 3 - delete(subplot('position',posInfo.Spec3Pos(i,:))); + stag_ = sprintf('PSfreqTime_%d', i); + h_old = findobj(PSspecfig3, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_old), delete(h_old); end try if ~updateSpec fld = [char(datSelectionString(tmpSpecVal3)) '_' int2str(i-1) '_']; @@ -93,8 +95,7 @@ [Tm F specMat{i}] = PStimeFreqCalc(dat', A_lograte(tmpFileVal3), specSmoothFactors(tmpSmoothVal3), timeSmoothFactors(tmpSubVal3)); end - h2=subplot('position',posInfo.Spec3Pos(i,:)); - set(h2, 'Tag', 'PSgrid'); + h2 = axes('Parent', PSspecfig3, 'Position', posInfo.Spec3Pos(i,:), 'Tag', stag_); h = imagesc(specMat{i}); set(gca,'Clim',[ClimScale3], 'fontsize',fontsz,'fontweight','bold') @@ -130,7 +131,8 @@ try set(cbar, 'Color', th.axesFg); catch, end if i == 3 && (strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisD') || strcmp(char(datSelectionString(get(guiHandlesSpec3.SpecList, 'Value'))), 'axisDpf')) - delete(subplot('position',posInfo.Spec3Pos(i,:))); + h_del = findobj(PSspecfig3, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_del), delete(h_del); end end box off @@ -198,7 +200,8 @@ end catch - delete(subplot('position',posInfo.Spec3Pos(i,:))); + h_del = findobj(PSspecfig3, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_del), delete(h_del); end end end updateSpec = 0; diff --git a/src/plot/PSplotDynNotchOverlay.m b/src/plot/PSplotDynNotchOverlay.m index ee81e77..35f8387 100644 --- a/src/plot/PSplotDynNotchOverlay.m +++ b/src/plot/PSplotDynNotchOverlay.m @@ -39,7 +39,12 @@ function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode, lw end end if numel(yPts) >= 5 - yPts = round(smooth(yPts(:), 5))'; + try + yPts = round(smooth(yPts(:), 5))'; + catch + kernel = ones(5,1)/5; + yPts = round(conv(yPts(:), kernel, 'same'))'; + end end if ~isempty(xPts) h = plot(ax, xPts, yPts, '-', 'LineWidth', lw); @@ -74,7 +79,12 @@ function PSplotDynNotchOverlay(ax, notchMat, xData, imgHeight, freqMax, mode, lw end end if numel(yPts) >= 5 - yPts = round(smooth(yPts(:), 5))'; + try + yPts = round(smooth(yPts(:), 5))'; + catch + kernel = ones(5,1)/5; + yPts = round(conv(yPts(:), kernel, 'same'))'; + end end if ~isempty(xPts) h = plot(ax, xPts, yPts, '-', 'LineWidth', lw); diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 593b758..6d34515 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -164,12 +164,12 @@ if get(guiHandles.RPYcomboLV, 'Value'), expandON = 0; end lpKey = ['linepos' int2str(ii)]; if ~get(guiHandles.RPYcomboLV, 'Value') && ~expandON - LVpanels{ii} = subplot('position', posInfo.(lpKey)); - LVpanel5 = subplot('position',posInfo.linepos4); + LVpanels{ii} = axes('Parent', PSfig, 'Position', posInfo.(lpKey), 'Tag', 'PSrpy'); + LVpanel5 = axes('Parent', PSfig, 'Position', posInfo.linepos4, 'Tag', 'PSmotor'); end if ~get(guiHandles.RPYcomboLV, 'Value') && expandON try - subplot(hexpand{ii}, 'position', expand_sz); + set(hexpand{ii}, 'Position', expand_sz); catch end end @@ -179,12 +179,13 @@ cntLV = cntLV + 1; if get(guiHandles.RPYcomboLV, 'Value') - LVpanel4 = subplot('position' ,fullszPlot); - set(LVpanel4, 'Tag', 'PScombo'); + LVpanel4 = findobj(PSfig, 'Type', 'axes', 'Tag', 'PScombo'); + if isempty(LVpanel4), LVpanel4 = axes('Parent', PSfig, 'Position', fullszPlot, 'Tag', 'PScombo'); + else set(PSfig, 'CurrentAxes', LVpanel4); end lnstyle = lineStyle2LV; end if ~get(guiHandles.RPYcomboLV, 'Value') && expandON == 0 - LVpanels{ii} = subplot('position', posInfo.(lpKey)); + set(PSfig, 'CurrentAxes', LVpanels{ii}); lnstyle = lineStyleLV; end @@ -243,7 +244,9 @@ grid on % Percent variables - LVpanel5 = subplot('position',posInfo.linepos4); + LVpanel5 = findobj(PSfig, 'Type', 'axes', 'Tag', 'PSmotor'); + if isempty(LVpanel5), LVpanel5 = axes('Parent', PSfig, 'Position', posInfo.linepos4, 'Tag', 'PSmotor'); + else set(PSfig, 'CurrentAxes', LVpanel5); end if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_0_', sFactor));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_1_', sFactor));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_2_', sFactor));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end diff --git a/src/plot/PSplotPIDerror.m b/src/plot/PSplotPIDerror.m index 3fbe4da..d3a3936 100644 --- a/src/plot/PSplotPIDerror.m +++ b/src/plot/PSplotPIDerror.m @@ -9,6 +9,8 @@ try +if ~exist('fnameMaster','var') || isempty(fnameMaster), return; end + set(PSerrfig, 'pointer', 'watch'); th = PStheme(); @@ -29,9 +31,10 @@ ylab2 = {'roll'; 'pitch'; 'yaw'}; figure(PSerrfig); for p = 1:3 - delete(subplot('position', posInfo.PIDerrAnalysis(p,:))); - h1 = subplot('position', posInfo.PIDerrAnalysis(p,:)); cla; - set(h1, 'Tag', 'PSgrid'); + stag_ = sprintf('PSerr_%d', p); + h_old = findobj(PSerrfig, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_old), delete(h_old); end + h1 = axes('Parent', PSerrfig, 'Position', posInfo.PIDerrAnalysis(p,:), 'Tag', stag_); hold on; piderr_A = T{fA}.(axPIDerr{p})(tIND{fA})'; @@ -142,9 +145,10 @@ %% plot error x stick ylab = ['R'; 'P'; 'Y']; for p = 1:3 - delete(subplot('position', posInfo.PIDerrAnalysis(p+3,:))); - h1 = subplot('position', posInfo.PIDerrAnalysis(p+3,:)); cla; - set(h1, 'Tag', 'PSgrid'); + stag_ = sprintf('PSerr_%d', p+3); + h_old = findobj(PSerrfig, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_old), delete(h_old); end + h1 = axes('Parent', PSerrfig, 'Position', posInfo.PIDerrAnalysis(p+3,:), 'Tag', stag_); posAx = .8:1:9.8; posBx = 1.2:1:10.2; diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index f89d257..c470b89 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -136,12 +136,12 @@ baselineY = [0 -40]; ftr = fspecial('gaussian',[get(guiHandlesSpec.smoothFactor_select, 'Value')*5 get(guiHandlesSpec.smoothFactor_select, 'Value')],4); for p=1:size(ampmat,2) - try delete(subplot('position',posInfo.SpecPos(p,:))); catch, end + stag_ = sprintf('PSspec_%d', p); + h_old = findobj(PSspecfig, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_old), delete(h_old); end if ~isempty(ampmat{p}) try - delete(subplot('position',posInfo.SpecPos(p,:))); - h1=subplot('position',posInfo.SpecPos(p,:)); cla - set(h1, 'Tag', 'PSgrid'); + h1 = axes('Parent', PSspecfig, 'Position', posInfo.SpecPos(p,:), 'Tag', stag_); img = flipud((filter2(ftr, ampmat{p} ))') + baselineY(get(guiHandlesSpec.checkboxPSD, 'Value')+1); imagesc(img); @@ -340,10 +340,11 @@ for p=1:size(amp2d,2) axLabel={'roll';'pitch';'yaw'}; - delete(subplot('position',posInfo.SpecPos(p,:))); + stag_ = sprintf('PSspec_%d', p); + h_old = findobj(PSspecfig, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_old), delete(h_old); end if ~isempty(amp2d{p}) - h2=subplot('position',posInfo.SpecPos(p,:)); cla - set(h2, 'Tag', 'PSgrid'); + h2 = axes('Parent', PSspecfig, 'Position', posInfo.SpecPos(p,:), 'Tag', stag_); h=plot(freq2d{p}, smooth(amp2d{p}, log10(size(amp2d{p},1)) * (get(guiHandlesSpec.smoothFactor_select, 'Value')^2), 'lowess'));hold on set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2) set(h2,'fontsize',fontsz,'fontweight','bold') diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 2f6d81b..b0eb643 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -178,12 +178,8 @@ multilineStyle = {'-' ; ':'; '--'}; rpyLineStyle = {'-' ; '--'; ':'}; -delete(subplot('position',posInfo.Spec2Pos(1,:))) -delete(subplot('position',posInfo.Spec2Pos(2,:))) -delete(subplot('position',posInfo.Spec2Pos(3,:))) -delete(subplot('position',posInfo.Spec2Pos(4,:))) -delete(subplot('position',posInfo.Spec2Pos(5,:))) -delete(subplot('position',posInfo.Spec2Pos(6,:))) +for di_=1:6, h_del=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_del), delete(h_del); end; end +h_del=findobj(PSspecfig2,'Type','axes','Tag','PSspec2_combo'); if ~isempty(h_del), delete(h_del); end %%%%% plot 2d amp spec axLabel={'Roll';'Pitch';'Yaw'}; @@ -203,9 +199,11 @@ if ~isempty(freq2d2{p}) && ~isempty(amp2d2{p}) if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 - - h2=subplot('position',posInfo.Spec2Pos(a,:)); - set(h2, 'Tag', 'PSgrid'); + + stag_ = sprintf('PSspec2_%d', a); + h2 = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag_); + if isempty(h2), h2 = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(a,:), 'Tag', stag_); + else set(PSspecfig2, 'CurrentAxes', h2); end ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -230,8 +228,10 @@ end grid on - h2=subplot('position',posInfo.Spec2Pos(a+3,:)); - set(h2, 'Tag', 'PSgrid'); + stag2_ = sprintf('PSspec2_%d', a+3); + h2 = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag2_); + if isempty(h2), h2 = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(a+3,:), 'Tag', stag2_); + else set(PSspecfig2, 'CurrentAxes', h2); end ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -299,8 +299,9 @@ else % combine R P Y - h2=subplot('position',[0.0500 0.1000 cpL-0.1 0.840]); - set(h2, 'Tag', 'PSgrid'); + h2 = findobj(PSspecfig2, 'Type', 'axes', 'Tag', 'PSspec2_combo'); + if isempty(h2), h2 = axes('Parent', PSspecfig2, 'Position', [0.0500 0.1000 cpL-0.1 0.840], 'Tag', 'PSspec2_combo'); + else set(PSspecfig2, 'CurrentAxes', h2); end ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index be36990..b34f651 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -54,8 +54,10 @@ end for sp = 1:4 - hhist = subplot('position', posInfo.statsPos(sp,:)); cla; - set(hhist, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstats_%d', sp); + hhist = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); + if isempty(hhist), hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp,:), 'Tag', stag_); + else set(PSstatsfig, 'CurrentAxes', hhist); cla; end if sp <= 3 pctData = Rpct_A{sp}; else @@ -92,8 +94,10 @@ end for sp = 1:4 - hhist = subplot('position', posInfo.statsPos(sp+4,:)); cla; - set(hhist, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstats_%d', sp+4); + hhist = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); + if isempty(hhist), hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp+4,:), 'Tag', stag_); + else set(PSstatsfig, 'CurrentAxes', hhist); cla; end if sp <= 3 pctData = Rpct_B{sp}; else @@ -167,8 +171,10 @@ xlbl = grp{3}; useAbs = grp{4}; - h1 = subplot('position', posInfo.statsPos2(slots(g),:)); cla; - set(h1, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstats2_%d', slots(g)); + h1 = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); + if isempty(h1), h1 = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos2(slots(g),:), 'Tag', stag_); + else set(PSstatsfig, 'CurrentAxes', h1); cla; end vals = zeros(nAx, 1); sds = zeros(nAx, 1); diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 1be323d..495a51c 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -25,7 +25,7 @@ pidlabels = p{fwIdx}; %%%%%%%%%%%%% step resp %%%%%%%%%%%%% -figure(PStunefig) +figure(PStunefig); ymax = str2double(get(guiHandlesTune.maxYStepInput, 'String')); ypos = [(ymax/3)*2.9 (ymax/3)*1.85 (ymax/3)*.8]; @@ -55,8 +55,10 @@ end if get(guiHandlesTune.RPYcombo, 'Value') == 0 - h1=subplot('position',posInfo.TparamsPos(p,:)); - set(h1, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstep_%d', p); + h1 = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); + if isempty(h1), h1 = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p,:), 'Tag', stag_); + else set(PStunefig, 'CurrentAxes', h1); end hold on if size(stepresp_A{p},1)>1 @@ -93,8 +95,10 @@ grid on % Col 2: PID text in dedicated column - hTxt=subplot('position',posInfo.TparamsPos(p+3,:)); - set(hTxt, 'Tag', 'PSgrid', 'Visible', 'off', 'XLim', [0 1], 'YLim', [0 1]); + stag_ = sprintf('PSstep_%d', p+3); + hTxt = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); + if isempty(hTxt), hTxt = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p+3,:), 'Tag', stag_, 'Visible', 'off', 'XLim', [0 1], 'YLim', [0 1]); + else set(PStunefig, 'CurrentAxes', hTxt); end hold on if size(stepresp_A{p},1)>1 if cnt <= 3, h=text(0.05, 0.97, [pidlabels],'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end @@ -107,8 +111,10 @@ end % Col 3: Peak - h2=subplot('position',posInfo.TparamsPos(p+6,:)); - set(h2, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstep_%d', p+6); + h2 = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); + if isempty(h2), h2 = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p+6,:), 'Tag', stag_); + else set(PStunefig, 'CurrentAxes', h2); end h=plot(fcntSR, peakresp(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) @@ -119,8 +125,10 @@ plot([0 10],[1 1],'--','Color',th.axesFg) % Col 4: Latency - h3=subplot('position',posInfo.TparamsPos(p+9,:)); - set(h3, 'Tag', 'PSgrid'); + stag_ = sprintf('PSstep_%d', p+9); + h3 = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); + if isempty(h3), h3 = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p+9,:), 'Tag', stag_); + else set(PStunefig, 'CurrentAxes', h3); end h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) @@ -143,7 +151,9 @@ if get(guiHandlesTune.RPYcombo, 'Value') == 1 - h1=subplot('position',[0.0500 0.1 0.72 0.84]) + h1 = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_combo'); + if isempty(h1), h1 = axes('Parent', PStunefig, 'Position', [0.0500 0.1 0.72 0.84], 'Tag', 'PSstep_combo'); + else set(PStunefig, 'CurrentAxes', h1); end hold on if size(stepresp_A{p},1)>1 @@ -218,16 +228,19 @@ updateStep=0; else for p = 1 : 3 - delete(subplot('position',posInfo.TparamsPos(p,:))) - delete(subplot('position',posInfo.TparamsPos(p+3,:))) - delete(subplot('position',posInfo.TparamsPos(p+6,:))) - delete(subplot('position',posInfo.TparamsPos(p+9,:))) + for off_ = [0 3 6 9] + stag_ = sprintf('PSstep_%d', p+off_); + h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); + if ~isempty(h_del), delete(h_del); end + end peaktime = []; peakresp = []; latencyHalfHeight = []; latencyHalfHeight_std = []; peakresp_std = []; end + h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_combo'); + if ~isempty(h_del), delete(h_del); end end diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 2d0ccc3..07d47f9 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -162,13 +162,13 @@ 'callback','PSplotSpec2D;'); guiHandlesSpec2.plotR =uicontrol(PSspecfig2,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotRspec], 'callback', 'delete(findobj(PSspecfig2,''Type'',''axes'')); set(PSspecfig2, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotRspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.plotP =uicontrol(PSspecfig2,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotPspec], 'callback', 'delete(findobj(PSspecfig2,''Type'',''axes'')); set(PSspecfig2, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotPspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.plotY =uicontrol(PSspecfig2,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotYspec], 'callback', 'delete(findobj(PSspecfig2,''Type'',''axes'')); set(PSspecfig2, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotYspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.checkboxPSD =uicontrol(PSspecfig2,'Style','checkbox','String','PSD','fontsize',fontsz,'TooltipString', ['Power Spectral Density'],... 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec2D;'); diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 64a05e1..84e7481 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -36,7 +36,7 @@ TooltipString_fileListWindowStep=['List of files available. Click to select which files to run']; TooltipString_clearPlot=['Clears lines from all subplots']; -fcntSR = 0; +if ~exist('fcntSR','var'), fcntSR = 0; end clear posInfo.TparamsPos plotR = cpL - 0.02; plotLt = 0.07; colGapT = 0.015; @@ -99,14 +99,14 @@ set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotP, 'Value', 1); guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotY, 'Value', 0); guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... @@ -114,16 +114,16 @@ set(guiHandlesTune.clearPlots, 'ForegroundColor', cautionCol); guiHandlesTune.Ycorrection =uicontrol(PStunefig,'Style','checkbox','String','Y correction','fontsize',fontsz,'TooltipString', ['Y axis offset correction '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Ycorrection],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.Ycorrection],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.Ycorrection, 'Value', 0); guiHandlesTune.RPYcombo =uicontrol(PStunefig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'TooltipString', ['Plot RPY in same panel '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow''); PStuningParams;'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.RPYcombo, 'Value', 0); guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); guiHandlesTune.maxYStepInput = uicontrol(PStunefig,'style','edit','string','1.75','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','Position',[posInfo.maxYStepInput],... - 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0;PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal'') ;PStuningParams; '); + 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothing off' 'smoothing low' 'smoothing medium' 'smoothing high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... 'callback','@selection2;'); diff --git a/tests/run_tests.m b/tests/run_tests.m index 4b8f477..0254a07 100644 --- a/tests/run_tests.m +++ b/tests/run_tests.m @@ -64,6 +64,7 @@ fprintf(' - %s\n', failed_files{i}); end fprintf('\nSOME TESTS FAILED\n'); + exit(1); else fprintf('\nALL TESTS PASSED\n'); end diff --git a/tests/test_PSfilterSim.m b/tests/test_PSfilterSim.m new file mode 100644 index 0000000..5a7ac2a --- /dev/null +++ b/tests/test_PSfilterSim.m @@ -0,0 +1,117 @@ +% test_PSfilterSim.m - verify filter response calculations used in PSfilterSim + +%!test +%! % PT1 at cutoff: forward Euler shifts -3dB point, expect -3 to -4 dB +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! [H, f] = freqz(b, a, 4096, Fs); +%! [~, ifc] = min(abs(f - fc)); +%! mag_at_fc = 20*log10(abs(H(ifc))); +%! assert(mag_at_fc, -3, 1.0); + +%!test +%! % PT2 at cutoff: BF corrects fc so -3dB lands near nominal fc +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt2', fc, Fs); +%! [H, f] = freqz(b, a, 4096, Fs); +%! [~, ifc] = min(abs(f - fc)); +%! mag_at_fc = 20*log10(abs(H(ifc))); +%! assert(mag_at_fc, -3, 1.5); + +%!test +%! % PT3 at cutoff: BF corrects fc so -3dB lands near nominal fc +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt3', fc, Fs); +%! [H, f] = freqz(b, a, 4096, Fs); +%! [~, ifc] = min(abs(f - fc)); +%! mag_at_fc = 20*log10(abs(H(ifc))); +%! assert(mag_at_fc, -3, 2.0); + +%!test +%! % DC gain of all lowpass types should be 0 dB +%! Fs = 4000; fc = 200; +%! for t = {'pt1', 'pt2', 'pt3', 'biquad'} +%! [b, a] = PSbfFilters(t{1}, fc, Fs); +%! [H, ~] = freqz(b, a, 4096, Fs); +%! dc_dB = 20*log10(abs(H(1))); +%! assert(abs(dc_dB) < 0.01); +%! end + +%!test +%! % Notch filter: near-zero magnitude at center, unity elsewhere +%! Fs = 4000; fc = 300; Q = 5; +%! [b, a] = PSbfFilters('notch', fc, Fs, Q); +%! [H, f] = freqz(b, a, 4096, Fs); +%! [~, ifc] = min(abs(f - fc)); +%! mag_notch = 20*log10(abs(H(ifc))); +%! mag_dc = 20*log10(abs(H(1))); +%! assert(mag_notch < -20); +%! assert(abs(mag_dc) < 0.01); + +%!test +%! % Cascaded H multiplication matches sequential filtering +%! Fs = 4000; +%! [b1, a1] = PSbfFilters('pt1', 200, Fs); +%! [b2, a2] = PSbfFilters('pt2', 300, Fs); +%! Nfft = 2048; +%! [H1, ~] = freqz(b1, a1, Nfft, Fs); +%! [H2, ~] = freqz(b2, a2, Nfft, Fs); +%! H_cascade = H1(:) .* H2(:); +%! imp = [1; zeros(Nfft*2-1, 1)]; +%! out = filter(b1, a1, imp); +%! out = filter(b2, a2, out); +%! H_seq = fft(out, Nfft*2); +%! H_seq = H_seq(1:Nfft); +%! mag_cascade = 20*log10(abs(H_cascade)); +%! mag_seq = 20*log10(abs(H_seq)); +%! assert(max(abs(mag_cascade - mag_seq)) < 0.5); + +%!test +%! % Group delay of PT1 at DC should be ~1/(2*pi*fc) seconds +%! Fs = 8000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! [H, f] = freqz(b, a, 4096, Fs); +%! f = f(:); +%! dw = gradient(2*pi*f); +%! gd_s = -gradient(unwrap(angle(H(:)))) ./ dw; +%! gd_ms = gd_s * 1000; +%! gd_dc = gd_ms(2); % skip f=0 edge artifact +%! expected_ms = 1/(2*pi*fc) * 1000; +%! assert(gd_dc, expected_ms, 0.15); + +%!test +%! % Step response of PT1 should reach ~63.2% at t=RC +%! Fs = 8000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! stepIn = ones(round(Fs * 0.050), 1); +%! stepOut = filter(b, a, stepIn); +%! RC_samples = round(1/(2*pi*fc) * Fs); +%! val_at_RC = stepOut(RC_samples); +%! assert(val_at_RC, 0.632, 0.05); + +%!test +%! % Step response of lowpass should settle to 1.0 +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt2', fc, Fs); +%! stepIn = ones(round(Fs * 0.1), 1); +%! stepOut = filter(b, a, stepIn); +%! assert(stepOut(end), 1.0, 0.01); + +%!test +%! % PT1 phase is 0 at DC and negative in passband +%! Fs = 4000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! [H, f] = freqz(b, a, 512, Fs); +%! ph = angle(H) * 180/pi; +%! assert(abs(ph(1)) < 0.1); % 0 at DC +%! [~, ifc] = min(abs(f - fc)); +%! assert(ph(ifc) < 0); % negative at cutoff + +%!test +%! % PT1 phase at cutoff should be -45 degrees +%! Fs = 8000; fc = 200; +%! [b, a] = PSbfFilters('pt1', fc, Fs); +%! [H, f] = freqz(b, a, 4096, Fs); +%! [~, ifc] = min(abs(f - fc)); +%! ph_deg = angle(H(ifc)) * 180/pi; +%! assert(ph_deg, -45, 3); From b026103a9a859db00367d603f9efbc83784796f3 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 14 Mar 2026 22:54:09 +0100 Subject: [PATCH 39/80] fix(ui): dynamic axis scaling, motor expansion, FileNum reload, Step Response data preservation --- src/plot/PSplotLogViewer.m | 29 +++++++++++++++++++++++++++++ src/plot/PSplotSpec2D.m | 20 ++++++++++++++++++++ src/plot/PStuningParams.m | 25 +++++++++++++++++++++++++ src/ui/PStuneUIcontrol.m | 8 ++++---- src/ui/PSviewerUIcontrol.m | 3 +++ src/util/PSresizeCP.m | 9 ++++++--- 6 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 6d34515..c4387dc 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -289,6 +289,35 @@ catch end end + + % motor-only mode: fill available space when all RPY disabled + if isempty(axesOptionsLV) && ~get(guiHandles.RPYcomboLV, 'Value') && ~expandON + plotTop_m = posInfo.slider(2) - 0.005; + motorFullH = plotTop_m - 0.1 - 0.01; + LVpanel5 = axes('Parent', PSfig, 'Position', [plotL 0.1 plotW motorFullH], 'Tag', 'PSmotor'); + fileIdx = get(guiHandles.FileNum, 'Value'); + sFactor = lineSmoothFactors(get(guiHandles.lineSmooth, 'Value')); + lwVal = get(guiHandles.linewidth, 'Value')/2; + xmax = max(tta{fileIdx}/us2sec); + tSec = tta{fileIdx}/us2sec; + if get(guiHandles.checkbox10, 'Value'), try hch11=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_0_', sFactor));hold on;set(hch11,'color', [linec.col10],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox11, 'Value'), try hch12=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_1_', sFactor));hold on;set(hch12,'color', [linec.col11],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox12, 'Value'), try hch13=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_2_', sFactor));hold on;set(hch13,'color', [linec.col12],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox13, 'Value'), try hch14=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_3_', sFactor));hold on;set(hch14,'color', [linec.col13],'LineWidth',lwVal), catch, end, end + if get(guiHandles.checkbox14, 'Value'), try hch19=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'setpoint_3_', sFactor, 0.1));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), catch, end, end + axis([0 xmax 0 100]) + set(gca,'Color',th.axesBg); + h=fill([0,t1,t1,0],[0, 0, 100, 100],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); + h=fill([t2,xmax,xmax,t2],[0, 0, 100, 100],th.epochFill); + set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); + y=xlabel('Time (s)','fontweight','bold'); set(y,'color',th.textPrimary); + y=ylabel({'Throttle | Motor (%)'},'fontweight','bold'); set(y,'color',th.textPrimary); + set(gca,'fontsize',fontsz,'XMinorGrid','on','ylim',[0 100],'ytick',[0 20 40 60 80 100],'fontweight','bold') + set(gca,'xtick',[round(xmax/10):round(xmax/10):round(xmax)],'XColor',th.axesFg,'YColor',th.axesFg,'GridColor',th.gridColor) + grid on + set(LVpanel5,'color',th.axesBg,'fontsize',fontsz,'tickdir','in','xminortick','on','yminortick','on','Tag','PSmotor'); + end end % i/o keyboard trim: 'i' sets in-point, 'o' sets out-point diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index b0eb643..3d651d1 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -22,6 +22,26 @@ datSelectionString=[s1]; axesOptionsSpec = find([get(guiHandlesSpec2.plotR, 'Value') get(guiHandlesSpec2.plotP, 'Value') get(guiHandlesSpec2.plotY, 'Value')]); +% scale row heights to fill space when fewer than 3 RPY axes +nActiveSpec = numel(axesOptionsSpec); +stdRows = [0.69 0.395 0.1]; stdRowH = 0.25; +if nActiveSpec > 0 && nActiveSpec < 3 && ~get(guiHandlesSpec2.RPYcomboSpec, 'Value') + topY_s = stdRows(1) + stdRowH; botY_s = stdRows(3); gapS = 0.045; + rowH_s = (topY_s - botY_s - (nActiveSpec-1)*gapS) / nActiveSpec; + ci = 0; + for jj = axesOptionsSpec + ci = ci + 1; + yy = topY_s - ci*rowH_s - (ci-1)*gapS; + posInfo.Spec2Pos(jj, 2) = yy; posInfo.Spec2Pos(jj, 4) = rowH_s; + posInfo.Spec2Pos(jj+3, 2) = yy; posInfo.Spec2Pos(jj+3, 4) = rowH_s; + end +else + for jj = 1:3 + posInfo.Spec2Pos(jj, 2) = stdRows(jj); posInfo.Spec2Pos(jj, 4) = stdRowH; + posInfo.Spec2Pos(jj+3, 2) = stdRows(jj); posInfo.Spec2Pos(jj+3, 4) = stdRowH; + end +end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% compute fft %%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 495a51c..9d8685b 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -17,6 +17,31 @@ ylab2={'roll';'pitch';'yaw'}; axesOptions = find([get(guiHandlesTune.plotR, 'Value') get(guiHandlesTune.plotP, 'Value') get(guiHandlesTune.plotY, 'Value')]); + +% scale row heights to fill space when fewer than 3 RPY axes +nActiveTune = numel(axesOptions); +stdRows_t = [0.69 0.395 0.1]; stdRowH_t = 0.245; +if nActiveTune > 0 && nActiveTune < 3 && ~get(guiHandlesTune.RPYcombo, 'Value') + topY_t = stdRows_t(1) + stdRowH_t; botY_t = stdRows_t(3); gapT = 0.05; + rowH_t = (topY_t - botY_t - (nActiveTune-1)*gapT) / nActiveTune; + ci = 0; + for jj = axesOptions + ci = ci + 1; + yy = topY_t - ci*rowH_t - (ci-1)*gapT; + for cc = 0:3 + posInfo.TparamsPos(jj + cc*3, 2) = yy; + posInfo.TparamsPos(jj + cc*3, 4) = rowH_t; + end + end +else + for jj = 1:3 + for cc = 0:3 + posInfo.TparamsPos(jj + cc*3, 2) = stdRows_t(jj); + posInfo.TparamsPos(jj + cc*3, 4) = stdRowH_t; + end + end +end + lineStyle = {'-' ; '--' ; ':'}; lnLabels = {'solid' ; 'dashed'; 'dotted'}; p = {' P, I, D, Dm, F'; ' P, I, D, Dm, F'; ' P, I, D, cD'; ... diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 84e7481..f266f69 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -99,14 +99,14 @@ set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotP, 'Value', 1); guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotY, 'Value', 0); guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... @@ -118,7 +118,7 @@ set(guiHandlesTune.Ycorrection, 'Value', 0); guiHandlesTune.RPYcombo =uicontrol(PStunefig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'TooltipString', ['Plot RPY in same panel '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.RPYcombo, 'Value', 0); guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 0333a3e..976e1e3 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -238,6 +238,9 @@ 'GR',guiHandles.overlayGR, 'GP',guiHandles.overlayGP, 'GY',guiHandles.overlayGY)); end % ishandle(checkpanel) +% always update file list (fnameMaster may have grown since last call) +set(guiHandles.FileNum, 'string', fnameMaster); + fileIdx = get(guiHandles.FileNum, 'Value'); if exist('tta','var') && iscell(tta) && numel(tta) >= fileIdx if numel(epoch1_A) < fileIdx || numel(epoch2_A) < fileIdx diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m index 2b58337..0346142 100644 --- a/src/util/PSresizeCP.m +++ b/src/util/PSresizeCP.m @@ -230,11 +230,14 @@ function PSresizeCP(fig, ~) for k = 1:numel(rpyAxes), if ishandle(rpyAxes(k)), upperAxes(end+1) = rpyAxes(k); end; end for k = 1:numel(comboAx), if ishandle(comboAx(k)), upperAxes(end+1) = comboAx(k); end; end + nUpper = numel(upperAxes); if ~isempty(motorAx) && ishandle(motorAx(1)) - set(motorAx(1), 'Position', [plotL 0.1 plotW linepos4H]); + if nUpper == 0 + set(motorAx(1), 'Position', [plotL 0.1 plotW plotTop - 0.1 - gapV]); + else + set(motorAx(1), 'Position', [plotL 0.1 plotW linepos4H]); + end end - - nUpper = numel(upperAxes); if nUpper > 0 upperBot = 0.1 + linepos4H + gapV; upperH = (plotTop - upperBot - max(0,nUpper-1)*gapV) / max(1,nUpper); From 4db149e795a6d7eb8bd7d8c33007c7942da147c0 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 15 Mar 2026 20:03:59 +0100 Subject: [PATCH 40/80] feat(ui): Test Signal Config, PStheme colors, Filter Sim/Test Signal no-log guard --- PIDscope.m | 37 ++-- src/compat/PSdatatip_click.m | 5 +- src/core/PSbfFilters.m | 2 +- src/plot/PSdynSpecPlayer.m | 8 +- src/plot/PSfilterSim.m | 80 ++----- src/plot/PSplotBode.m | 24 +-- src/plot/PSplotLogViewer.m | 4 +- src/plot/PSplotMotorNoise.m | 4 +- src/plot/PSplotSpec2D.m | 45 ++-- src/ui/PSTestSignalConfig.m | 381 +++++++++++++++++++++++++++++++++ src/ui/PSfreqTimeUIcontrol.m | 6 +- src/ui/PSsliderTool.m | 4 +- src/ui/PSspec2DUIcontrol.m | 20 +- src/ui/PSspecUIcontrol.m | 4 +- src/ui/PStuneUIcontrol.m | 9 +- src/ui/PSviewerUIcontrol.m | 15 +- src/util/PSparseFilterParams.m | 46 ++++ src/util/PStheme.m | 50 ++++- 18 files changed, 599 insertions(+), 145 deletions(-) create mode 100644 src/ui/PSTestSignalConfig.m create mode 100644 src/util/PSparseFilterParams.m diff --git a/PIDscope.m b/PIDscope.m index 5d04c4f..19eff6f 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -127,7 +127,6 @@ plotall_flag=-1; colorA = th.btnDash1; -colorA2=[.4 .0 .6]; colorB = th.btnDash2; colorC=[1 .2 .2]; colorD=[.1 .7 .2]; @@ -198,7 +197,8 @@ posInfo.spectrogramButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.TuningButton = [cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; posInfo.PIDsliderButton=[cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; -posInfo.filterSimButton=[cpL+cpM vPos-rs*row cpW-2*cpM rh]; row=row+1; +posInfo.filterSimButton=[cpL+cpM vPos-rs*row cpW/2-cpM rh]; +posInfo.testSignalButton=[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; posInfo.PIDErrorButton = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; posInfo.FlightStatsButton=[cpL+cpW/2 vPos-rs*row cpW/2-cpM rh]; row=row+1; posInfo.period2Hz = [cpL+cpM vPos-rs*row cpW/2-cpM rh]; @@ -266,13 +266,13 @@ 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end'); guiHandles.plotR =uicontrol(PSfig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.plotP =uicontrol(PSfig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisPitch,'Position',[posInfo.plotP_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.plotY =uicontrol(PSfig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisYaw,'Position',[posInfo.plotY_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.RPYcomboLV=uicontrol(PSfig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.RPYcomboLV],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); @@ -304,25 +304,33 @@ 'catch,' ... 'PSsliderTool();' ... 'end']); -set(guiHandles.PIDsliderButton, 'ForegroundColor', [.40 .80 1.0]); +set(guiHandles.PIDsliderButton, 'ForegroundColor', th.btnDash3); guiHandles.filterSimButton = uicontrol(PSfig,'string','Filter Simulator','fontsize',fontsz,... 'TooltipString','Simulate BF filter chain (theoretical response)','units','normalized',... 'Position',[posInfo.filterSimButton],... - 'callback',['try,' ... - 'tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... + 'callback',['if ~exist(''A_lograte'',''var''),warndlg(''Please select file(s)'');' ... + 'else,tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... 'PSfilterSim([],1000*A_lograte(tmpFcnt),SetupInfo{tmpFcnt});' ... - 'clear tmpFcnt;' ... - 'catch e,warndlg([''Filter Simulator: '' e.message]),end']); -set(guiHandles.filterSimButton, 'ForegroundColor', [.85 .55 .15]); + 'clear tmpFcnt;end']); +set(guiHandles.filterSimButton, 'ForegroundColor', th.btnDash4); + +guiHandles.testSignalButton = uicontrol(PSfig,'string','Test Signal','fontsize',fontsz,... + 'TooltipString','Configure and apply offline filter chain to log data','units','normalized',... + 'Position',[posInfo.testSignalButton],... + 'callback',['if ~exist(''T'',''var''),warndlg(''Please select file(s)'');' ... + 'else,tmpFcnt=get(guiHandles.FileNum,''Value'');tmpFcnt=tmpFcnt(1);' ... + 'PSTestSignalConfig(PSfig,T,Nfiles,A_lograte,SetupInfo,guiHandles,tIND);' ... + 'clear tmpFcnt;end']); +set(guiHandles.testSignalButton, 'ForegroundColor', th.btnDash5); guiHandles.PIDErrorButton = uicontrol(PSfig,'string','PID Error','fontsize',fontsz,'TooltipString', ['PID error distribution analysis'],'units','normalized','Position',[posInfo.PIDErrorButton],... 'callback','PSerrUIcontrol; PSplotPIDerror;'); -set(guiHandles.PIDErrorButton, 'ForegroundColor', [.8 .4 .1]); +set(guiHandles.PIDErrorButton, 'ForegroundColor', th.btnDash6); guiHandles.FlightStatsButton = uicontrol(PSfig,'string','Flight Stats','fontsize',fontsz,'TooltipString', ['Flight statistics and stick analysis'],'units','normalized','Position',[posInfo.FlightStatsButton],... 'callback','PSstatsUIcontrol; PSplotStats;'); -set(guiHandles.FlightStatsButton, 'ForegroundColor', [.2 .6 .8]); +set(guiHandles.FlightStatsButton, 'ForegroundColor', th.btnDash7); guiHandles.period2Hz = uicontrol(PSfig,'string','Period','fontsize',fontsz,'TooltipString', ['Calculates peak to peak in Hz similar to the BBE ''Mark'' tool' , newline, 'press button, position mouse over 1st peak, mouse click,' , newline, 'then position over 2nd peak, then mouse click again'], 'units','normalized','Position',[posInfo.period2Hz],... 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.period2Hz, ''Value''), try, [x1 y1] = ginput(1); figure(PSfig); h=plot([x1 x1],[-(maxY*2) maxY],''-r'');set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); [x2 y2] = ginput(1); h=plot([x2 x2],[-(maxY*2) maxY],''-r''); set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); plot([x1 x2],[y1 y2],'':k''); x3=[round(x1*1000) round(x2*1000)]; f = 1000/(x3(2)-x3(1)); text(x2, y2, [num2str(x3(2)-x3(1)) ''ms, '' num2str(f) ''Hz''],''FontSize'',fontsz, ''FontWeight'', ''Bold''); catch, end, end'); @@ -432,7 +440,8 @@ try set(guiHandles.linewidth, 'Value', defaults.Values(find(strcmp(defaults.Para cpItems{end+1} = struct('h', guiHandles.spectrogramButton, 'type','full', 'row',7, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.TuningButton, 'type','full', 'row',8, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.PIDsliderButton, 'type','full', 'row',9, 'col',0, 'nrows',0); -cpItems{end+1} = struct('h', guiHandles.filterSimButton, 'type','full', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.filterSimButton, 'type','left', 'row',10, 'col',0, 'nrows',0); +cpItems{end+1} = struct('h', guiHandles.testSignalButton, 'type','right', 'row',10, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.PIDErrorButton, 'type','left', 'row',11, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.FlightStatsButton, 'type','right', 'row',11, 'col',0, 'nrows',0); cpItems{end+1} = struct('h', guiHandles.period2Hz, 'type','left', 'row',12, 'col',0, 'nrows',0); diff --git a/src/compat/PSdatatip_click.m b/src/compat/PSdatatip_click.m index c1c5f89..0525a1c 100644 --- a/src/compat/PSdatatip_click.m +++ b/src/compat/PSdatatip_click.m @@ -108,7 +108,8 @@ function PSdatatip_click(ax) if isempty(obj_type), return; end % Format output text and annotation position - fontsz = 12; + th = PStheme(); + fontsz = th.fontsz; txt = {}; ann_x = x; ann_y = y; @@ -201,7 +202,7 @@ function PSdatatip_click(ax) % Show annotation text(ann_x, ann_y, txt, 'Parent', ax, 'Tag', 'PSdatatip', ... - 'BackgroundColor', [1 1 0.88], 'EdgeColor', [0.3 0.3 0.3], ... + 'BackgroundColor', th.datatipBg, 'EdgeColor', th.gridColor, ... 'FontSize', fontsz, 'FontWeight', 'bold', ... 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'left', ... 'Margin', 4, 'Clipping', 'on'); diff --git a/src/core/PSbfFilters.m b/src/core/PSbfFilters.m index a67fa53..89fa2cd 100644 --- a/src/core/PSbfFilters.m +++ b/src/core/PSbfFilters.m @@ -1,6 +1,6 @@ function [b, a] = PSbfFilters(type, fc, Fs, Q) %% PSbfFilters - BF-compatible discrete filter coefficients -% type: 'pt1','pt2','pt3','biquad','notch' +% type: 'pt1','pt2','pt3','biquad','biquad-bessel','notch' % fc: cutoff/center freq (Hz) % Fs: sample rate (Hz) % Q: quality factor (notch only, default 3.5 = BF dyn_notch_q/100) diff --git a/src/plot/PSdynSpecPlayer.m b/src/plot/PSdynSpecPlayer.m index ac1bca2..6458a73 100644 --- a/src/plot/PSdynSpecPlayer.m +++ b/src/plot/PSdynSpecPlayer.m @@ -42,7 +42,7 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) 'Color', thm.figBg, 'Position', round([0 0 screensz(3) screensz(4)])); try set(fig, 'WindowState', 'maximized'); catch, end -axColors = {[0 .85 .85], [.85 .85 0], [.85 .3 .85]}; +axColors = {thm.axisRoll, thm.axisPitch, thm.axisYaw}; % layout: nValid spectrum rows + 1 spectrogram row at bottom + transport bar specH = 0.58 / nValid; @@ -105,17 +105,17 @@ function PSdynSpecPlayer(specMatCell, Tm, F, axisLabels, signalName) btnW = .07; btnH = .04; btnY = .02; uicontrol(fig, 'Style', 'pushbutton', 'String', 'Play', ... 'Units', 'normalized', 'Position', [.07 btnY btnW btnH], ... - 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [0 .6 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', thm.btnPlay, ... 'Callback', @playCallback); uicontrol(fig, 'Style', 'pushbutton', 'String', 'Pause', ... 'Units', 'normalized', 'Position', [.07+btnW+.01 btnY btnW btnH], ... - 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [.8 .2 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', thm.btnPause, ... 'Callback', @pauseCallback); uicontrol(fig, 'Style', 'pushbutton', 'String', 'Stop', ... 'Units', 'normalized', 'Position', [.07+2*(btnW+.01) btnY btnW btnH], ... - 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', [.7 0 0], ... + 'FontSize', fontsz, 'FontWeight', 'bold', 'ForegroundColor', thm.btnStop, ... 'Callback', @stopCallback); uicontrol(fig, 'Style', 'text', 'String', 'Speed:', ... diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 341f648..d5d32a1 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -12,7 +12,7 @@ function PSfilterSim(~, Fs, setupInfo) 'Position', round([0 0 screensz(3) screensz(4)])); try set(fig, 'WindowState', 'maximized'); catch, end -fp = parseFilterParams(setupInfo); +fp = PSparseFilterParams(setupInfo); % Layout: 2 cols x 4 rows + gradient bars plotL = 0.05; @@ -52,11 +52,11 @@ function PSfilterSim(~, Fs, setupInfo) hTitleL = uicontrol(fig, 'Style', 'text', 'String', 'LOWPASS FILTERS', ... 'Units', 'normalized', 'Position', [colL(1) titleY colW titleH], ... 'FontSize', fontsz+1, 'FontWeight', 'bold', ... - 'BackgroundColor', thm.figBg, 'ForegroundColor', [.4 .9 1]); + 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.textAccent); hTitleN = uicontrol(fig, 'Style', 'text', 'String', 'NOTCH FILTERS', ... 'Units', 'normalized', 'Position', [colL(2) titleY colW titleH], ... 'FontSize', fontsz+1, 'FontWeight', 'bold', ... - 'BackgroundColor', thm.figBg, 'ForegroundColor', [1 .6 .3]); + 'BackgroundColor', thm.figBg, 'ForegroundColor', thm.secNotch); % Control panel cpL = .76; cpW = .23; @@ -92,36 +92,36 @@ function PSfilterSim(~, Fs, setupInfo) 'Units', 'normalized', 'Position', [x0+halfW+.04 row halfW-.04 rh], 'FontSize', fontsz, 'Callback', cb); row = row - rh - gap*2; -mkSection(fig, 'Gyro LPF1', cpL, row, cpW, rh, bgc, [.4 .9 1], fontsz-1); +mkSection(fig, 'Gyro LPF1', cpL, row, cpW, rh, bgc, thm.textAccent, fontsz-1); row = row - rh - gap; [h.glpf1_type, h.glpf1_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.gyro_lpf1_type, fp.gyro_lpf1_hz, cb, fontsz); -mkSection(fig, 'Gyro LPF2', cpL, row, cpW, rh, bgc, [.4 .9 1], fontsz-1); +mkSection(fig, 'Gyro LPF2', cpL, row, cpW, rh, bgc, thm.textAccent, fontsz-1); row = row - rh - gap; [h.glpf2_type, h.glpf2_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.gyro_lpf2_type, fp.gyro_lpf2_hz, cb, fontsz); -mkSection(fig, 'Gyro Notch 1', cpL, row, cpW, rh, bgc, [1 .7 .3], fontsz-1); +mkSection(fig, 'Gyro Notch 1', cpL, row, cpW, rh, bgc, thm.secNotch, fontsz-1); row = row - rh - gap; [h.gn1_hz, h.gn1_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.gyro_notch1_hz, fp.gyro_notch1_cut, cb, fontsz); -mkSection(fig, 'Gyro Notch 2', cpL, row, cpW, rh, bgc, [1 .7 .3], fontsz-1); +mkSection(fig, 'Gyro Notch 2', cpL, row, cpW, rh, bgc, thm.secNotch, fontsz-1); row = row - rh - gap; [h.gn2_hz, h.gn2_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.gyro_notch2_hz, fp.gyro_notch2_cut, cb, fontsz); -mkSection(fig, 'D-term LPF1', cpL, row, cpW, rh, bgc, [.4 1 .4], fontsz-1); +mkSection(fig, 'D-term LPF1', cpL, row, cpW, rh, bgc, thm.secDtermLPF, fontsz-1); row = row - rh - gap; [h.dlpf1_type, h.dlpf1_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.dterm_lpf1_type, fp.dterm_lpf1_hz, cb, fontsz); -mkSection(fig, 'D-term LPF2', cpL, row, cpW, rh, bgc, [.4 1 .4], fontsz-1); +mkSection(fig, 'D-term LPF2', cpL, row, cpW, rh, bgc, thm.secDtermLPF, fontsz-1); row = row - rh - gap; [h.dlpf2_type, h.dlpf2_hz, row] = mkTypeHz(fig, x0, row, rh, gap, cW, ibc, ifc, lc, fp.dterm_lpf2_type, fp.dterm_lpf2_hz, cb, fontsz); -mkSection(fig, 'D-term Notch', cpL, row, cpW, rh, bgc, [1 .5 .5], fontsz-1); +mkSection(fig, 'D-term Notch', cpL, row, cpW, rh, bgc, thm.secDtermNotch, fontsz-1); row = row - rh - gap; [h.dn_hz, h.dn_cut, row] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, fp.dterm_notch_hz, fp.dterm_notch_cut, cb, fontsz); % RPM: Hz + #Harm + Q on one line -mkSection(fig, 'RPM Filter Sim', cpL, row, cpW, rh, bgc, [1 .4 .4], fontsz-1); +mkSection(fig, 'RPM Filter Sim', cpL, row, cpW, rh, bgc, thm.btnDash1, fontsz-1); row = row - rh - gap; thirdW = cW / 3; uicontrol(fig, 'Style', 'text', 'String', 'Hz:', ... @@ -145,7 +145,7 @@ function PSfilterSim(~, Fs, setupInfo) row = row - rh - gap; % Test signal: Lo + Hi + Dur on one line -mkSection(fig, 'Test Signal', cpL, row, cpW, rh, bgc, [.8 .6 .2], fontsz-1); +mkSection(fig, 'Test Signal', cpL, row, cpW, rh, bgc, thm.btnDash5, fontsz-1); row = row - rh - gap; uicontrol(fig, 'Style', 'text', 'String', 'Lo:', ... 'Units', 'normalized', 'Position', [x0 row .025 rh], ... @@ -168,7 +168,7 @@ function PSfilterSim(~, Fs, setupInfo) row = row - rh - gap; % Options -mkSection(fig, 'Options', cpL, row, cpW, rh, bgc, [.7 .7 .7], fontsz-1); +mkSection(fig, 'Options', cpL, row, cpW, rh, bgc, thm.btnSave, fontsz-1); row = row - rh - gap; h.magdB = uicontrol(fig, 'Style', 'checkbox', 'String', 'Magnitude dB', 'Value', 0, ... 'Units', 'normalized', 'Position', [x0 row halfW rh], ... @@ -204,7 +204,7 @@ function PSfilterSim(~, Fs, setupInfo) h.totalDelay = uicontrol(fig, 'Style', 'text', 'String', '', ... 'Units', 'normalized', 'Position', [x0 row cW rh], ... 'FontSize', fontsz, 'FontWeight', 'bold', ... - 'BackgroundColor', bgc, 'ForegroundColor', [.9 .2 .2], 'HorizontalAlignment', 'left'); + 'BackgroundColor', bgc, 'ForegroundColor', thm.btnDash1, 'HorizontalAlignment', 'left'); doUpdate(); @@ -519,9 +519,9 @@ function doUpdate() end fLo = xlimF(useLog, fMax); if usedB - line(axLmag, [fLo(1) fMax], [-3 -3], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + line(axLmag, [fLo(1) fMax], [-3 -3], 'Color', thm.refLine3dB, 'LineStyle', ':', 'LineWidth', 0.5); else - line(axLmag, [fLo(1) fMax], [0.707 0.707], 'Color', [.6 .6 .2], 'LineStyle', ':', 'LineWidth', 0.5); + line(axLmag, [fLo(1) fMax], [0.707 0.707], 'Color', thm.refLine3dB, 'LineStyle', ':', 'LineWidth', 0.5); end hold(axLmag, 'off'); PSstyleAxes(axLmag, thm); @@ -859,50 +859,7 @@ function annotateNotch(ax, gn1f, gn2f, dnf, rpmBase, rpmNharm, rpmCols, colStati end end -function fp = parseFilterParams(si) - % BF enum is same across all versions: 0=PT1, 1=BIQUAD, 2=PT2, 3=PT3 - % Filter disabled when hz == 0, NOT by type value - % Header key names differ: BF 4.3+ uses gyro_lpf1_*, BF 4.2 uses gyro_lowpass_* - fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', hval(si, 'gyro_lowpass_type', 0)); - fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', 0)); - fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', hval(si, 'gyro_lowpass2_type', 0)); - fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', 0)); - fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', hval(si, 'dterm_lowpass_type', 0)); - fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 0)); - fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', hval(si, 'dterm_lowpass2_type', 0)); - fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); - fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); - fp.dterm_notch_cut = hval(si, 'dterm_notch_cutoff', 0); - tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2double(strsplit(tmp, ',')); - if any(isnan(v)), v = [0 0]; end - fp.gyro_notch1_hz = v(1); - fp.gyro_notch2_hz = 0; if numel(v) > 1, fp.gyro_notch2_hz = v(2); end - tmp = hstr(si, 'gyro_notch_cutoff', '0,0'); v = str2double(strsplit(tmp, ',')); - if any(isnan(v)), v = [0 0]; end - fp.gyro_notch1_cut = v(1); - fp.gyro_notch2_cut = 0; if numel(v) > 1, fp.gyro_notch2_cut = v(2); end -end - -function v = hval(si, key, default) - v = default; - for k = 1:size(si, 1) - if strcmp(strtrim(si{k,1}), key) - tmp = str2double(strtrim(si{k,2})); - if ~isnan(tmp), v = tmp; end - return; - end - end -end - -function s = hstr(si, key, default) - s = default; - for k = 1:size(si, 1) - if strcmp(strtrim(si{k,1}), key) - s = strtrim(si{k,2}); - return; - end - end -end +% parseFilterParams/hval/hstr moved to src/util/PSparseFilterParams.m function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) uicontrol(fig, 'Style', 'text', 'String', txt, ... @@ -914,6 +871,7 @@ function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) if initHz == 0, ddVal = 1; else ddVal = min(initType + 2, 5); end ddW = cW * 0.48; edW = cW * 0.35; lblW = cW * 0.15; + th_ = PStheme(); hType = uicontrol(fig, 'Style', 'popupmenu', ... 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', ddVal, ... 'Units', 'normalized', 'Position', [x0 row ddW rh], 'FontSize', fsz, 'Callback', cb); @@ -923,7 +881,7 @@ function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) 'HorizontalAlignment', 'center', 'Callback', cb); uicontrol(fig, 'Style', 'text', 'String', 'Hz', ... 'Units', 'normalized', 'Position', [x0+ddW+edW+.008 row lblW rh], ... - 'FontSize', fsz-1, 'BackgroundColor', [.22 .22 .24], ... + 'FontSize', fsz-1, 'BackgroundColor', th_.panelBg, ... 'ForegroundColor', lc, 'HorizontalAlignment', 'left'); rowOut = row - rh - gap; end diff --git a/src/plot/PSplotBode.m b/src/plot/PSplotBode.m index af7d3bd..cde6543 100644 --- a/src/plot/PSplotBode.m +++ b/src/plot/PSplotBode.m @@ -24,15 +24,15 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) % --- magnitude --- ax1 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.08 .72 .55 .22]); mag_T = 20*log10(abs(G_track(freq > 0))); -semilogx(ax1, fPlot, mag_T, 'Color', [0 .8 1], 'LineWidth', 1.5); +semilogx(ax1, fPlot, mag_T, 'Color', th.bodeMain, 'LineWidth', 1.5); hold(ax1, 'on'); if ~isempty(G_plant) mag_P = 20*log10(abs(G_plant(freq > 0))); - semilogx(ax1, fPlot, mag_P, 'Color', [1 .5 0], 'LineWidth', 1.2); + semilogx(ax1, fPlot, mag_P, 'Color', th.bodeSecondary, 'LineWidth', 1.2); h_leg = legend(ax1, {'Tracking (T)', 'Plant (P)'}, 'Location', 'southwest'); try PSstyleLegend(h_leg, th); catch, end end -line(ax1, [fPlot(1) fPlot(end)], [0 0], 'Color', [.5 .5 .5], 'LineStyle', '--'); +line(ax1, [fPlot(1) fPlot(end)], [0 0], 'Color', th.bodeRef, 'LineStyle', '--'); hold(ax1, 'off'); PSstyleAxes(ax1, th); set(get(ax1, 'YLabel'), 'String', 'Magnitude (dB)'); @@ -41,22 +41,22 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) % --- phase --- ax2 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.08 .42 .55 .22]); phase_T = unwrap(angle(G_track(freq > 0))) * 180/pi; -semilogx(ax2, fPlot, phase_T, 'Color', [0 .8 1], 'LineWidth', 1.5); +semilogx(ax2, fPlot, phase_T, 'Color', th.bodeMain, 'LineWidth', 1.5); hold(ax2, 'on'); if ~isempty(G_plant) phase_P = unwrap(angle(G_plant(freq > 0))) * 180/pi; - semilogx(ax2, fPlot, phase_P, 'Color', [1 .5 0], 'LineWidth', 1.2); + semilogx(ax2, fPlot, phase_P, 'Color', th.bodeSecondary, 'LineWidth', 1.2); end -line(ax2, [fPlot(1) fPlot(end)], [-180 -180], 'Color', [.8 .3 .3], 'LineStyle', '--'); +line(ax2, [fPlot(1) fPlot(end)], [-180 -180], 'Color', th.btnDash1, 'LineStyle', '--'); hold(ax2, 'off'); PSstyleAxes(ax2, th); set(get(ax2, 'YLabel'), 'String', 'Phase (deg)'); % --- coherence --- ax3 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.08 .08 .55 .26]); -semilogx(ax3, fPlot, C(freq > 0), 'Color', [.3 .9 .3], 'LineWidth', 1.2); +semilogx(ax3, fPlot, C(freq > 0), 'Color', th.bodeCoherence, 'LineWidth', 1.2); hold(ax3, 'on'); -line(ax3, [fPlot(1) fPlot(end)], [.8 .8], 'Color', [.5 .5 .5], 'LineStyle', '--'); +line(ax3, [fPlot(1) fPlot(end)], [.8 .8], 'Color', th.bodeRef, 'LineStyle', '--'); hold(ax3, 'off'); PSstyleAxes(ax3, th); set(ax3, 'YLim', [0 1.05]); @@ -71,9 +71,9 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) % --- step response (right side) --- ax4 = axes('Parent', fig, 'Units', 'normalized', 'Position', [.72 .42 .24 .52]); if ~isempty(stepData) && isfield(stepData, 't_ms') && isfield(stepData, 'step') - plot(ax4, stepData.t_ms, stepData.step, 'Color', [0 .8 1], 'LineWidth', 1.8); + plot(ax4, stepData.t_ms, stepData.step, 'Color', th.bodeMain, 'LineWidth', 1.8); hold(ax4, 'on'); - line(ax4, [0 max(stepData.t_ms)], [1 1], 'Color', [.5 .5 .5], 'LineStyle', '--'); + line(ax4, [0 max(stepData.t_ms)], [1 1], 'Color', th.bodeRef, 'LineStyle', '--'); % overshoot peak = max(stepData.step); if peak > 1.01 @@ -81,7 +81,7 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) [~, pk_idx] = max(stepData.step); plot(ax4, stepData.t_ms(pk_idx), peak, 'ro', 'MarkerSize', 8, 'LineWidth', 2); text(stepData.t_ms(pk_idx)+5, peak, sprintf('%.0f%%', os_pct), ... - 'Color', [1 .3 .3], 'FontSize', fontsz, 'FontWeight', 'bold', 'Parent', ax4); + 'Color', th.btnDash1, 'FontSize', fontsz, 'FontWeight', 'bold', 'Parent', ax4); end hold(ax4, 'off'); end @@ -109,7 +109,7 @@ function PSplotBode(freq, G_track, G_plant, C, stepData, titleStr) infoLines{end+1} = sprintf('Settling (2%%): %.0f ms', stepData.t_ms(settled)); end end -text(0.05, 0.9, infoLines, 'Parent', ax5, 'Color', th.textAccent, ... +text(0.05, 0.9, infoLines, 'Parent', ax5, 'Color', th.bodeMain, ... 'FontSize', fontsz, 'FontWeight', 'bold', 'VerticalAlignment', 'top', ... 'Units', 'normalized'); diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index c4387dc..d125523 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -43,6 +43,7 @@ set(guiHandles.checkbox12, 'Value', allVal); set(guiHandles.checkbox13, 'Value', allVal); set(guiHandles.checkbox14, 'Value', allVal); + set(guiHandles.checkboxTS, 'Value', allVal); end plotall_flag=-1; @@ -214,8 +215,9 @@ if get(guiHandles.checkbox7, 'Value'), hch8=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['setpoint_' int2str(ii-1) '_'], sFactor));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end if get(guiHandles.checkbox8, 'Value'), hch9=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['pidsum_' int2str(ii-1) '_'], sFactor));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end if get(guiHandles.checkbox9, 'Value'), hch10=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['piderr_' int2str(ii-1) '_'], sFactor));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkboxTS, 'Value') && isfield(T{fileIdx}, ['testSignal_' int2str(ii-1) '_']), try hchTS=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['testSignal_' int2str(ii-1) '_'], sFactor));hold on;set(hchTS,'color', th.sigTestSignal,'LineWidth',lwVal,'linestyle','--'), catch, end, end + - h=fill([0,t1,t1,0],[-maxY,-maxY,maxY,maxY],th.epochFill); set(h,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill); h=fill([t2,xmax,xmax,t2],[-maxY,-maxY,maxY,maxY],th.epochFill); diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index 87b28cd..b4266c1 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -102,7 +102,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) % --- TOP RIGHT: Gyro PSD --- axGyro = axes('Parent', fig, 'Units', 'normalized', 'Position', [.56 .70 .38 .22]); -gCols = {[1 .4 .4], [.4 1 .4], [.4 .6 1]}; +gCols = {th.axisRoll, th.axisPitch, th.axisYaw}; for g = 1:3 plot(axGyro, gyroFreqV{g}, gyroSpec{g}, 'Color', gCols{g}, 'LineWidth', 1.1); hold(axGyro, 'on'); @@ -159,7 +159,7 @@ function PSplotMotorNoise(T, f, tIND, Fs) plot(axTime, t, motorData{m}, 'Color', mCol{m}, 'LineWidth', 0.5); hold(axTime, 'on'); end -plot(axTime, t, throttle, 'Color', [.9 .9 .9], 'LineWidth', 1.5, 'LineStyle', '--'); +plot(axTime, t, throttle, 'Color', th.sigThrottle, 'LineWidth', 1.5, 'LineStyle', '--'); hold(axTime, 'off'); PSstyleAxes(axTime, th); set(axTime, 'XLim', [0 max(t)]); set(axTime, 'YLim', [0 100]); diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 3d651d1..079d48f 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -18,6 +18,7 @@ %% s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; +if isfield(T{1}, 'testSignal_0_'), s1{end+1} = 'testSignal'; end datSelectionString=[s1]; axesOptionsSpec = find([get(guiHandlesSpec2.plotR, 'Value') get(guiHandlesSpec2.plotP, 'Value') get(guiHandlesSpec2.plotY, 'Value')]); @@ -171,6 +172,9 @@ smat{p}=[];%string amp2d2{p}=[];%spec 2d freq2d2{p}=[];% freq2d2 + elseif ~isfield(T{tmpFileVal(f)}, [s '_' int2str(a-1) '_']) + p=p+1; + smat{p}=[]; amp2d2{p}=[]; freq2d2{p}=[]; else p = p + 1; fld = [s '_' int2str(a-1) '_']; @@ -192,7 +196,6 @@ end try close(hw_fft); catch, end - figure(PSspecfig2); baselineYlines = [0 -50]; multilineStyle = {'-' ; ':'; '--'}; @@ -209,10 +212,13 @@ tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); tmpSmoothVal = get(guiHandlesSpec2.smoothFactor_select, 'Value'); for k = 1 : length(tmpSpecVal) - s = char(datSelectionString(k)); + s = char(datSelectionString(tmpSpecVal(k))); for f = 1 : size(tmpFileVal,2) + lineCol = multiLineCols(f,:); + isTS = strcmp(s, 'testSignal'); + if isTS, lineCol = th.sigTestSignal; end cnt = 0; - for a = axesOptionsSpec + for a = axesOptionsSpec cnt = cnt + 1; p = p + 1; if ~isempty(freq2d2) @@ -227,12 +233,13 @@ ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) + lsty = multilineStyle{k}; if isTS, lsty = '-'; end + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',lsty) set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) + set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0:m/10:m], 'yminortick','on') - axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) xlabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary); if get(guiHandlesSpec2.checkboxPSD, 'Value') ylabel(['Power Spectral Density (dB)'],'fontweight','bold','Color',th.textPrimary); @@ -255,9 +262,10 @@ ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',multilineStyle{k}) + lsty = multilineStyle{k}; if isTS, lsty = '-'; end + set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',lsty) set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) + set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0 20 40 60 80 100],'yminortick','on') axis([0 100 climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) @@ -287,32 +295,32 @@ if get(guiHandlesSpec2.Delay, 'Value') == 1 && a == 1 if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' Debug01{tmpFileIdx} 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); else h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Filter: ' 'ms | Dterm Filter: ' FilterDelayDterm{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); end end if get(guiHandlesSpec2.Delay, 'Value') == 2 h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP-Gyro: ' int2str(SPGyroDelay(tmpFileIdx, a)) 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); end if get(guiHandlesSpec2.Delay, 'Value') == 3 && a == 1 if debugmode(tmpFileIdx) == tmpDbgIdx.RC_INTERPOLATION || debugmode(tmpFileIdx) == tmpDbgIdx.FEEDFORWARD h=text(75, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['SP smoothing delay: ' Debug02{tmpFileIdx} 'ms']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); else h=text(80, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['debug mode not set ']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); end end if get(guiHandlesSpec2.Delay, 'Value') == 4 && a == 1 if debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_SCALED || debugmode(tmpFileIdx) == tmpDbgIdx.GYRO_FILTERED || debugmode(tmpFileIdx) == 1 || debugmode(tmpFileIdx) == 0 h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' num2str(gyro_phase_shift_deg(tmpFileIdx)) 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); else h=text(65, climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)-(f*4), ['Gyro Phase: ' 'deg | Dterm Phase: ' num2str(dterm_phase_shift_deg(tmpFileIdx)) 'deg']); - set(h,'Color',[multiLineCols(f,:)],'fontsize',fontsz); + set(h,'Color',[lineCol],'fontsize',fontsz); end end @@ -332,7 +340,7 @@ set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2.6,'linestyle',rpyLineStyle{cnt}) end set(h2,'fontsize',fontsz) - set(h,'Color',[multiLineCols(f,:)]) + set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0:m/10:m], 'yminortick','on') axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) @@ -366,11 +374,14 @@ tmpFileSelStr = get(guiHandlesSpec2.FileSelect, 'String'); tmpFileSelVal = get(guiHandlesSpec2.FileSelect, 'Value'); for m = 1 : length(tmpSpecListVal) + sLeg = char(datSelectionString(tmpSpecListVal(m))); for n = 1 : length(tmpFileSelVal) + fIdx_ = tmpFileSelVal(n); + if ~isfield(T{fIdx_}, [sLeg '_0_']), continue; end l = l + 1; clear fstr fltDelayStr fstr = char(tmpFileSelStr(tmpFileSelVal(n))); - if size(fstr,2) > 12, fstr = fstr(1,1:12); end % only use first 20 characters of file name + if size(fstr,2) > 12, fstr = fstr(1,1:12); end if get(guiHandlesSpec2.RPYcomboSpec, 'Value') == 0 legnd{l} = [char(tmpSpecListStr(tmpSpecListVal(m))) ' | ' fstr]; else diff --git a/src/ui/PSTestSignalConfig.m b/src/ui/PSTestSignalConfig.m new file mode 100644 index 0000000..39e2ae1 --- /dev/null +++ b/src/ui/PSTestSignalConfig.m @@ -0,0 +1,381 @@ +function PSTestSignalConfig(PSfig, T, Nfiles, A_lograte, SetupInfo, guiHandles, tIND) +%% PSTestSignalConfig - configure BF filter chain and apply to log data + +thm = PStheme(); +fontsz = thm.fontsz; +screensz = get(0, 'ScreenSize'); + +fig = findobj('Type', 'figure', 'Name', 'Test Signal Configuration'); +if ~isempty(fig), close(fig); end +fig = figure('Name', 'Test Signal Configuration', 'NumberTitle', 'off', ... + 'Color', thm.figBg, ... + 'Position', round([0 0 screensz(3) screensz(4)])); +try set(fig, 'WindowState', 'maximized'); catch, end + +fileIdx = 1; +try fileIdx = get(guiHandles.FileNum, 'Value'); fileIdx = fileIdx(1); catch, end +Fs = A_lograte(fileIdx) * 1000; + +% parse filter params from log headers +fp = struct(); +if iscell(SetupInfo) && numel(SetupInfo) >= fileIdx + fp = PSparseFilterParams(SetupInfo{fileIdx}); +else + fp.gyro_lpf1_type=0; fp.gyro_lpf1_hz=0; + fp.gyro_lpf2_type=0; fp.gyro_lpf2_hz=0; + fp.gyro_notch1_hz=0; fp.gyro_notch1_cut=0; + fp.gyro_notch2_hz=0; fp.gyro_notch2_cut=0; + fp.dterm_lpf1_type=0; fp.dterm_lpf1_hz=0; + fp.dterm_lpf2_type=0; fp.dterm_lpf2_hz=0; + fp.dterm_notch_hz=0; fp.dterm_notch_cut=0; +end + +% RPY colors: raw=solid, filtered=lighter +axCols = {thm.axisRoll, thm.axisPitch, thm.axisYaw}; +axColsFilt = {thm.axisRollFilt, thm.axisPitchFilt, thm.axisYawFilt}; +axNames = {'Roll','Pitch','Yaw'}; + +% layout: plots left 75%, controls right 25% +cpL = 0.76; cpW = 0.23; +plotL = 0.06; plotR_ = cpL - 0.02; +plotW = plotR_ - plotL; +bgc = thm.panelBg; fgc = thm.textPrimary; +ibc = thm.inputBg; ifc = thm.inputFg; +lc = thm.textSecondary; + +uipanel('Parent', fig, 'Title', '', ... + 'BackgroundColor', thm.panelBg, 'ForegroundColor', thm.panelFg, ... + 'HighlightColor', thm.panelBorder, ... + 'FontSize', fontsz, 'Position', [cpL .02 cpW .96]); + +% axes: top = PSD spectrum (raw vs filtered), bottom = time domain +axSpec = axes('Parent', fig, 'Units', 'normalized', 'Position', [plotL 0.54 plotW 0.42]); +axTime = axes('Parent', fig, 'Units', 'normalized', 'Position', [plotL 0.06 plotW 0.42]); +PSstyleAxes(axSpec, thm); PSstyleAxes(axTime, thm); + +% control panel +x0 = cpL + 0.02; +cW = cpW - 0.04; +halfW = cW / 2; +rh = 0.026; gap = 0.004; +row = 0.94; +cb = @(~,~) updatePreview(); + +% source signal +uicontrol(fig, 'Style', 'text', 'String', 'Source:', ... + 'Units', 'normalized', 'Position', [x0 row .06 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +srcLabels = {'Gyro','P-term','D-term(prefilt)','D-term','Setpoint','PIDsum','PIDerror'}; +h.source = uicontrol(fig, 'Style', 'popupmenu', 'String', srcLabels, 'Value', 1, ... + 'Units', 'normalized', 'Position', [x0+.065 row cW-.065 rh], ... + 'FontSize', fontsz, 'Callback', cb); +row = row - rh - gap*2; + +mkSection('Gyro LPF1', thm.textAccent); +[h.glpf1_type, h.glpf1_hz, row] = mkTypeHz(fp.gyro_lpf1_type, fp.gyro_lpf1_hz); + +mkSection('Gyro LPF2', thm.textAccent); +[h.glpf2_type, h.glpf2_hz, row] = mkTypeHz(fp.gyro_lpf2_type, fp.gyro_lpf2_hz); + +mkSection('Gyro Notch 1', thm.secNotch); +[h.gn1_hz, h.gn1_cut, row] = mkNotchPair(fp.gyro_notch1_hz, fp.gyro_notch1_cut); + +mkSection('Gyro Notch 2', thm.secNotch); +[h.gn2_hz, h.gn2_cut, row] = mkNotchPair(fp.gyro_notch2_hz, fp.gyro_notch2_cut); + +mkSection('D-term LPF1', thm.secDtermLPF); +[h.dlpf1_type, h.dlpf1_hz, row] = mkTypeHz(fp.dterm_lpf1_type, fp.dterm_lpf1_hz); + +mkSection('D-term LPF2', thm.secDtermLPF); +[h.dlpf2_type, h.dlpf2_hz, row] = mkTypeHz(fp.dterm_lpf2_type, fp.dterm_lpf2_hz); + +mkSection('D-term Notch', thm.secDtermNotch); +[h.dn_hz, h.dn_cut, row] = mkNotchPair(fp.dterm_notch_hz, fp.dterm_notch_cut); + +row = row - gap*2; + +% R/P/Y checkboxes +chkW = cW / 3; +h.chkR = uicontrol(fig, 'Style', 'checkbox', 'String', 'R', 'Value', 1, ... + 'Units', 'normalized', 'Position', [x0 row chkW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', thm.axisRoll, ... + 'Callback', cb); +h.chkP = uicontrol(fig, 'Style', 'checkbox', 'String', 'P', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0+chkW row chkW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', thm.axisPitch, ... + 'Callback', cb); +h.chkY = uicontrol(fig, 'Style', 'checkbox', 'String', 'Y', 'Value', 0, ... + 'Units', 'normalized', 'Position', [x0+2*chkW row chkW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', thm.axisYaw, ... + 'Callback', cb); +row = row - rh - gap; + +% preview window +uicontrol(fig, 'Style', 'text', 'String', 'Win:', ... + 'Units', 'normalized', 'Position', [x0 row .04 rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); +h.winLen = uicontrol(fig, 'Style', 'popupmenu', 'String', {'0.5s','1s','2s','5s','Full'}, 'Value', 2, ... + 'Units', 'normalized', 'Position', [x0+.045 row cW-.045 rh], ... + 'FontSize', fontsz, 'Callback', cb); +row = row - rh - gap*3; + +% Apply button +h.applyBtn = uicontrol(fig, 'Style', 'pushbutton', 'String', 'Apply', ... + 'Units', 'normalized', 'Position', [x0 row cW rh*1.3], ... + 'FontSize', fontsz+1, 'FontWeight', 'bold', ... + 'BackgroundColor', thm.btnRun, 'ForegroundColor', [1 1 1], ... + 'Callback', @(~,~) applyToLogViewer()); +row = row - rh - gap; + +h.status = uicontrol(fig, 'Style', 'text', 'String', '', ... + 'Units', 'normalized', 'Position', [x0 row cW rh], ... + 'FontSize', fontsz-1, 'BackgroundColor', bgc, 'ForegroundColor', thm.btnRun, ... + 'HorizontalAlignment', 'center'); + +updatePreview(); + +%% nested: section header + function mkSection(txt, col) + uicontrol(fig, 'Style', 'text', 'String', txt, ... + 'Units', 'normalized', 'Position', [cpL+.01 row cpW-.02 rh], ... + 'FontSize', fontsz-1, 'FontWeight', 'bold', ... + 'BackgroundColor', bgc, 'ForegroundColor', col); + row = row - rh - gap; + end + +%% nested: type + hz + function [hType, hHz, r] = mkTypeHz(defType, defHz) + if defHz == 0, ddVal = 1; + else ddVal = min(defType + 2, 5); end + ddW = cW * 0.48; edW = cW * 0.35; lblW = cW * 0.15; + hType = uicontrol(fig, 'Style', 'popupmenu', ... + 'String', {'OFF', 'PT1', 'Biquad', 'PT2', 'PT3'}, 'Value', ddVal, ... + 'Units', 'normalized', 'Position', [x0 row ddW rh], 'FontSize', fontsz, 'Callback', cb); + hHz = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(defHz)), ... + 'Units', 'normalized', 'Position', [x0+ddW+.005 row edW rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); + uicontrol(fig, 'Style', 'text', 'String', 'Hz', ... + 'Units', 'normalized', 'Position', [x0+ddW+edW+.008 row lblW rh], ... + 'FontSize', fontsz-1, 'BackgroundColor', bgc, ... + 'ForegroundColor', lc, 'HorizontalAlignment', 'left'); + r = row - rh - gap; + end + +%% nested: notch pair + function [hHz, hCut, r] = mkNotchPair(defHz, defCut) + lblW = .03; edW = halfW - lblW - .005; + uicontrol(fig, 'Style', 'text', 'String', 'Ctr:', ... + 'Units', 'normalized', 'Position', [x0 row lblW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); + hHz = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(defHz)), ... + 'Units', 'normalized', 'Position', [x0+lblW+.005 row edW rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); + uicontrol(fig, 'Style', 'text', 'String', 'Cut:', ... + 'Units', 'normalized', 'Position', [x0+halfW row lblW rh], ... + 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); + hCut = uicontrol(fig, 'Style', 'edit', 'String', num2str(round(defCut)), ... + 'Units', 'normalized', 'Position', [x0+halfW+lblW+.005 row edW rh], ... + 'FontSize', fontsz, 'BackgroundColor', ibc, 'ForegroundColor', ifc, ... + 'HorizontalAlignment', 'center', 'Callback', cb); + r = row - rh - gap; + end + +%% nested: read edit field + function v = readEdit(hEdit) + v = str2double(get(hEdit, 'String')); + if isnan(v), v = 0; end + end + +%% nested: get source field name + function srcField = getSrcField() + srcIdx = get(h.source, 'Value'); + fields = {'gyroADC_','axisP_','axisDpf_','axisD_','setpoint_','pidsum_','piderr_'}; + srcField = fields{srcIdx}; + end + +%% nested: get full source signal for one axis + function [tFull, rawFull] = getFullData(axNum) + srcField = getSrcField(); + fname = [srcField int2str(axNum) '_']; + if ~isfield(T{fileIdx}, fname) + tFull = []; rawFull = []; return; + end + rawFull = T{fileIdx}.(fname); + N = numel(rawFull); + tFull = (0:N-1)' / Fs; + end + +%% nested: get windowed data slice + function [tSlice, rawSlice, i1, i2] = getPreviewSlice(axNum) + [tFull, rawFull] = getFullData(axNum); + if isempty(tFull), tSlice=[]; rawSlice=[]; i1=1; i2=1; return; end + N = numel(rawFull); + + winIdx = get(h.winLen, 'Value'); + winSecs = [0.5 1 2 5 inf]; + wSec = winSecs(winIdx); + + idx = tIND{fileIdx}; + if isempty(idx), idx = 1:N; end + midSample = round(mean([idx(1) idx(end)])); + if wSec >= tFull(end) + i1 = 1; i2 = N; + else + halfWin = round(wSec/2 * Fs); + i1 = max(1, midSample - halfWin); + i2 = min(N, midSample + halfWin); + end + tSlice = tFull(i1:i2); + rawSlice = rawFull(i1:i2); + end + +%% nested: apply filter chain to a signal vector + function out = applyChain(sig, sFs) + out = applyLPF(sig, get(h.glpf1_type,'Value')-1, readEdit(h.glpf1_hz), sFs); + out = applyLPF(out, get(h.glpf2_type,'Value')-1, readEdit(h.glpf2_hz), sFs); + out = applyNotch(out, readEdit(h.gn1_hz), readEdit(h.gn1_cut), sFs); + out = applyNotch(out, readEdit(h.gn2_hz), readEdit(h.gn2_cut), sFs); + srcIdx = get(h.source, 'Value'); + if srcIdx == 3 || srcIdx == 4 + out = applyLPF(out, get(h.dlpf1_type,'Value')-1, readEdit(h.dlpf1_hz), sFs); + out = applyLPF(out, get(h.dlpf2_type,'Value')-1, readEdit(h.dlpf2_hz), sFs); + out = applyNotch(out, readEdit(h.dn_hz), readEdit(h.dn_cut), sFs); + end + end + +%% nested: which axes are selected + function sel = getSelectedAxes() + sel = find([get(h.chkR,'Value') get(h.chkP,'Value') get(h.chkY,'Value')]); + end + +%% nested: update preview plots + function updatePreview() + if ~ishandle(fig), return; end + sel = getSelectedAxes(); + if isempty(sel), return; end + + fMax = min(Fs/2, 1000); + Nfft = 2^floor(log2(min(4096, numel(T{fileIdx}.loopIteration)))); + + cla(axSpec); hold(axSpec, 'on'); + cla(axTime); hold(axTime, 'on'); + legTxt = {}; + + for si = 1:numel(sel) + axNum = sel(si) - 1; + colRaw = axCols{sel(si)}; + colFilt = axColsFilt{sel(si)}; + + [~, rawFull] = getFullData(axNum); + if isempty(rawFull), continue; end + filtFull = applyChain(rawFull, Fs); + + % PSD + try + [pRaw, fPsd] = pwelch(rawFull, hanning(Nfft), round(Nfft/2), Nfft, Fs); + [pFilt, ~] = pwelch(filtFull, hanning(Nfft), round(Nfft/2), Nfft, Fs); + catch + win = hanning(Nfft); + nSeg = floor(numel(rawFull) / Nfft); + pRaw = zeros(Nfft/2+1, 1); pFilt = pRaw; + for ssi = 1:nSeg + sidx = (ssi-1)*Nfft + (1:Nfft); + segR = rawFull(sidx) .* win; + segF = filtFull(sidx) .* win; + fR = abs(fft(segR)).^2; fF = abs(fft(segF)).^2; + pRaw = pRaw + fR(1:Nfft/2+1); + pFilt = pFilt + fF(1:Nfft/2+1); + end + pRaw = pRaw / max(nSeg,1); + pFilt = pFilt / max(nSeg,1); + fPsd = linspace(0, Fs/2, Nfft/2+1)'; + end + + fIdx_ = fPsd <= fMax & fPsd > 0; + plot(axSpec, fPsd(fIdx_), 10*log10(pRaw(fIdx_)+1e-12), ... + 'Color', colRaw, 'LineWidth', 1.2); + plot(axSpec, fPsd(fIdx_), 10*log10(pFilt(fIdx_)+1e-12), ... + 'Color', colFilt, 'LineWidth', 1.2); + legTxt{end+1} = [axNames{sel(si)} ' raw']; + legTxt{end+1} = [axNames{sel(si)} ' filt']; + + % time domain + [tSlice, rawSlice] = getPreviewSlice(axNum); + if isempty(tSlice), continue; end + filtSlice = applyChain(rawSlice, Fs); + plot(axTime, tSlice, rawSlice, 'Color', colRaw, 'LineWidth', 0.8); + plot(axTime, tSlice, filtSlice, 'Color', colFilt, 'LineWidth', 1.2); + end + + hold(axSpec, 'off'); hold(axTime, 'off'); + PSstyleAxes(axSpec, thm); PSstyleAxes(axTime, thm); + + ttl = strjoin(axNames(sel), '/'); + title(axSpec, [ttl ' - Spectrum'], 'Color', thm.textPrimary, 'FontSize', fontsz); + xlabel(axSpec, 'Frequency (Hz)', 'Color', thm.textSecondary); + ylabel(axSpec, 'PSD (dB)', 'Color', thm.textSecondary); + set(axSpec, 'XLim', [0 fMax]); + legend(axSpec, legTxt, 'TextColor', thm.textPrimary, ... + 'Color', thm.panelBg, 'EdgeColor', thm.legendEdge, 'Location', 'northeast'); + + title(axTime, [ttl ' - Time Domain'], 'Color', thm.textPrimary, 'FontSize', fontsz); + xlabel(axTime, 'Time (s)', 'Color', thm.textSecondary); + ylabel(axTime, 'deg/s', 'Color', thm.textSecondary); + end + +%% nested: apply filter chain to all log data + function applyToLogViewer() + set(h.status, 'String', 'Applying...', 'ForegroundColor', thm.btnDash5); + drawnow; + srcField = getSrcField(); + for f = 1:Nfiles + fFs = A_lograte(f) * 1000; + for p = 0:2 + fname = [srcField int2str(p) '_']; + if isfield(T{f}, fname) + T{f}.(['testSignal_' int2str(p) '_']) = applyChain(T{f}.(fname), fFs); + else + T{f}.(['testSignal_' int2str(p) '_']) = zeros(size(T{f}.loopIteration)); + end + end + end + assignin('base', 'T', T); + set(h.status, 'String', 'Applied! Check Log Viewer & Spectral Analyzer.', 'ForegroundColor', thm.btnRun); + % add Test Signal to SA SpecList if not already there + try + sl = evalin('base', 'guiHandlesSpec2.SpecList'); + if ishandle(sl) + items = get(sl, 'String'); + if ~any(strcmp(items, 'Test Signal')) + items{end+1} = 'Test Signal'; + set(sl, 'String', items); + end + end + catch, end + try + if get(guiHandles.checkboxTS, 'Value') + evalin('base', 'PSplotLogViewer'); + end + catch, end + end + +end + +%% local filter helpers +function y = applyLPF(x, type, hz, Fs) + if type == 0 || hz == 0, y = x; return; end + types = {'pt1', 'biquad', 'pt2', 'pt3'}; + if type > numel(types), y = x; return; end + [b, a] = PSbfFilters(types{type}, hz, Fs); + y = filter(b, a, x); +end + +function y = applyNotch(x, center_hz, cutoff_hz, Fs) + if center_hz == 0, y = x; return; end + if cutoff_hz <= 0, cutoff_hz = center_hz * 0.7; end + Q = center_hz / (center_hz - cutoff_hz + 1); + [b, a] = PSbfFilters('notch', center_hz, Fs, Q); + y = filter(b, a, x); +end diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 52c2f3c..c8f36ee 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -161,12 +161,12 @@ guiHandlesSpec3.rpmDynNotch = uicontrol(PSspecfig3, 'Style','checkbox', 'String','Dyn Notch', ... 'fontsize', fontsz, 'Value', 0, ... - 'ForegroundColor', [0 .8 .8], 'BackgroundColor', bgcolor, ... + 'ForegroundColor', th.overlayDynNotch, 'BackgroundColor', bgcolor, ... 'units','normalized', 'Position', posInfo.rpmDynNotch, 'callback', rpmCb); guiHandlesSpec3.rpmEstChk = uicontrol(PSspecfig3, 'Style','checkbox', 'String','RPM est.', ... 'fontsize', fontsz, 'Value', 0, ... - 'ForegroundColor', [.6 .9 .6], 'BackgroundColor', bgcolor, ... + 'ForegroundColor', th.overlayRPM, 'BackgroundColor', bgcolor, ... 'units','normalized', 'Position', posInfo.rpmEstChk, 'callback', rpmCb); guiHandlesSpec3.playerBtn = uicontrol(PSspecfig3,'string','Player','fontsize',fontsz,... @@ -176,7 +176,7 @@ 'tmpSA3={''Gyro'',''Gyro prefilt'',''Dterm'',''Dterm prefilt'',''Pterm'',''PID error'',''Set point'',''PIDsum''};' ... 'PSdynSpecPlayer(specMat,Tm,F,{''Roll'',''Pitch'',''Yaw''},tmpSA3{get(guiHandlesSpec3.SpecList,''Value'')});' ... 'else,warndlg(''Run spectrogram first''),end']); -set(guiHandlesSpec3.playerBtn, 'ForegroundColor', [0 .4 .8]); +set(guiHandlesSpec3.playerBtn, 'ForegroundColor', th.btnPlayer); end % ishandle(Spec3Crtlpanel) % Register CP for fixed-pixel resize diff --git a/src/ui/PSsliderTool.m b/src/ui/PSsliderTool.m index 3722d0a..c92b0b0 100644 --- a/src/ui/PSsliderTool.m +++ b/src/ui/PSsliderTool.m @@ -56,7 +56,7 @@ function PSsliderTool(rollPID_str, pitchPID_str, yawPID_str) h.resetBtn = uicontrol(fig, 'Style', 'pushbutton', 'String', 'Reset', ... 'Position', [20 figH-50 70 30], ... 'FontSize', fontsz, 'FontWeight', 'bold', ... - 'BackgroundColor', [.85 .55 .15], 'ForegroundColor', [0 0 0], ... + 'BackgroundColor', thm.btnReset, 'ForegroundColor', [0 0 0], ... 'Callback', @cb_reset); h.fwDd = uicontrol(fig, 'Style', 'popupmenu', 'String', fwNames, ... @@ -111,7 +111,7 @@ function PSsliderTool(rollPID_str, pitchPID_str, yawPID_str) % Math mode description label h.mathDesc = uicontrol(fig, 'Style', 'text', 'String', '', ... 'Position', [420 figH-50 290 22], 'FontSize', fontsz-1, ... - 'ForegroundColor', [.65 .65 .65], 'BackgroundColor', thm.figBg, ... + 'ForegroundColor', thm.textSecondary, 'BackgroundColor', thm.figBg, ... 'HorizontalAlignment', 'left'); % Sliders diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 07d47f9..4a87355 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -19,6 +19,7 @@ %%% +th = PStheme(); PScolormap; % define smat=[];%string @@ -42,7 +43,7 @@ % Control panel layout — cpL/cpW/rh/rs/cpM/ddh inherited from PIDscope.m % yTop tracks where the TOP of the next element goes; Position Y = yTop - height -listH = 5*rs; termH = 4*rs; gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; +listH = 5*rs; termH = round(4.3*rs); gap = rs - rh; fw = cpW-2*cpM; hw = cpW/2-cpM; tbOff_s2 = 40/screensz(4); yTop = 1 - tbOff_s2 - cpTitleH - cpMv; posInfo.fileListWindowSpec= [cpL+cpM yTop-listH fw listH]; yTop=yTop-listH-gap; @@ -119,6 +120,7 @@ % create string list for SpecSelect sA={'Gyro','Gyro prefilt','Dterm','Dterm prefilt','Pterm','PID error','Set point','PIDsum'}; +if isfield(T{1}, 'testSignal_0_'), sA{end+1} = 'Test Signal'; end guiHandlesSpec2.SpecList = uicontrol(PSspecfig2,'Style','listbox','string',[sA],'max',3,'min',1, 'fontsize',fontsz, 'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.TermListWindowSpec], 'callback', 'if length(get(guiHandlesSpec2.SpecList, ''Value'')) > 2, set(guiHandlesSpec2.SpecList, ''Value'', 1); end;'); set(guiHandlesSpec2.SpecList, 'Value', [1 2]); @@ -130,11 +132,11 @@ guiHandlesSpec2.spectrogramButton2 = uicontrol(PSspecfig2,'string','Freq x Throttle','fontsize',fontsz,'TooltipString', ['Opens Freq x Throttle Spectrogram in New Window'], 'units','normalized','Position',[posInfo.spectrogramButton2],... 'callback','PSspecUIcontrol;'); -set(guiHandlesSpec2.spectrogramButton2, 'ForegroundColor', colorA); +set(guiHandlesSpec2.spectrogramButton2, 'ForegroundColor', th.btnDash1); guiHandlesSpec2.spectrogramButton3 = uicontrol(PSspecfig2,'string','Freq x Time','fontsize',fontsz,'TooltipString', ['Opens Freq x Time Spectrogram in New Window'], 'units','normalized','Position',[posInfo.spectrogramButton3],... 'callback','PSfreqTimeUIcontrol;'); - set(guiHandlesSpec2.spectrogramButton3, 'ForegroundColor', colorB); + set(guiHandlesSpec2.spectrogramButton3, 'ForegroundColor', th.btnDash2); guiHandlesSpec2.motorNoiseButton = uicontrol(PSspecfig2,'string','Motor Noise','fontsize',fontsz,... 'TooltipString','Per-motor spectral analysis and noise comparison','units','normalized',... @@ -144,7 +146,7 @@ 'PSplotMotorNoise(T{tmpFcnt},tmpFcnt,tIND{tmpFcnt},1000*A_lograte(tmpFcnt));' ... 'clear tmpFcnt;' ... 'catch e,warndlg([''Motor Noise: '' e.message]),end']); -set(guiHandlesSpec2.motorNoiseButton, 'ForegroundColor', [.2 .7 .2]); +set(guiHandlesSpec2.motorNoiseButton, 'ForegroundColor', th.btnMotNoise); guiHandlesSpec2.chirpButton = uicontrol(PSspecfig2,'string','Chirp Analysis','fontsize',fontsz,... 'TooltipString','Frequency response from chirp log (BF 2025.12+, debug_mode=CHIRP)','units','normalized',... @@ -156,19 +158,19 @@ 'PSrunChirpAnalysis(T{tmpFcnt},SetupInfo{tmpFcnt},debugIdx{tmpFcnt},1000*A_lograte(tmpFcnt),tIND{tmpFcnt},tmpAx);' ... 'clear tmpFcnt tmpRPY tmpAx;' ... 'catch e,warndlg([''Chirp: '' e.message]),end']); -set(guiHandlesSpec2.chirpButton, 'ForegroundColor', [.8 .3 .8]); +set(guiHandlesSpec2.chirpButton, 'ForegroundColor', th.btnChirp); guiHandlesSpec2.Delay = uicontrol(PSspecfig2,'style','popupmenu','string',{'filter delay', 'SP-gyro delay', 'SP smoothing delay', 'phase shift'},'fontsize',fontsz,'TooltipString', ['Select which Delay Display'], 'units','normalized','Position',[posInfo.Delay],... 'callback','PSplotSpec2D;'); guiHandlesSpec2.plotR =uicontrol(PSspecfig2,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotRspec], 'callback', 'PSplotSpec2D;'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotRspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.plotP =uicontrol(PSspecfig2,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotPspec], 'callback', 'PSplotSpec2D;'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisPitch,'Position',[posInfo.plotPspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.plotY =uicontrol(PSspecfig2,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotYspec], 'callback', 'PSplotSpec2D;'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisYaw,'Position',[posInfo.plotYspec], 'callback', 'PSplotSpec2D;'); guiHandlesSpec2.checkboxPSD =uicontrol(PSspecfig2,'Style','checkbox','String','PSD','fontsize',fontsz,'TooltipString', ['Power Spectral Density'],... 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.checkboxPSD],'callback', 'PSplotSpec2D;'); @@ -192,7 +194,7 @@ 'ddh', ddh_px, 'cbW', cbW_px, 'rhs', rhs_px, 'cpTitle', cpTitle_px, 'infoH', 0); cpI = {}; cpI{end+1} = struct('h', spec2Crtlpanel, 'type','panel', 'row',0, 'col',0, 'hpx',0); -listH_px = 5*rs_px; termH_px = 4*rs_px; +listH_px = 5*rs_px; termH_px = round(4.3*rs_px); cpI{end+1} = struct('h', guiHandlesSpec2.FileSelect, 'type','full', 'row',0, 'col',0, 'hpx',listH_px); cpI{end+1} = struct('h', guiHandlesSpec2.SpecList, 'type','full', 'row',0, 'col',0, 'hpx',termH_px); cpI{end+1} = struct('h', guiHandlesSpec2.computeSpec, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index c5e53a8..5a8d529 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -201,12 +201,12 @@ guiHandlesSpec.rpmDynNotch = uicontrol(PSspecfig, 'Style','checkbox', 'String','Dyn Notch', ... 'fontsize', fontsz, 'Value', 0, ... - 'ForegroundColor', [0 .8 .8], 'BackgroundColor', bgcolor, ... + 'ForegroundColor', th.overlayDynNotch, 'BackgroundColor', bgcolor, ... 'units','normalized', 'Position', posInfo.rpmDynNotch, 'callback', rpmCb); guiHandlesSpec.rpmEstChk = uicontrol(PSspecfig, 'Style','checkbox', 'String','RPM est.', ... 'fontsize', fontsz, 'Value', 0, ... - 'ForegroundColor', [.6 .9 .6], 'BackgroundColor', bgcolor, ... + 'ForegroundColor', th.overlayRPM, 'BackgroundColor', bgcolor, ... 'units','normalized', 'Position', posInfo.rpmEstChk, 'callback', rpmCb); guiHandlesSpec.controlFreqCutoff_text = uicontrol(PSspecfig,'style','text','string','freq lims Hz','fontsize',fontsz,'TooltipString',[TooltipString_controlFreqCutoff],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.controlFreqCutoff_text]); diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index f266f69..6eb59a8 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -8,7 +8,8 @@ % ---------------------------------------------------------------------------------- if exist('fnameMaster','var') && ~isempty(fnameMaster) - + +th = PStheme(); if exist('PStunefig','var') && ishandle(PStunefig) figure(PStunefig); else @@ -99,14 +100,14 @@ set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotP],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisPitch,'Position',[posInfo.plotP],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotP, 'Value', 1); guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.plotY],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisYaw,'Position',[posInfo.plotY],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotY, 'Value', 0); guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 976e1e3..65cb6b0 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -32,7 +32,8 @@ posInfo.checkbox11=[chkX chkRow1 chkMotW chkH]; posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; -posInfo.checkbox15=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkboxTS=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.maxYtext = [chkX chkRow1 chkTxtW chkH]; posInfo.maxYinput = [chkX+chkTxtW chkRow1 chkEdtW chkH]; @@ -110,6 +111,8 @@ 'units','normalized','Position',[posInfo.checkbox13],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.checkbox14=uicontrol(PSfig,'Style','checkbox','String','Throttle','fontsize',fontsz,'ForegroundColor',[linec.col14],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox14],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +guiHandles.checkboxTS=uicontrol(PSfig,'Style','checkbox','String','Test Signal','fontsize',fontsz,'ForegroundColor',th.btnDash5,'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkboxTS],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); set(guiHandles.checkbox1, 'Value', 1); set(guiHandles.checkbox7, 'Value', 1); @@ -151,7 +154,8 @@ chkBarItems{end+1} = struct('h', guiHandles.checkbox11, 'wpx', chkMotW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkbox10, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkbox14, 'wpx', chkMotW_px, 'row', 1, 'advance', false); -chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkboxTS, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_text, 'wpx', chkTxtW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.nCols_text, 'wpx', chkTxtW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_input, 'wpx', chkEdtW_px, 'row', 1, 'advance', false); @@ -219,15 +223,15 @@ 'units','normalized','Position',[cpL+halfW oY halfW oH]); oY = oY - oH; guiHandles.overlayGR = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... - 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'ForegroundColor',th_.axisRollFilt,'BackgroundColor',oBg,'HorizontalAlignment','left', ... 'units','normalized','Position',[cpL oY fullW oH]); oY = oY - oH; guiHandles.overlayGP = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... - 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'ForegroundColor',th_.axisPitchFilt,'BackgroundColor',oBg,'HorizontalAlignment','left', ... 'units','normalized','Position',[cpL oY fullW oH]); oY = oY - oH; guiHandles.overlayGY = uicontrol(PSfig,'Style','text','String','','FontSize',oFsz, ... - 'ForegroundColor',linec.col1,'BackgroundColor',oBg,'HorizontalAlignment','left', ... + 'ForegroundColor',th_.axisYawFilt,'BackgroundColor',oBg,'HorizontalAlignment','left', ... 'units','normalized','Position',[cpL oY fullW oH]); % Store overlay handles for PSresizeCP setappdata(PSfig, 'PSoverlay', struct( ... @@ -241,6 +245,7 @@ % always update file list (fnameMaster may have grown since last call) set(guiHandles.FileNum, 'string', fnameMaster); + fileIdx = get(guiHandles.FileNum, 'Value'); if exist('tta','var') && iscell(tta) && numel(tta) >= fileIdx if numel(epoch1_A) < fileIdx || numel(epoch2_A) < fileIdx diff --git a/src/util/PSparseFilterParams.m b/src/util/PSparseFilterParams.m new file mode 100644 index 0000000..13cb27e --- /dev/null +++ b/src/util/PSparseFilterParams.m @@ -0,0 +1,46 @@ +function fp = PSparseFilterParams(si) +%% PSparseFilterParams - extract BF filter settings from setupInfo headers +% BF enum: 0=PT1, 1=BIQUAD, 2=PT2, 3=PT3 +% Filter disabled when hz == 0 (not by type value) +% Header key names differ: BF 4.3+ uses gyro_lpf1_*, BF 4.2 uses gyro_lowpass_* + +fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', hval(si, 'gyro_lowpass_type', 0)); +fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', 0)); +fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', hval(si, 'gyro_lowpass2_type', 0)); +fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', 0)); +fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', hval(si, 'dterm_lowpass_type', 0)); +fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 0)); +fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', hval(si, 'dterm_lowpass2_type', 0)); +fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); +fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); +fp.dterm_notch_cut = hval(si, 'dterm_notch_cutoff', 0); +tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2double(strsplit(tmp, ',')); +if any(isnan(v)), v = [0 0]; end +fp.gyro_notch1_hz = v(1); +fp.gyro_notch2_hz = 0; if numel(v) > 1, fp.gyro_notch2_hz = v(2); end +tmp = hstr(si, 'gyro_notch_cutoff', '0,0'); v = str2double(strsplit(tmp, ',')); +if any(isnan(v)), v = [0 0]; end +fp.gyro_notch1_cut = v(1); +fp.gyro_notch2_cut = 0; if numel(v) > 1, fp.gyro_notch2_cut = v(2); end +end + +function v = hval(si, key, default) + v = default; + for k = 1:size(si, 1) + if strcmp(strtrim(si{k,1}), key) + tmp = str2double(strtrim(si{k,2})); + if ~isnan(tmp), v = tmp; end + return; + end + end +end + +function s = hstr(si, key, default) + s = default; + for k = 1:size(si, 1) + if strcmp(strtrim(si{k,1}), key) + s = strtrim(si{k,2}); + return; + end + end +end diff --git a/src/util/PStheme.m b/src/util/PStheme.m index 8b4a525..544475a 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -31,12 +31,21 @@ th.btnRun = [.15 .85 .25]; th.btnReset = [.95 .55 .15]; th.btnSave = [.70 .70 .70]; -th.btnDash1 = [.85 .25 .25]; % Spectral Analyzer -th.btnDash2 = [.25 .50 .90]; % Step Response -th.btnDash3 = [.85 .55 .15]; % Filter Sim -th.btnDash4 = [.20 .70 .30]; % Motor Noise -th.btnDash5 = [.20 .80 .80]; % Chirp Analysis -th.btnLink = [.85 .55 .15]; % Support PIDscope +th.btnDash1 = [.95 .30 .30]; % Spectral Analyzer - vivid red +th.btnDash2 = [.30 .50 .95]; % Step Response - vivid blue +th.btnDash3 = [.20 .80 .90]; % PID Slider Tool - vivid cyan +th.btnDash4 = [.95 .60 .15]; % Filter Sim - vivid amber +th.btnDash5 = [.90 .75 .20]; % Test Signal - vivid gold +th.btnDash6 = [.90 .30 .60]; % PID Error - vivid magenta +th.btnDash7 = [.25 .75 .65]; % Flight Stats - vivid teal +th.btnMotNoise = [.20 .85 .30]; % Motor Noise - vivid green +th.btnChirp = [.70 .40 .95]; % Chirp Analysis - vivid purple +th.btnLink = [.95 .60 .15]; % Support PIDscope - amber +th.btnPlayer = [.40 .75 1.0]; % Player button - bright sky blue + +% overlay indicator colors (checkboxes in CP) +th.overlayDynNotch = [0 .8 .8]; % Dyn Notch - cyan +th.overlayRPM = [.6 .9 .6]; % RPM est - light green % font size - single source of truth screensz = get(0, 'ScreenSize'); @@ -54,6 +63,34 @@ th.inputBg = [.14 .14 .16]; th.inputFg = [.90 .90 .90]; +% datatip tooltip +th.datatipBg = [1.0 1.0 .88]; % pale yellow + +% RPY axis colors (FPV standard: Roll=red, Pitch=green, Yaw=blue) +th.axisRoll = [.95 .35 .15]; % red-orange +th.axisRollFilt = [1.0 .45 .45]; % light red (distinct from M2 orange) +th.axisPitch = [.20 .85 .25]; % green +th.axisPitchFilt = [.55 1.0 .55]; % lighter lime +th.axisYaw = [.30 .45 .95]; % blue +th.axisYawFilt = [.60 .75 1.0]; % lighter sky blue + +% filter section headers +th.secNotch = [1.0 .70 .30]; % amber/orange +th.secDtermLPF = [.40 1.0 .40]; % green +th.secDtermNotch = [1.0 .50 .50]; % salmon +th.refLine3dB = [.60 .60 .20]; % -3dB / 0.707 reference + +% Bode / chirp analysis +th.bodeMain = [0 .80 1.0]; % tracking TF - bright cyan +th.bodeSecondary = [1.0 .50 0]; % plant TF - orange +th.bodeCoherence = [.30 .90 .30]; % coherence - green +th.bodeRef = [.50 .50 .50]; % reference lines (0dB, -180, unity) + +% transport buttons (Player) +th.btnPlay = [0 .60 0]; % play - dark green +th.btnPause = [.80 .20 0]; % pause - dark red-orange +th.btnStop = [.70 0 0]; % stop - crimson + % signal colors (bright for dark bg) th.sigDebug = [.50 .50 .50]; th.sigGyro = [.85 .85 .85]; @@ -67,5 +104,6 @@ th.sigPIDerr = [.55 .20 .95]; th.sigMotor = {[.95 .20 .20], [.95 .65 .10], [.20 .85 .25], [.20 .45 .95]}; th.sigThrottle = [.85 .85 .85]; +th.sigTestSignal = [1.0 1.0 1.0]; % white (matches Log Viewer overlay) end From 5da63c24287818f2aadc14fb3020647e22ecf921 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 15 Mar 2026 22:37:18 +0100 Subject: [PATCH 41/80] fix(filter-sim): plot fidelity across all 8 subplots --- src/plot/PSfilterSim.m | 67 ++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index d5d32a1..4165529 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -325,7 +325,7 @@ function doUpdate() sigDur = max(0.1, readEditF(h.sig_dur)); types = {'pt1', 'biquad', 'pt2', 'pt3'}; - Nfft = 2048; + Nfft = 4096; fVec = linspace(0, Fs/2, Nfft)'; fMax = min(Fs/2, 1000); fIdx = fVec <= fMax; @@ -347,18 +347,25 @@ function doUpdate() % RPM harmonic notches (cascaded nMotors times per harmonic) rpmCols = {[.9 .2 .2],[.9 .6 .1],[.9 .9 .2],[.3 .8 .3],[.2 .8 .8],[.9 .9 .9],[.8 .3 .8]}; H_rpm = cell(rpmNharm, 1); + H_rpm1 = cell(rpmNharm, 1); % single-motor for phase plot H_rpmAll = ones(Nfft, 1); for ri = 1:rpmNharm fc_rpm = rpmBase * ri; if fc_rpm > 0 && fc_rpm < Fs/2 H_single = notchH_Q(fc_rpm, rpmQ, Fs, Nfft); + H_rpm1{ri} = H_single; H_rpm{ri} = H_single .^ nMotors; else + H_rpm1{ri} = ones(Nfft, 1); H_rpm{ri} = ones(Nfft, 1); end H_rpmAll = H_rpmAll .* H_rpm{ri}; end H_gyroN = H_gn1 .* H_gn2 .* H_rpmAll; + % single-motor product for phase plot (avoids ±180 wrapping) + H_rpmAll1 = ones(Nfft, 1); + for ri = 1:rpmNharm, H_rpmAll1 = H_rpmAll1 .* H_rpm1{ri}; end + H_gyroN1 = H_gn1 .* H_gn2 .* H_rpmAll1; H_dtermN = H_dn; % Group delay @@ -561,18 +568,19 @@ function doUpdate() end if isfinite(gdMax) && gdMax > 0, set(axLdelay, 'YLim', [0 gdMax]); end set(get(axLdelay, 'YLabel'), 'String', 'Filter Delay (ms)'); + drawCutoffLines(axLdelay, g1on, glpf1f, g2on, glpf2f, colG); yTxtL = gdMax * 0.92; yTxtStp = gdMax * 0.17; if combineLPF - text(fMax*0.02, yTxtL, sprintf('gyro lpf: %.3fms', gd_gL(2)), 'Parent', axLdelay, ... + text(fMax*0.02, yTxtL, sprintf('gyro lpf: %.5fms', gd_gL(2)), 'Parent', axLdelay, ... 'Color', colG, 'FontSize', fontsz-1); - text(fMax*0.02, yTxtL - yTxtStp, sprintf('dterm lpf: %.3fms', gd_dL(2)), 'Parent', axLdelay, ... + text(fMax*0.02, yTxtL - yTxtStp, sprintf('dterm lpf: %.5fms', gd_dL(2)), 'Parent', axLdelay, ... 'Color', colD, 'FontSize', fontsz-1); else - if g1on, text(fMax*0.02, yTxtL, sprintf('gyro lpf1: %.3fms', gd_g1(2)), 'Parent', axLdelay, 'Color', colG*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if g2on, text(fMax*0.02, yTxtL, sprintf('gyro lpf2: %.3fms', gd_g2(2)), 'Parent', axLdelay, 'Color', colG, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if d1on, text(fMax*0.02, yTxtL, sprintf('dterm lpf1: %.3fms', gd_d1(2)), 'Parent', axLdelay, 'Color', colD*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if d2on, text(fMax*0.02, yTxtL, sprintf('dterm lpf2: %.3fms', gd_d2(2)), 'Parent', axLdelay, 'Color', colD, 'FontSize', fontsz-1); end + if g1on, text(fMax*0.02, yTxtL, sprintf('gyro lpf1: %.5fms', gd_g1(2)), 'Parent', axLdelay, 'Color', colG*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if g2on, text(fMax*0.02, yTxtL, sprintf('gyro lpf2: %.5fms', gd_g2(2)), 'Parent', axLdelay, 'Color', colG, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if d1on, text(fMax*0.02, yTxtL, sprintf('dterm lpf1: %.5fms', gd_d1(2)), 'Parent', axLdelay, 'Color', colD*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end + if d2on, text(fMax*0.02, yTxtL, sprintf('dterm lpf2: %.5fms', gd_d2(2)), 'Parent', axLdelay, 'Color', colD, 'FontSize', fontsz-1); end end cla(axLphase); hold(axLphase, 'on'); @@ -588,12 +596,13 @@ function doUpdate() if d2on, plotFn(axLphase, fVec(fIdx), ph_d2(fIdx), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end end if showBoth - ph_gN = smooth(angle(H_gyroN) * 180/pi, 9, 'moving'); + ph_gN = smooth(angle(H_gyroN1) * 180/pi, 9, 'moving'); plotFn(axLphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.2); end hold(axLphase, 'off'); PSstyleAxes(axLphase, thm); set(axLphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); set(get(axLphase, 'YLabel'), 'String', 'Phase Delay (deg)'); + drawCutoffLines(axLphase, g1on, glpf1f, g2on, glpf2f, colG); if showStep cla(axLstep); @@ -651,11 +660,21 @@ function doUpdate() annotateNotch(axNmag, gn1f, gn2f, dnf, rpmBase, rpmNharm, rpmCols, colStaticN, colCombN, colD, fontsz, usedB); cla(axNdelay); hold(axNdelay, 'on'); - rpmGd = cell(rpmNharm, 1); + % per-single-motor delay (not cascaded nMotors) + rpmGd1 = cell(rpmNharm, 1); for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); - rpmGd{ri} = smooth(-gradient(unwrap(angle(H_rpm{ri}))) ./ dw * 1000, 51, 'moving'); - plotFn(axNdelay, fVec(fIdx), rpmGd{ri}(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + rpmGd1{ri} = smooth(-gradient(unwrap(angle(H_rpm1{ri}))) ./ dw * 1000, 51, 'moving'); + plotFn(axNdelay, fVec(fIdx), rpmGd1{ri}(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + end + % static notch delay curves + if gn1f > 0 + gd_gn1_v = smooth(-gradient(unwrap(angle(H_gn1))) ./ dw * 1000, 51, 'moving'); + plotFn(axNdelay, fVec(fIdx), gd_gn1_v(fIdx), 'Color', colStaticN, 'LineWidth', 0.8); + end + if gn2f > 0 + gd_gn2_v = smooth(-gradient(unwrap(angle(H_gn2))) ./ dw * 1000, 51, 'moving'); + plotFn(axNdelay, fVec(fIdx), gd_gn2_v(fIdx), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); end plotFn(axNdelay, fVec(fIdx), gd_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); if dnf > 0 @@ -679,19 +698,17 @@ function doUpdate() for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); fc_rpm = rpmBase * ri; - text(fMax*0.3, yTxt, sprintf('RPM %dHz: %.4fms', fc_rpm, rpmGd{ri}(2)), ... + text(fMax*0.3, yTxt, sprintf('RPM %dHz: %.5fms', fc_rpm, rpmGd1{ri}(2)), ... 'Parent', axNdelay, 'Color', rpmCols{ci}, 'FontSize', fontsz-1); yTxt = yTxt - yStN; end if gn1f > 0 - gd_gn1_v = smooth(-gradient(unwrap(angle(H_gn1))) ./ dw * 1000, 51, 'moving'); - text(fMax*0.3, yTxt, sprintf('N1 %dHz: %.4fms', gn1f, gd_gn1_v(2)), ... + text(fMax*0.3, yTxt, sprintf('N1 %dHz: %.5fms', gn1f, gd_gn1_v(2)), ... 'Parent', axNdelay, 'Color', colStaticN, 'FontSize', fontsz-1); yTxt = yTxt - yStN; end if gn2f > 0 - gd_gn2_v = smooth(-gradient(unwrap(angle(H_gn2))) ./ dw * 1000, 51, 'moving'); - text(fMax*0.3, yTxt, sprintf('N2 %dHz: %.4fms', gn2f, gd_gn2_v(2)), ... + text(fMax*0.3, yTxt, sprintf('N2 %dHz: %.5fms', gn2f, gd_gn2_v(2)), ... 'Parent', axNdelay, 'Color', colStaticN*0.85, 'FontSize', fontsz-1); yTxt = yTxt - yStN; end @@ -703,10 +720,18 @@ function doUpdate() cla(axNphase); hold(axNphase, 'on'); for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); - ph_ri = smooth(angle(H_rpm{ri}) * 180/pi, 9, 'moving'); + ph_ri = smooth(angle(H_rpm1{ri}) * 180/pi, 9, 'moving'); plotFn(axNphase, fVec(fIdx), ph_ri(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); end - ph_gN = smooth(angle(H_gyroN) * 180/pi, 9, 'moving'); + if gn1f > 0 + ph_gn1 = smooth(angle(H_gn1) * 180/pi, 9, 'moving'); + plotFn(axNphase, fVec(fIdx), ph_gn1(fIdx), 'Color', colStaticN, 'LineWidth', 0.8); + end + if gn2f > 0 + ph_gn2 = smooth(angle(H_gn2) * 180/pi, 9, 'moving'); + plotFn(axNphase, fVec(fIdx), ph_gn2(fIdx), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); + end + ph_gN = smooth(angle(H_gyroN1) * 180/pi, 9, 'moving'); ph_dN = smooth(angle(H_dtermN) * 180/pi, 9, 'moving'); plotFn(axNphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); if dnf > 0 @@ -811,6 +836,12 @@ function doUpdate() H = H(:); end +function drawCutoffLines(ax, f1on, f1, f2on, f2, col) + yl = get(ax, 'YLim'); + if f1on && f1 > 0, line(ax, [f1 f1], yl, 'Color', col*0.7, 'LineStyle', '--', 'LineWidth', 0.5); end + if f2on && f2 > 0, line(ax, [f2 f2], yl, 'Color', col, 'LineStyle', '--', 'LineWidth', 0.5); end +end + function annotateLPF(ax, t1, f1, t2, f2, ~, col, fsz, usedB) names = {'off', 'pt1', 'biquad', 'pt2', 'pt3'}; if usedB, yLim = [-60 3]; y = -3; yd = -6; From 2e1733e24fa0ba74574c3dc3ece42fc346f719fd Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 16 Mar 2026 21:03:20 +0100 Subject: [PATCH 42/80] feat(step-response): SD shading, error bars, raw traces, period tool, callback fixes --- src/compat/nanstd.m | 29 +++++++++++ src/plot/PStuningParams.m | 102 +++++++++++++++++++++++++++++++++++--- src/ui/PStuneUIcontrol.m | 36 +++++++++++--- src/util/PSstepPeriod.m | 54 ++++++++++++++++++++ src/util/PStheme.m | 3 ++ 5 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 src/compat/nanstd.m create mode 100644 src/util/PSstepPeriod.m diff --git a/src/compat/nanstd.m b/src/compat/nanstd.m new file mode 100644 index 0000000..3417507 --- /dev/null +++ b/src/compat/nanstd.m @@ -0,0 +1,29 @@ +function s = nanstd(x, flag, dim) +%% NANSTD - Standard deviation ignoring NaN entries + + if nargin < 2 || isempty(flag), flag = 0; end + if nargin < 3 + if isvector(x) + dim = find(size(x) > 1, 1); + if isempty(dim), dim = 1; end + else + dim = 1; + end + end + + mask = ~isnan(x); + n = sum(mask, dim); + mu = nanmean(x, dim); + x(~mask) = 0; + x2 = (x - mu).^2; + x2(~mask) = 0; + + if flag == 0 + denom = max(n - 1, 1); + else + denom = max(n, 1); + end + denom(n == 0) = NaN; + s = sqrt(sum(x2, dim) ./ denom); + +end diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 9d8685b..030ada5 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -73,7 +73,7 @@ H = T{f}.(['setpoint_' int2str(p-1) '_'])(tIND{f}); G = T{f}.(['gyroADC_' int2str(p-1) '_'])(tIND{f}); [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value')); - % xcorrLag(p) = finddelay(H, G) * A_lograte(f); + try xcorrLag_cache(p) = finddelay(H, G) / A_lograte(f); catch, xcorrLag_cache(p) = nan; end end catch stepresp_A{p}=[]; @@ -90,19 +90,57 @@ s = []; s = stepresp_A{p}; m=nanmean(s); + sd=nanstd(s); + + col_i = multiLineCols(fcntSR,:); + + % SD shading band + tFlip = [tA fliplr(tA)]; + sdBand = [m+sd fliplr(m-sd)]; + patch(tFlip, sdBand, col_i, 'FaceAlpha', 0.15, 'EdgeColor', 'none', 'Parent', gca); + + % raw segment traces - blend toward bg to simulate low alpha + if isfield(guiHandlesTune, 'rawTraces') && get(guiHandlesTune.rawTraces, 'Value') + col_raw = col_i * 0.12 + th.axesBg * 0.88; + for si = 1:size(s,1) + plot(tA, s(si,:), 'Color', col_raw, 'LineWidth', 0.5); + end + end h1=plot(tA,m); - set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5); - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + set(h1, 'color', col_i, 'linewidth', get(guiHandles.linewidth, 'Value')/1.5); + if get(guiHandlesTune.srLatency, 'Value') == 2 && exist('xcorrLag_cache', 'var') + latencyHalfHeight(p, fcntSR) = xcorrLag_cache(p); + else + latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + end peakresp(p, fcntSR)=max(m(find(tA<150))); peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); + % per-segment metrics for error bars + peakIdx = find(tA < 150); + segPeaks = zeros(1, size(s,1)); + segLats = zeros(1, size(s,1)); + for si = 1:size(s,1) + segPeaks(si) = max(s(si, peakIdx)); + idx50 = find(s(si,:) > 0.5, 1); + if ~isempty(idx50) + segLats(si) = (idx50 / A_lograte(f)) - 1; + else + segLats(si) = nan; + end + end + peakresp_std(p, fcntSR) = nanstd(segPeaks); + latencyHalfHeight_std(p, fcntSR) = nanstd(segLats); + pidvar = [ylab2{p} 'PIDF']; PID = eval([pidvar '{f}']); else peakresp(p, fcntSR) = nan; peaktime(p, fcntSR) = nan; latencyHalfHeight(p, fcntSR) = nan; + peakresp_std(p, fcntSR) = nan; + latencyHalfHeight_std(p, fcntSR) = nan; PID = ''; end @@ -142,9 +180,25 @@ else set(PStunefig, 'CurrentAxes', h2); end h=plot(fcntSR, peakresp(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) - set(gca,'fontsize',fontsz, 'ylim',[0.8 ymax],'ytick',[0.8:.1:ymax],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) + if ~isnan(peakresp_std(p, fcntSR)) && peakresp_std(p, fcntSR) > 0 + ey = peakresp_std(p, fcntSR); + yc = peakresp(p, fcntSR); + line([fcntSR fcntSR], [yc-ey yc+ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + capW = 0.15; + line([fcntSR-capW fcntSR+capW], [yc-ey yc-ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + line([fcntSR-capW fcntSR+capW], [yc+ey yc+ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + end + ymxP = ymax; ymnP = 0.8; + for ei = 1:size(peakresp_std, 2) + if ~isnan(peakresp_std(p, ei)) + ymxP = max(ymxP, peakresp(p, ei) + peakresp_std(p, ei)); + ymnP = min(ymnP, peakresp(p, ei) - peakresp_std(p, ei)); + end + end + ymxP = ymxP + 0.05; ymnP = max(0, ymnP - 0.05); + set(gca,'fontsize',fontsz, 'ylim',[ymnP ymxP],'ytick',[ymnP:.1:ymxP],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) ylabel([ylab{p} ' Peak '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); + xlabel('test', 'fontweight','bold'); hold on grid on plot([0 10],[1 1],'--','Color',th.axesFg) @@ -156,10 +210,23 @@ else set(PStunefig, 'CurrentAxes', h3); end h=plot(fcntSR, latencyHalfHeight(p, fcntSR),'sk'); set(h,'Markersize',markerSz, 'MarkerFaceColor', [multiLineCols(fcntSR,:)]) + if ~isnan(latencyHalfHeight_std(p, fcntSR)) && latencyHalfHeight_std(p, fcntSR) > 0 + ey = latencyHalfHeight_std(p, fcntSR); + yc = latencyHalfHeight(p, fcntSR); + line([fcntSR fcntSR], [yc-ey yc+ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + capW = 0.15; + line([fcntSR-capW fcntSR+capW], [yc-ey yc-ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + line([fcntSR-capW fcntSR+capW], [yc+ey yc+ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); + end mn = min(latencyHalfHeight(p, :))-rem(min(latencyHalfHeight(p, :)),2); mx = max(latencyHalfHeight(p, :))+rem(max(latencyHalfHeight(p, :)),2); - + for ei = 1:size(latencyHalfHeight_std, 2) + if ~isnan(latencyHalfHeight_std(p, ei)) + mn = min(mn, latencyHalfHeight(p, ei) - latencyHalfHeight_std(p, ei)); + mx = max(mx, latencyHalfHeight(p, ei) + latencyHalfHeight_std(p, ei)); + end + end ymaxLat = mx+4; yminLat = mn-4; try @@ -185,10 +252,29 @@ s = []; s = stepresp_A{p}; m=nanmean(s); + sd=nanstd(s); + + col_i = multiLineCols(fcntSR,:); + + % SD shading band + tFlip = [tA fliplr(tA)]; + sdBand = [m+sd fliplr(m-sd)]; + patch(tFlip, sdBand, col_i, 'FaceAlpha', 0.15, 'EdgeColor', 'none', 'Parent', gca); + + if isfield(guiHandlesTune, 'rawTraces') && get(guiHandlesTune.rawTraces, 'Value') + col_raw = col_i * 0.12 + th.axesBg * 0.88; + for si = 1:size(s,1) + plot(tA, s(si,:), 'Color', col_raw, 'LineWidth', 0.5); + end + end h1=plot(tA,m); - set(h1, 'color',[multiLineCols(fcntSR,:)],'linewidth', get(guiHandles.linewidth, 'Value')/1.5, 'linestyle', lineStyle{cnt2}); - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + set(h1, 'color', col_i, 'linewidth', get(guiHandles.linewidth, 'Value')/1.5, 'linestyle', lineStyle{cnt2}); + if get(guiHandlesTune.srLatency, 'Value') == 2 && exist('xcorrLag_cache', 'var') + latencyHalfHeight(p, fcntSR) = xcorrLag_cache(p); + else + latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + end peakresp(p, fcntSR)=max(m(find(tA<150))); peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 6eb59a8..57ce892 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -68,11 +68,15 @@ posInfo.clearPlots= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.saveFig4= [cpL+cpM yTop-rh hw rh]; posInfo.saveSettings4= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; -posInfo.smooth_tuning= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +posInfo.period= [cpL+cpM yTop-rh hw rh]; +posInfo.markup= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; +posInfo.smooth_tuning= [cpL+cpM yTop-ddh fw/2-gap ddh]; +posInfo.srLatency= [cpL+cpM+fw/2 yTop-ddh fw/2 ddh]; yTop=yTop-ddh-gap; posInfo.plotR= [cpL+cpM yTop-rh cbW rh]; posInfo.plotP= [cpL+cpM+cbW yTop-rh cbW rh]; posInfo.plotY= [cpL+cpM+2*cbW yTop-rh cbW rh]; yTop=yTop-rh-gap; -posInfo.RPYcombo= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.RPYcombo= [cpL+cpM yTop-rh fw/2-gap rh]; +posInfo.rawTraces= [cpL+cpM+fw/2 yTop-rh fw/2 rh]; yTop=yTop-rh-gap; posInfo.Ycorrection= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.maxYStepInput= [cpL+cpM yTop-rh cpW/3 rh]; posInfo.maxYStepTxt= [cpL+cpW/3+cpM yTop-rhs cpW/2 rhs]; @@ -99,6 +103,17 @@ 'callback','set(guiHandlesTune.saveSettings, ''FontWeight'', ''bold'');PSsaveSettings; set(guiHandlesTune.saveSettings, ''FontWeight'', ''normal'');'); set(guiHandlesTune.saveSettings, 'ForegroundColor', saveCol); +guiHandlesTune.period = uicontrol(PStunefig,'string','Period','fontsize',fontsz,'TooltipString', 'Click two points to measure period + frequency', 'units','normalized','Position',[posInfo.period],... + 'callback','PSstepPeriod(PStunefig);'); +guiHandlesTune.markup = uicontrol(PStunefig,'string','Markup','fontsize',fontsz,'TooltipString', 'Clear period markers', 'units','normalized','Position',[posInfo.markup],... + 'callback','delete(findobj(PStunefig, ''Tag'', ''PSperiod''));'); + +guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothin...' 'smooth low' 'smooth med' 'smooth high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.smoothFactor_select, 'Value', 1); +guiHandlesTune.srLatency = uicontrol(PStunefig,'style','popupmenu','string',{'SR Latency' 'Xcorr Latency'},'fontsize',fontsz,'TooltipString', 'Latency measurement method', 'units','normalized','Position',[posInfo.srLatency],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); @@ -122,13 +137,14 @@ 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.RPYcombo, 'Value', 0); +guiHandlesTune.rawTraces =uicontrol(PStunefig,'Style','checkbox','String','Raw','fontsize',fontsz,'TooltipString', ['Show individual segment traces'],... + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.rawTraces],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.rawTraces, 'Value', 0); + guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); guiHandlesTune.maxYStepInput = uicontrol(PStunefig,'style','edit','string','1.75','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','Position',[posInfo.maxYStepInput],... 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); - -guiHandlesTune.smoothFactor_select = uicontrol(PStunefig,'style','popupmenu','string',{'smoothing off' 'smoothing low' 'smoothing medium' 'smoothing high'},'fontsize',fontsz,'TooltipString', ['Smooth the gyro when step response traces are too noisy'], 'units','normalized','Position',[posInfo.smooth_tuning],... - 'callback','@selection2;'); -set(guiHandlesTune.smoothFactor_select, 'Value', 1); tuneCrtlpanel_init = true; end % ishandle(tuneCrtlpanel) @@ -143,11 +159,15 @@ cpI{end+1} = struct('h', guiHandlesTune.clearPlots, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.saveFig4, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.saveSettings, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesTune.smoothFactor_select, 'type','dd_full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesTune.period, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.markup, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.smoothFactor_select, 'type','left', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesTune.srLatency, 'type','right', 'row',0, 'col',0, 'hpx',ddh_px); cpI{end+1} = struct('h', guiHandlesTune.plotR, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.plotP, 'type','cb', 'row',0, 'col',1, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.plotY, 'type','cb_end', 'row',0, 'col',2, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesTune.RPYcombo, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.RPYcombo, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.rawTraces, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.Ycorrection, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepInput, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepTxt, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); diff --git a/src/util/PSstepPeriod.m b/src/util/PSstepPeriod.m new file mode 100644 index 0000000..ddec44d --- /dev/null +++ b/src/util/PSstepPeriod.m @@ -0,0 +1,54 @@ +function PSstepPeriod(fig) +%% PSstepPeriod - click two points on step response trace, show period + frequency + +th = PStheme(); + +% clear previous period markers +delete(findobj(fig, 'Tag', 'PSperiod')); + +allAx = findobj(fig, 'Type', 'axes', 'Visible', 'on'); +if isempty(allAx), return; end + +% first click - determines target axes +try ginput(1); catch, return; end +figPt = get(fig, 'CurrentPoint'); +ax = []; +for ai = 1:numel(allAx) + p = getpixelposition(allAx(ai)); + if figPt(1) >= p(1) && figPt(1) <= p(1)+p(3) && figPt(2) >= p(2) && figPt(2) <= p(2)+p(4) + ax = allAx(ai); break; + end +end +if isempty(ax), return; end + +xl = get(ax, 'XLim'); +x1 = xl(1) + (figPt(1) - p(1)) / p(3) * (xl(2) - xl(1)); + +% second click +try ginput(1); catch, return; end +figPt = get(fig, 'CurrentPoint'); +p = getpixelposition(ax); +x2 = xl(1) + (figPt(1) - p(1)) / p(3) * (xl(2) - xl(1)); + +x = sort([x1 x2]); +dt = x(2) - x(1); +freq = 1000 / dt; + +% draw on all step trace axes (XLim > 100ms excludes scatter plots) +stepAxes = []; +for ai = 1:numel(allAx) + xl_chk = get(allAx(ai), 'XLim'); + if xl_chk(2) > 100, stepAxes(end+1) = allAx(ai); end +end +if isempty(stepAxes), stepAxes = ax; end +for ai = 1:numel(stepAxes) + yl_i = get(stepAxes(ai), 'YLim'); + line(stepAxes(ai), [x(1) x(1)], yl_i, 'Color', th.periodMarker, 'LineWidth', 1.5, 'Tag', 'PSperiod'); + line(stepAxes(ai), [x(2) x(2)], yl_i, 'Color', th.periodMarker, 'LineWidth', 1.5, 'Tag', 'PSperiod'); +end +yl = get(ax, 'YLim'); +text(mean(x), yl(2)*0.93, sprintf('%.0fms, %.4fHz', dt, freq), ... + 'Parent', ax, 'Color', th.textPrimary, 'FontSize', th.fontsz, ... + 'HorizontalAlignment', 'center', 'FontWeight', 'bold', 'Tag', 'PSperiod'); + +end diff --git a/src/util/PStheme.m b/src/util/PStheme.m index 544475a..11a7b5d 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -55,6 +55,9 @@ th.fontsz = round(th.fontsz * 0.85); end +% period marker (Step Response) +th.periodMarker = [.95 .20 .20]; + % diff highlight (Setup Info) th.diffBg = [.45 .18 .18]; From 005d024e5fb3c10ca8273a9ea4a904956ce524a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 22 Mar 2026 22:18:21 +0100 Subject: [PATCH 43/80] fix(filter-sim): fixed LPF step 4ms range + notch phase +-90 deg clamp --- src/plot/PSfilterSim.m | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index 4165529..c808516 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -14,6 +14,9 @@ function PSfilterSim(~, Fs, setupInfo) fp = PSparseFilterParams(setupInfo); +% Use gyro loop rate from headers if available (filters run at gyro rate, not logging rate) +if fp.gyro_rate_hz > 0, Fs = fp.gyro_rate_hz; end + % Layout: 2 cols x 4 rows + gradient bars plotL = 0.05; plotR = 0.75; @@ -382,12 +385,8 @@ function doUpdate() set(h.totalDelay, 'String', sprintf('Total Delay: %.3fms (LPF %.3f + Notch %.3f)', ... delayL + delayN, delayL, delayN)); - % Step responses — LPF: adaptive time, Notch: 100ms - minCutoff = Fs/2; - for cc = [glpf1f glpf2f dlpf1f dlpf2f] - if cc > 0, minCutoff = min(minCutoff, cc); end - end - lpfStepMs = max(4, min(10, 1000 / max(minCutoff, 50))); + % Step responses + lpfStepMs = 4; stepLenL = round(Fs * lpfStepMs / 1000); stepInL = ones(stepLenL, 1); tStepL = (0:stepLenL-1)' / Fs * 1000; @@ -625,7 +624,7 @@ function doUpdate() end end hold(axLstep, 'off'); - PSstyleAxes(axLstep, thm); set(axLstep, 'XLim', [tStepL(1) tStepL(end)], 'YLim', [-0.05 1.15]); + PSstyleAxes(axLstep, thm); set(axLstep, 'XLim', [0 lpfStepMs], 'YLim', [-0.05 1.15]); xlabel(axLstep, 'Time (ms)', 'Color', thm.textPrimary); set(get(axLstep, 'YLabel'), 'String', 'Step Resp.'); end @@ -738,7 +737,7 @@ function doUpdate() plotFn(axNphase, fVec(fIdx), ph_dN(fIdx), 'Color', colD, 'LineWidth', 1.2); end hold(axNphase, 'off'); - PSstyleAxes(axNphase, thm); set(axNphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + PSstyleAxes(axNphase, thm); set(axNphase, 'XLim', xlimF(useLog, fMax), 'YLim', [-90 90], 'XTickLabel', {}); set(get(axNphase, 'YLabel'), 'String', 'Phase Delay (deg)'); if showStep From 63bfcc2091bd76a4cfff2beefce23b5538d543b9 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 23 Mar 2026 07:16:11 +0100 Subject: [PATCH 44/80] fix(filter-sim): Emuflight per-axis keys + gyro rate from looptime header --- src/util/PSparseFilterParams.m | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/util/PSparseFilterParams.m b/src/util/PSparseFilterParams.m index 13cb27e..507bd49 100644 --- a/src/util/PSparseFilterParams.m +++ b/src/util/PSparseFilterParams.m @@ -5,13 +5,13 @@ % Header key names differ: BF 4.3+ uses gyro_lpf1_*, BF 4.2 uses gyro_lowpass_* fp.gyro_lpf1_type = hval(si, 'gyro_lpf1_type', hval(si, 'gyro_lowpass_type', 0)); -fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', 0)); +fp.gyro_lpf1_hz = hval(si, 'gyro_lpf1_static_hz', hval(si, 'gyro_lowpass_hz', hval(si, 'gyro_lowpass_hz_roll', 0))); fp.gyro_lpf2_type = hval(si, 'gyro_lpf2_type', hval(si, 'gyro_lowpass2_type', 0)); -fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', 0)); +fp.gyro_lpf2_hz = hval(si, 'gyro_lpf2_static_hz', hval(si, 'gyro_lowpass2_hz', hval(si, 'gyro_lowpass2_hz_roll', 0))); fp.dterm_lpf1_type = hval(si, 'dterm_lpf1_type', hval(si, 'dterm_lowpass_type', 0)); -fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', 0)); +fp.dterm_lpf1_hz = hval(si, 'dterm_lpf1_static_hz', hval(si, 'dterm_lowpass_hz', hval(si, 'dterm_lowpass_hz_roll', 0))); fp.dterm_lpf2_type = hval(si, 'dterm_lpf2_type', hval(si, 'dterm_lowpass2_type', 0)); -fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', 0)); +fp.dterm_lpf2_hz = hval(si, 'dterm_lpf2_static_hz', hval(si, 'dterm_lowpass2_hz', hval(si, 'dterm_lowpass2_hz_roll', 0))); fp.dterm_notch_hz = hval(si, 'dterm_notch_hz', 0); fp.dterm_notch_cut = hval(si, 'dterm_notch_cutoff', 0); tmp = hstr(si, 'gyro_notch_hz', '0,0'); v = str2double(strsplit(tmp, ',')); @@ -22,6 +22,14 @@ if any(isnan(v)), v = [0 0]; end fp.gyro_notch1_cut = v(1); fp.gyro_notch2_cut = 0; if numel(v) > 1, fp.gyro_notch2_cut = v(2); end + +% Gyro loop rate from headers (filters run at gyro rate, not logging rate) +lt = hval(si, 'looptime', 0); +if lt > 0 + fp.gyro_rate_hz = round(1e6 / lt); +else + fp.gyro_rate_hz = 0; +end end function v = hval(si, key, default) From 2ad090737b4e4fb3c97011298330a522ee48939e Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 24 Mar 2026 09:13:45 +0100 Subject: [PATCH 45/80] feat(ui): Period tool - red markers on all LV axes + ms/Hz annotation --- PIDscope.m | 4 +-- src/util/PSlogViewerPeriod.m | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/util/PSlogViewerPeriod.m diff --git a/PIDscope.m b/PIDscope.m index 19eff6f..9ce53e8 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -332,8 +332,8 @@ 'callback','PSstatsUIcontrol; PSplotStats;'); set(guiHandles.FlightStatsButton, 'ForegroundColor', th.btnDash7); -guiHandles.period2Hz = uicontrol(PSfig,'string','Period','fontsize',fontsz,'TooltipString', ['Calculates peak to peak in Hz similar to the BBE ''Mark'' tool' , newline, 'press button, position mouse over 1st peak, mouse click,' , newline, 'then position over 2nd peak, then mouse click again'], 'units','normalized','Position',[posInfo.period2Hz],... - 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.period2Hz, ''Value''), try, [x1 y1] = ginput(1); figure(PSfig); h=plot([x1 x1],[-(maxY*2) maxY],''-r'');set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); [x2 y2] = ginput(1); h=plot([x2 x2],[-(maxY*2) maxY],''-r''); set(h,''linewidth'' , get(guiHandles.linewidth, ''Value'')/2); plot([x1 x2],[y1 y2],'':k''); x3=[round(x1*1000) round(x2*1000)]; f = 1000/(x3(2)-x3(1)); text(x2, y2, [num2str(x3(2)-x3(1)) ''ms, '' num2str(f) ''Hz''],''FontSize'',fontsz, ''FontWeight'', ''Bold''); catch, end, end'); +guiHandles.period2Hz = uicontrol(PSfig,'string','Period','fontsize',fontsz,'TooltipString', ['Click two points on any trace to measure period and frequency.' , newline, 'Red vertical lines + ms/Hz annotation on all axes.'], 'units','normalized','Position',[posInfo.period2Hz],... + 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA), PSlogViewerPeriod(PSfig); end'); guiHandles.DispInfoButton = uicontrol(PSfig,'string','Setup Info','fontsize',fontsz,'TooltipString', [TooltipString_setup],'units','normalized','Position',[posInfo.DispInfoButton],... 'callback','PSdispSetupInfoUIcontrol;PSdispSetupInfo;'); diff --git a/src/util/PSlogViewerPeriod.m b/src/util/PSlogViewerPeriod.m new file mode 100644 index 0000000..423800e --- /dev/null +++ b/src/util/PSlogViewerPeriod.m @@ -0,0 +1,63 @@ +function PSlogViewerPeriod(fig) +%% PSlogViewerPeriod - click two points on log viewer trace, show period + frequency + +th = PStheme(); + +% clear previous markers +delete(findobj(fig, 'Tag', 'PSperiodLV')); + +% find all visible LV axes (PSrpy, PSmotor, PScombo) +allAx = findobj(fig, 'Type', 'axes', 'Visible', 'on'); +lvAx = []; +for ai = 1:numel(allAx) + t = get(allAx(ai), 'Tag'); + if any(strcmp(t, {'PSrpy', 'PSmotor', 'PScombo'})) + lvAx(end+1) = allAx(ai); + end +end +if isempty(lvAx), return; end + +set(fig, 'pointer', 'crosshair'); + +% first click +try ginput(1); catch, set(fig,'pointer','arrow'); return; end +figPt = get(fig, 'CurrentPoint'); +ax = []; +for ai = 1:numel(lvAx) + p = getpixelposition(lvAx(ai)); + if figPt(1) >= p(1) && figPt(1) <= p(1)+p(3) && figPt(2) >= p(2) && figPt(2) <= p(2)+p(4) + ax = lvAx(ai); break; + end +end +if isempty(ax), set(fig,'pointer','arrow'); return; end + +xl = get(ax, 'XLim'); +p = getpixelposition(ax); +x1 = xl(1) + (figPt(1) - p(1)) / p(3) * (xl(2) - xl(1)); + +% second click +try ginput(1); catch, set(fig,'pointer','arrow'); return; end +figPt = get(fig, 'CurrentPoint'); +p = getpixelposition(ax); +x2 = xl(1) + (figPt(1) - p(1)) / p(3) * (xl(2) - xl(1)); + +set(fig, 'pointer', 'arrow'); + +x = sort([x1 x2]); +dt_sec = x(2) - x(1); +if dt_sec <= 0, return; end +dt_ms = dt_sec * 1000; +freq = 1 / dt_sec; + +% draw on all LV axes +for ai = 1:numel(lvAx) + yl_i = get(lvAx(ai), 'YLim'); + line(lvAx(ai), [x(1) x(1)], yl_i, 'Color', th.periodMarker, 'LineWidth', 1.5, 'Tag', 'PSperiodLV'); + line(lvAx(ai), [x(2) x(2)], yl_i, 'Color', th.periodMarker, 'LineWidth', 1.5, 'Tag', 'PSperiodLV'); +end +yl = get(ax, 'YLim'); +text(mean(x), yl(2)*0.93, sprintf('%.1fms, %.2fHz', dt_ms, freq), ... + 'Parent', ax, 'Color', th.textPrimary, 'FontSize', th.fontsz, ... + 'HorizontalAlignment', 'center', 'FontWeight', 'bold', 'Tag', 'PSperiodLV'); + +end From 8a5421203321a4f69fd04806654852a1efc404c6 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 24 Mar 2026 21:49:11 +0100 Subject: [PATCH 46/80] feat(ui): eRPM overlay on motor subplot --- src/plot/PSplotLogViewer.m | 57 ++++++++++++++++++++++++++++++++++++++ src/ui/PSviewerUIcontrol.m | 8 ++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index d125523..548e45b 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -44,6 +44,7 @@ set(guiHandles.checkbox13, 'Value', allVal); set(guiHandles.checkbox14, 'Value', allVal); set(guiHandles.checkboxTS, 'Value', allVal); + try set(guiHandles.checkboxeRPM, 'Value', allVal); catch, end end plotall_flag=-1; @@ -137,6 +138,7 @@ try delete(findobj(PSfig,'Tag','PSrpy')); catch, end try delete(findobj(PSfig,'Tag','PSmotor')); catch, end try delete(findobj(PSfig,'Tag','PScombo')); catch, end + try delete(findobj(PSfig,'Tag','PSeRPMax')); catch, end end try delete(hch1); catch, end, try delete(hch2); catch, end @@ -322,6 +324,61 @@ end end + % eRPM overlay on motor subplot + try delete(findobj(PSfig,'Tag','PSeRPMax')); catch, end + try + if isfield(guiHandles, 'checkboxeRPM') && get(guiHandles.checkboxeRPM, 'Value') + motorAx = findobj(PSfig, 'Type', 'axes', 'Tag', 'PSmotor'); + if ~isempty(motorAx) + motorAx = motorAx(1); + fileIdx_ = get(guiHandles.FileNum, 'Value'); + hasERPM = isfield(T{fileIdx_}, 'eRPM_0_'); + if hasERPM + % get motor_poles from header (default 14) + mPoles = 14; + try + mpRow = find(strcmp(SetupInfo{fileIdx_}(:,1), 'motor_poles')); + if ~isempty(mpRow), mPoles = str2double(SetupInfo{fileIdx_}(mpRow(1),2)); end + catch, end + if mPoles < 2, mPoles = 14; end + + mPos = get(motorAx, 'Position'); + mXL = get(motorAx, 'XLim'); + tSec_ = tta{fileIdx_}/us2sec; + sFactor_ = lineSmoothFactors(get(guiHandles.lineSmooth, 'Value')); + lwVal_ = get(guiHandles.linewidth, 'Value')/2; + + rpmAx = axes('Parent', PSfig, 'Position', mPos, ... + 'Color', 'none', 'XLim', mXL, ... + 'YAxisLocation', 'right', 'Box', 'off', ... + 'XTick', [], 'Tag', 'PSeRPMax'); + hold(rpmAx, 'on'); + + rpmColors = {linec.col10, linec.col11, linec.col12, linec.col13}; + rpmMax = 0; + for mk = 0:3 + fld = ['eRPM_' int2str(mk) '_']; + if isfield(T{fileIdx_}, fld) + raw = PSsmoothLV(PSfig, T{fileIdx_}, fileIdx_, fld, sFactor_); + hz = raw * 100 / (mPoles/2) / 60; + plot(rpmAx, tSec_, hz, 'Color', rpmColors{mk+1}, 'LineWidth', lwVal_, 'LineStyle', ':'); + rpmMax = max(rpmMax, max(hz)); + end + end + if rpmMax > 0 + rpmCeil = ceil(rpmMax/100)*100; + rpmStep = rpmCeil / 5; + set(rpmAx, 'YLim', [0 rpmCeil], 'YColor', th.textSecondary, ... + 'YTick', 0:rpmStep:rpmCeil, 'fontsize', fontsz, ... + 'XColor', 'none', 'TickDir', 'in'); + yl_ = ylabel(rpmAx, 'eRPM (Hz)', 'fontweight', 'bold'); + set(yl_, 'color', th.textSecondary); + end + end + end + end + catch, end + % i/o keyboard trim: 'i' sets in-point, 'o' sets out-point set(PSfig, 'KeyPressFcn', [ ... 'if exist(''filenameA'',''var'') && ~isempty(filenameA), ' ... diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 65cb6b0..a7d0eaa 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -33,7 +33,8 @@ posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; posInfo.checkboxTS=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; -posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; +posInfo.checkboxeRPM=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.maxYtext = [chkX chkRow1 chkTxtW chkH]; posInfo.maxYinput = [chkX+chkTxtW chkRow1 chkEdtW chkH]; @@ -124,6 +125,8 @@ guiHandles.checkbox15=uicontrol(PSfig,'Style','checkbox','String','All','fontsize',fontsz,'TooltipString', ['Plot or clear all lines '],'ForegroundColor',[linec.col15],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); +guiHandles.checkboxeRPM=uicontrol(PSfig,'Style','checkbox','String','eRPM','fontsize',fontsz,'TooltipString','Motor eRPM traces (Hz) on motor subplot','ForegroundColor',th.textSecondary,'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkboxeRPM],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; set(guiHandles.FileNum, 'string', fnameMaster, 'TooltipString', TooltipString_FileNum,... @@ -155,7 +158,8 @@ chkBarItems{end+1} = struct('h', guiHandles.checkbox10, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkbox14, 'wpx', chkMotW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkboxTS, 'wpx', chkMotW_px, 'row', 2, 'advance', true); -chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkboxeRPM, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_text, 'wpx', chkTxtW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.nCols_text, 'wpx', chkTxtW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_input, 'wpx', chkEdtW_px, 'row', 1, 'advance', false); From e65bc094698bd85d5feaa8fc75440ebf4a9c004c Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Wed, 25 Mar 2026 20:51:06 +0100 Subject: [PATCH 47/80] fix(ui): 4 per-motor RPM checkboxes, Stats mode-switch cleanup --- src/plot/PSplotLogViewer.m | 31 ++++++++++++++++++++++++------- src/plot/PSplotStats.m | 23 ++++++++++------------- src/ui/PSviewerUIcontrol.m | 24 ++++++++++++++++++------ src/util/PStheme.m | 1 + 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 548e45b..4b3f623 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -44,7 +44,7 @@ set(guiHandles.checkbox13, 'Value', allVal); set(guiHandles.checkbox14, 'Value', allVal); set(guiHandles.checkboxTS, 'Value', allVal); - try set(guiHandles.checkboxeRPM, 'Value', allVal); catch, end + for rk_=1:4, try set(guiHandles.(['checkboxRPM' int2str(rk_)]), 'Value', allVal); catch, end, end end plotall_flag=-1; @@ -64,6 +64,16 @@ %% where you want full range of data fileIdx = get(guiHandles.FileNum, 'Value'); + % enable/disable RPM checkboxes based on eRPM data + hasERPM_ = exist('T','var') && iscell(T) && numel(T) >= fileIdx && isfield(T{fileIdx}, 'eRPM_0_'); + rpmEn_ = 'off'; if hasERPM_, rpmEn_ = 'on'; end + for rk_=1:4 + try + set(guiHandles.(['checkboxRPM' int2str(rk_)]), 'Enable', rpmEn_); + if ~hasERPM_, set(guiHandles.(['checkboxRPM' int2str(rk_)]), 'Value', 0); end + catch, end + end + % Update Debug checkbox label for RC_INTERPOLATION mode (version-aware) tmpRCidx = RC_INTERPOLATION; % global default if exist('debugIdx','var') && numel(debugIdx) >= fileIdx @@ -324,17 +334,18 @@ end end - % eRPM overlay on motor subplot + % eRPM overlay on motor subplot (per-motor RPM checkboxes) try delete(findobj(PSfig,'Tag','PSeRPMax')); catch, end try - if isfield(guiHandles, 'checkboxeRPM') && get(guiHandles.checkboxeRPM, 'Value') + rpmEnabled_ = false(1,4); + for rk_=1:4, try rpmEnabled_(rk_) = get(guiHandles.(['checkboxRPM' int2str(rk_)]), 'Value'); catch, end, end + if any(rpmEnabled_) motorAx = findobj(PSfig, 'Type', 'axes', 'Tag', 'PSmotor'); if ~isempty(motorAx) motorAx = motorAx(1); fileIdx_ = get(guiHandles.FileNum, 'Value'); hasERPM = isfield(T{fileIdx_}, 'eRPM_0_'); if hasERPM - % get motor_poles from header (default 14) mPoles = 14; try mpRow = find(strcmp(SetupInfo{fileIdx_}(:,1), 'motor_poles')); @@ -351,17 +362,18 @@ rpmAx = axes('Parent', PSfig, 'Position', mPos, ... 'Color', 'none', 'XLim', mXL, ... 'YAxisLocation', 'right', 'Box', 'off', ... - 'XTick', [], 'Tag', 'PSeRPMax'); + 'XTick', [], 'Tag', 'PSeRPMax', 'HitTest', 'off'); hold(rpmAx, 'on'); - rpmColors = {linec.col10, linec.col11, linec.col12, linec.col13}; + rpmColors = th.sigRPM; rpmMax = 0; for mk = 0:3 + if ~rpmEnabled_(mk+1), continue; end fld = ['eRPM_' int2str(mk) '_']; if isfield(T{fileIdx_}, fld) raw = PSsmoothLV(PSfig, T{fileIdx_}, fileIdx_, fld, sFactor_); hz = raw * 100 / (mPoles/2) / 60; - plot(rpmAx, tSec_, hz, 'Color', rpmColors{mk+1}, 'LineWidth', lwVal_, 'LineStyle', ':'); + plot(rpmAx, tSec_, hz, 'Color', rpmColors{mk+1}, 'LineWidth', lwVal_, 'LineStyle', ':', 'HitTest', 'off'); rpmMax = max(rpmMax, max(hz)); end end @@ -373,6 +385,11 @@ 'XColor', 'none', 'TickDir', 'in'); yl_ = ylabel(rpmAx, 'eRPM (Hz)', 'fontweight', 'bold'); set(yl_, 'color', th.textSecondary); + % epoch trim fills on RPM overlay + hf_=fill(rpmAx,[0,t1,t1,0],[0,0,rpmCeil,rpmCeil],th.epochFill); + set(hf_,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill,'HitTest','off'); + hf_=fill(rpmAx,[t2,mXL(2),mXL(2),t2],[0,0,rpmCeil,rpmCeil],th.epochFill); + set(hf_,'FaceAlpha',th.epochAlpha,'EdgeColor',th.epochFill,'HitTest','off'); end end end diff --git a/src/plot/PSplotStats.m b/src/plot/PSplotStats.m index b34f651..c557307 100644 --- a/src/plot/PSplotStats.m +++ b/src/plot/PSplotStats.m @@ -14,6 +14,8 @@ th = PStheme(); set(PSstatsfig, 'pointer', 'watch'); +% clean up all old stats axes before replotting +try delete(findobj(PSstatsfig, 'Type', 'axes')); catch, end fA = get(guiHandlesStats.FileA, 'Value'); fB = []; @@ -55,9 +57,7 @@ for sp = 1:4 stag_ = sprintf('PSstats_%d', sp); - hhist = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); - if isempty(hhist), hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp,:), 'Tag', stag_); - else set(PSstatsfig, 'CurrentAxes', hhist); cla; end + hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp,:), 'Tag', stag_); if sp <= 3 pctData = Rpct_A{sp}; else @@ -95,9 +95,7 @@ for sp = 1:4 stag_ = sprintf('PSstats_%d', sp+4); - hhist = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); - if isempty(hhist), hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp+4,:), 'Tag', stag_); - else set(PSstatsfig, 'CurrentAxes', hhist); cla; end + hhist = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos(sp+4,:), 'Tag', stag_); if sp <= 3 pctData = Rpct_B{sp}; else @@ -172,9 +170,7 @@ useAbs = grp{4}; stag_ = sprintf('PSstats2_%d', slots(g)); - h1 = findobj(PSstatsfig, 'Type', 'axes', 'Tag', stag_); - if isempty(h1), h1 = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos2(slots(g),:), 'Tag', stag_); - else set(PSstatsfig, 'CurrentAxes', h1); cla; end + h1 = axes('Parent', PSstatsfig, 'Position', posInfo.statsPos2(slots(g),:), 'Tag', stag_); vals = zeros(nAx, 1); sds = zeros(nAx, 1); @@ -202,12 +198,14 @@ set(s, 'FaceColor', clr); end + ymx_ = max(vals(1:nAx)+sds(1:nAx))*1.2+1; + if isnan(ymx_) || ymx_ < 1, ymx_ = 10; end if nAx == 2 set(gca, 'Xtick', 1:nAx, 'xticklabel', axLabelsRPY(1:2)); - axis([.5 2.5 0 max(vals+sds)*1.2+1]); + axis([.5 2.5 0 ymx_]); elseif nAx == 3 set(gca, 'Xtick', 1:nAx, 'xticklabel', axLabelsRPY); - axis([.5 3.5 0 max(vals+sds)*1.2+1]); + axis([.5 3.5 0 ymx_]); elseif nAx == 4 && strcmp(prefix, 'setpoint_') set(gca, 'Xtick', 1:4, 'xticklabel', axLabelsRPYT); axis([.5 4.5 0 100]); @@ -216,8 +214,7 @@ axis([.5 4.5 0 100]); elseif nAx == 4 set(gca, 'Xtick', 1:4, 'xticklabel', axLabelsD4); - ymax = max(vals+sds)*1.2+1; if ymax < 1, ymax = 10; end - axis([.5 4.5 0 ymax]); + axis([.5 4.5 0 ymx_]); end set(gca, 'xcolor', clr, 'ycolor', clr, 'YMinorGrid', 'on'); diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index a7d0eaa..1b20671 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -33,8 +33,12 @@ posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; posInfo.checkboxTS=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; -posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; -posInfo.checkboxeRPM=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; chkX=chkX+chkMotW; +chkRpmW_px = 80; chkRpmW = chkRpmW_px/figW; +posInfo.checkboxRPM4=[chkX chkRow1 chkRpmW chkH]; +posInfo.checkboxRPM3=[chkX chkRow2 chkRpmW chkH]; chkX=chkX+chkRpmW; +posInfo.checkboxRPM2=[chkX chkRow1 chkRpmW chkH]; +posInfo.checkboxRPM1=[chkX chkRow2 chkRpmW chkH]; chkX=chkX+chkRpmW; posInfo.maxYtext = [chkX chkRow1 chkTxtW chkH]; posInfo.maxYinput = [chkX+chkTxtW chkRow1 chkEdtW chkH]; @@ -125,8 +129,13 @@ guiHandles.checkbox15=uicontrol(PSfig,'Style','checkbox','String','All','fontsize',fontsz,'TooltipString', ['Plot or clear all lines '],'ForegroundColor',[linec.col15],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); -guiHandles.checkboxeRPM=uicontrol(PSfig,'Style','checkbox','String','eRPM','fontsize',fontsz,'TooltipString','Motor eRPM traces (Hz) on motor subplot','ForegroundColor',th.textSecondary,'BackgroundColor',bgcolor,... - 'units','normalized','Position',[posInfo.checkboxeRPM],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +rpmColors_ = th.sigRPM; +rpmLabels_ = {'RPM 1', 'RPM 2', 'RPM 3', 'RPM 4'}; +rpmTip_ = 'Motor eRPM trace (Hz) on motor subplot'; +for rk_ = 1:4 + guiHandles.(['checkboxRPM' int2str(rk_)]) = uicontrol(PSfig,'Style','checkbox','String',rpmLabels_{rk_},'fontsize',fontsz,'TooltipString',rpmTip_,'ForegroundColor',rpmColors_{rk_},'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.(['checkboxRPM' int2str(rk_)])],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); +end TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; set(guiHandles.FileNum, 'string', fnameMaster, 'TooltipString', TooltipString_FileNum,... @@ -158,8 +167,11 @@ chkBarItems{end+1} = struct('h', guiHandles.checkbox10, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkbox14, 'wpx', chkMotW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkboxTS, 'wpx', chkMotW_px, 'row', 2, 'advance', true); -chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', false); -chkBarItems{end+1} = struct('h', guiHandles.checkboxeRPM, 'wpx', chkMotW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM4, 'wpx', chkRpmW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM3, 'wpx', chkRpmW_px, 'row', 2, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM2, 'wpx', chkRpmW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM1, 'wpx', chkRpmW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_text, 'wpx', chkTxtW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.nCols_text, 'wpx', chkTxtW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.maxY_input, 'wpx', chkEdtW_px, 'row', 1, 'advance', false); diff --git a/src/util/PStheme.m b/src/util/PStheme.m index 11a7b5d..f2df371 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -106,6 +106,7 @@ th.sigPIDsum = [1.0 .35 .90]; th.sigPIDerr = [.55 .20 .95]; th.sigMotor = {[.95 .20 .20], [.95 .65 .10], [.20 .85 .25], [.20 .45 .95]}; +th.sigRPM = {[1.0 .50 .50], [1.0 .80 .40], [.50 1.0 .55], [.50 .65 1.0]}; th.sigThrottle = [.85 .85 .85]; th.sigTestSignal = [1.0 1.0 1.0]; % white (matches Log Viewer overlay) From 39ac3a1a4622004e54428b3306007d5d3bc1fe2d Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 28 Mar 2026 13:47:44 +0100 Subject: [PATCH 48/80] feat(ui): Setup Info diff, Filter Sim overhaul, Copy CLI --- src/plot/PSdispSetupInfo.m | 131 +++++-- src/plot/PSfilterSim.m | 688 ++++++++++++++++++++++++++----------- src/util/PStheme.m | 4 +- tests/test_PSfilterSim.m | 74 ++++ 4 files changed, 652 insertions(+), 245 deletions(-) diff --git a/src/plot/PSdispSetupInfo.m b/src/plot/PSdispSetupInfo.m index ee722cf..6976864 100644 --- a/src/plot/PSdispSetupInfo.m +++ b/src/plot/PSdispSetupInfo.m @@ -1,4 +1,4 @@ -%% PSdispSetupInfo +%% PSdispSetupInfo % ---------------------------------------------------------------------------------- % "THE BEER-WARE LICENSE" (Revision 42): @@ -8,71 +8,132 @@ % ---------------------------------------------------------------------------------- if exist('fnameMaster','var') && ~isempty(fnameMaster) - if Nfiles < 2 - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); % Octave compatible (was: strings) - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); - setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); % Octave compatible (was: string()) - else - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo,1), 1); - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,1), str); - setupA=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispA, 'Value')).SetupInfo(:,2)); - str=repmat({':'}, size(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo,1), 1); - str2=strcat(dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,1), str); - setupB=strcat(str2, dataA(get(guiHandlesInfo.FileNumDispB, 'Value')).SetupInfo(:,2)); - end - + fA_ = get(guiHandlesInfo.FileNumDispA, 'Value'); + siA = dataA(fA_).SetupInfo; + keysA = strtrim(siA(:,1)); + valsA = strtrim(siA(:,2)); + nA = numel(keysA); + str = repmat({':'}, nA, 1); + setupA = strcat(keysA, str, valsA); + th = PStheme(); diffCol = th.diffBg; - nA = size(setupA,1); BGCol = repmat(th.panelBg, nA, 1); u = false(nA, 1); - try - for i = 1 : min(nA, size(setupB,1)) - if ~strcmp(setupA{i}, setupB{i}) + + if Nfiles >= 2 && isfield(guiHandlesInfo, 'FileNumDispB') && ishandle(guiHandlesInfo.FileNumDispB) + fB_ = get(guiHandlesInfo.FileNumDispB, 'Value'); + siB = dataA(fB_).SetupInfo; + keysB = strtrim(siB(:,1)); + valsB = strtrim(siB(:,2)); + nB = numel(keysB); + strB = repmat({':'}, nB, 1); + setupB = strcat(keysB, strB, valsB); + + % renamed parameter aliases (BF version changes) + aliases = { ... + 'gyro_lowpass_type', 'gyro_lpf1_type'; ... + 'gyro_lowpass_hz', 'gyro_lpf1_static_hz'; ... + 'gyro_lowpass2_type', 'gyro_lpf2_type'; ... + 'gyro_lowpass2_hz', 'gyro_lpf2_static_hz'; ... + 'dterm_lowpass_type', 'dterm_lpf1_type'; ... + 'dterm_lowpass_hz', 'dterm_lpf1_static_hz'; ... + 'dterm_lowpass2_type', 'dterm_lpf2_type'; ... + 'dterm_lowpass2_hz', 'dterm_lpf2_static_hz'; ... + 'd_min', 'd_max'; ... + 'feedforward_weight', 'ff_weight'; ... + 'dshot_idle_value', 'motor_idle'; ... + 'gyro_to_use', 'gyro_enabled_bitmask'; ... + }; + + % key-based matching: for each row in A, find matching key in B + for i = 1:nA + kA = keysA{i}; + idxB = find(strcmp(keysB, kA)); + if isempty(idxB) + % try alias lookup + ai = find(strcmp(aliases(:,1), kA)); + if ~isempty(ai) + idxB = find(strcmp(keysB, aliases{ai(1),2})); + else + ai = find(strcmp(aliases(:,2), kA)); + if ~isempty(ai) + idxB = find(strcmp(keysB, aliases{ai(1),1})); + end + end + end + if isempty(idxB) + % param only in A + BGCol(i,:) = diffCol; + u(i) = true; + elseif ~strcmp(valsA{i}, valsB{idxB(1)}) + % same param, different value BGCol(i,:) = diffCol; u(i) = true; end end - for i = size(setupB,1)+1 : nA - BGCol(i,:) = diffCol; - u(i) = true; + + % mark B rows not in A + BGColB = repmat(th.panelBg, nB, 1); + uB = false(nB, 1); + for i = 1:nB + kB = keysB{i}; + idxA = find(strcmp(keysA, kB)); + if isempty(idxA) + ai = find(strcmp(aliases(:,1), kB)); + if ~isempty(ai) + idxA = find(strcmp(keysA, aliases{ai(1),2})); + else + ai = find(strcmp(aliases(:,2), kB)); + if ~isempty(ai) + idxA = find(strcmp(keysA, aliases{ai(1),1})); + end + end + end + if isempty(idxA) + BGColB(i,:) = diffCol; + uB(i) = true; + elseif ~strcmp(valsB{i}, valsA{idxA(1)}) + BGColB(i,:) = diffCol; + uB(i) = true; + end end - catch end delete(findobj(PSdisp, 'Type', 'uitable')); + diffFg = [1.0 .55 .55]; tbH = 0.88; if get(guiHandlesInfo.checkboxDIFF, 'Value') == 1 nDiff = sum(u); diffBG = repmat(diffCol, max(nDiff,1), 1); st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA(u)))]); - set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]); + set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{fA_}]); try set(st,'BackgroundColor', diffBG); catch, end - try set(st,'ForegroundColor', th.textPrimary); catch, end + try set(st,'ForegroundColor', diffFg); catch, end try set(st,'RowStriping', 'off'); catch, end - if Nfiles > 1 - st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(u)))]); - set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}); - try set(st,'BackgroundColor', diffBG); catch, end - try set(st,'ForegroundColor', th.textPrimary); catch, end + if Nfiles > 1 && exist('setupB','var') + nDiffB = sum(uB); + diffBGB = repmat(diffCol, max(nDiffB,1), 1); + st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB(uB)))]); + set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{fB_}); + try set(st,'BackgroundColor', diffBGB); catch, end + try set(st,'ForegroundColor', diffFg); catch, end try set(st,'RowStriping', 'off'); catch, end end else st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupA))]); - set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{get(guiHandlesInfo.FileNumDispA, 'Value')}]); + set(st,'units','normalized','Position',[.02 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', [fnameMaster{fA_}]); try set(st,'BackgroundColor', BGCol); catch, end try set(st,'ForegroundColor', th.textPrimary); catch, end try set(st,'RowStriping', 'off'); catch, end - if Nfiles > 1 + if Nfiles > 1 && exist('setupB','var') st = uitable(PSdisp,'ColumnWidth',{columnWidth},'ColumnFormat',{'char'},'Data',[cellstr(char(setupB))]); - set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{get(guiHandlesInfo.FileNumDispB, 'Value')}); - try set(st,'BackgroundColor', BGCol); catch, end + set(st,'units','normalized','Position',[.52 .02 .45 tbH],'FontSize',fontsz, 'ColumnName', fnameMaster{fB_}); + try set(st,'BackgroundColor', BGColB); catch, end try set(st,'ForegroundColor', th.textPrimary); catch, end try set(st,'RowStriping', 'off'); catch, end end end end - - \ No newline at end of file diff --git a/src/plot/PSfilterSim.m b/src/plot/PSfilterSim.m index c808516..cce7abe 100644 --- a/src/plot/PSfilterSim.m +++ b/src/plot/PSfilterSim.m @@ -47,6 +47,40 @@ function PSfilterSim(~, Fs, setupInfo) axNphase = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB3 colW rowH]); axNstep = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) rowB4 colW rowH]); +% Style all plot axes once (dark theme) +for ax_ = {axLmag, axLdelay, axLphase, axNmag, axNdelay, axNphase} + PSstyleAxes(ax_{1}, thm); + set(ax_{1}, 'XTickLabel', {}); % freq-domain rows: hide X ticks (shared axis) +end +for ax_ = {axLstep, axNstep} + PSstyleAxes(ax_{1}, thm); % row4: keep X tick labels +end + +% Pre-create pooled line objects: 16 per axes, reuse via setLine/hideLines +POOL = 16; +axAll8 = [axLmag axLdelay axLphase axLstep axNmag axNdelay axNphase axNstep]; +lnPool = cell(8, POOL); +for ai = 1:8 + hold(axAll8(ai), 'on'); + for li = 1:POOL + lnPool{ai, li} = line(axAll8(ai), NaN, NaN, 'Visible', 'off', 'HitTest', 'off'); + end +end +% text annotation pools: 8 per axes +TPOOL = 8; +txPool = cell(8, TPOOL); +for ai = 1:8 + for ti = 1:TPOOL + txPool{ai, ti} = text(NaN, NaN, '', 'Parent', axAll8(ai), 'Visible', 'off', ... + 'FontSize', fontsz-1, 'HitTest', 'off'); + end +end +% re-enable grid after hold+line creation (hold can reset grid in Octave) +for ai = 1:8, grid(axAll8(ai), 'on'); end +% axes indices for easy access +AX_LMAG=1; AX_LDLY=2; AX_LPH=3; AX_LSTP=4; +AX_NMAG=5; AX_NDLY=6; AX_NPH=7; AX_NSTP=8; + % Gradient frequency bars axBarL = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(1) barY colW barH], 'Tag', 'gradbar'); axBarN = axes('Parent', fig, 'Units', 'normalized', 'Position', [colL(2) barY colW barH], 'Tag', 'gradbar'); @@ -183,18 +217,19 @@ function PSfilterSim(~, Fs, setupInfo) h.combinelpf = uicontrol(fig, 'Style', 'checkbox', 'String', 'combine lpf', 'Value', 0, ... 'Units', 'normalized', 'Position', [x0 row halfW rh], ... 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); +row = row - rh - gap; +ddW_ = cW * 0.48; +h.row4mode = uicontrol(fig, 'Style', 'popupmenu', 'String', {'Step resp.','Impulse resp.','Signal Generator'}, 'Value', 1, ... + 'Units', 'normalized', 'Position', [x0 row ddW_ rh], ... + 'FontSize', fontsz, 'Callback', @(~,~) toggleStepRow()); h.addnoise = uicontrol(fig, 'Style', 'checkbox', 'String', 'Add noise', 'Value', 0, ... 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); row = row - rh - gap; -h.showstep = uicontrol(fig, 'Style', 'checkbox', 'String', 'Step resp.', 'Value', 1, ... - 'Units', 'normalized', 'Position', [x0 row halfW rh], ... - 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, ... - 'Callback', @(~,~) toggleStepRow()); h.showboth = uicontrol(fig, 'Style', 'checkbox', 'String', 'Show Both', 'Value', 0, ... - 'Units', 'normalized', 'Position', [x0+halfW row halfW rh], ... + 'Units', 'normalized', 'Position', [x0 row halfW rh], ... 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, 'Callback', cb); -row = row - rh - gap*2; +row = row - rh - gap; h.autoupd = uicontrol(fig, 'Style', 'checkbox', 'String', 'Auto Update', 'Value', 1, ... 'Units', 'normalized', 'Position', [x0 row halfW rh], ... 'FontSize', fontsz, 'BackgroundColor', bgc, 'ForegroundColor', fgc, ... @@ -208,6 +243,12 @@ function PSfilterSim(~, Fs, setupInfo) 'Units', 'normalized', 'Position', [x0 row cW rh], ... 'FontSize', fontsz, 'FontWeight', 'bold', ... 'BackgroundColor', bgc, 'ForegroundColor', thm.btnDash1, 'HorizontalAlignment', 'left'); +row = row - rh - gap*2; +h.copyCLI = uicontrol(fig, 'Style', 'pushbutton', 'String', 'Copy CLI', ... + 'Units', 'normalized', 'Position', [x0+halfW*.3 row halfW*1.3 rh+.004], ... + 'FontSize', fontsz, 'FontWeight', 'bold', ... + 'BackgroundColor', thm.btnBg, 'ForegroundColor', thm.textAccent, ... + 'Callback', @(~,~) copyCLI()); doUpdate(); @@ -226,7 +267,7 @@ function toggleStepRow() end function applyLayout() - sStep = get(h.showstep, 'Value'); + sStep = get(h.row4mode, 'Value') > 0; % always show row 4 sBoth = get(h.showboth, 'Value'); offscr = [-2 -2 .01 .01]; @@ -259,10 +300,10 @@ function applyLayout() end if sBoth - set(axNmag, 'Position', offscr); cla(axNmag); - set(axNdelay, 'Position', offscr); cla(axNdelay); - set(axNphase, 'Position', offscr); cla(axNphase); - set(axNstep, 'Position', offscr); cla(axNstep); + set(axNmag, 'Position', offscr); + set(axNdelay, 'Position', offscr); + set(axNphase, 'Position', offscr); + set(axNstep, 'Position', offscr); set(hTitleN, 'Visible', 'off'); set(axBarN, 'Position', offscr); set(axBarL, 'Position', [plotL barY plotR-plotL barH]); @@ -322,11 +363,29 @@ function doUpdate() combineLPF = get(h.combinelpf, 'Value'); addNoise = get(h.addnoise, 'Value'); showBoth = get(h.showboth, 'Value'); - showStep = get(h.showstep, 'Value'); + row4mode = get(h.row4mode, 'Value'); % 1=step, 2=impulse, 3=signal gen sigHzLo = readEdit(h.sig_start); sigHzHi = readEdit(h.sig_end); sigDur = max(0.1, readEditF(h.sig_dur)); + % partial update: skip unchanged column + curAll = [glpf1t glpf1f glpf2t glpf2f dlpf1t dlpf1f dlpf2t dlpf2f ... + gn1f gn1c gn2f gn2c dnf dnc rpmBase rpmNharm rpmQ nMotors ... + usedB useLog combineLPF addNoise showBoth row4mode sigHzLo sigHzHi round(sigDur*100)]; + curLPF = curAll(1:8); curNotch = curAll(9:18); + prev = getappdata(fig, 'prevAll'); + doLPF = true; doNotch = true; + if ~isempty(prev) && numel(prev) == numel(curAll) + lpfChanged = ~isequal(curLPF, prev(1:8)); + notchChanged = ~isequal(curNotch, prev(9:18)); + optsChanged = ~isequal(curAll(19:end), prev(19:end)); + if ~optsChanged && ~showBoth + if lpfChanged && ~notchChanged, doNotch = false; end + if notchChanged && ~lpfChanged, doLPF = false; end + end + end + setappdata(fig, 'prevAll', curAll); + types = {'pt1', 'biquad', 'pt2', 'pt3'}; Nfft = 4096; fVec = linspace(0, Fs/2, Nfft)'; @@ -385,20 +444,44 @@ function doUpdate() set(h.totalDelay, 'String', sprintf('Total Delay: %.3fms (LPF %.3f + Notch %.3f)', ... delayL + delayN, delayL, delayN)); - % Step responses - lpfStepMs = 4; - stepLenL = round(Fs * lpfStepMs / 1000); - stepInL = ones(stepLenL, 1); - tStepL = (0:stepLenL-1)' / Fs * 1000; - - notchStepMs = 100; - stepLenN = round(Fs * notchStepMs / 1000); - stepInN = ones(stepLenN, 1); - tStepN = (0:stepLenN-1)' / Fs * 1000; + % Row 4 input signals + if row4mode == 3 + % Signal Generator: chirp + sigLen = round(Fs * sigDur); + tSigL = (0:sigLen-1)' / Fs; + sigInL = localChirp(tSigL, sigHzLo, sigDur, sigHzHi); + tSigN = tSigL; sigInN = sigInL; + r4labelL = 'Duration (sec)'; r4ylabelL = 'Amplitude'; + r4labelN = 'Duration (sec)'; r4ylabelN = 'Amplitude'; + elseif row4mode == 2 + % Impulse response + lpfStepMs = 4; + stepLenL = round(Fs * lpfStepMs / 1000); + sigInL = zeros(stepLenL, 1); sigInL(1) = 1; + tSigL = (0:stepLenL-1)' / Fs * 1000; + notchStepMs = 100; + stepLenN = round(Fs * notchStepMs / 1000); + sigInN = zeros(stepLenN, 1); sigInN(1) = 1; + tSigN = (0:stepLenN-1)' / Fs * 1000; + r4labelL = 'Time (ms)'; r4ylabelL = 'Impulse Resp.'; + r4labelN = 'Time (ms)'; r4ylabelN = 'Impulse Resp.'; + else + % Step response + lpfStepMs = 4; + stepLenL = round(Fs * lpfStepMs / 1000); + sigInL = ones(stepLenL, 1); + tSigL = (0:stepLenL-1)' / Fs * 1000; + notchStepMs = 100; + stepLenN = round(Fs * notchStepMs / 1000); + sigInN = ones(stepLenN, 1); + tSigN = (0:stepLenN-1)' / Fs * 1000; + r4labelL = 'Time (ms)'; r4ylabelL = 'Step Resp.'; + r4labelN = 'Time (ms)'; r4ylabelN = 'Step Resp.'; + end - sL_g = applyLPF(applyLPF(stepInL, glpf2t, glpf2f, Fs), glpf1t, glpf1f, Fs); - sL_d = applyLPF(applyLPF(stepInL, dlpf2t, dlpf2f, Fs), dlpf1t, dlpf1f, Fs); - sN_g = applyNotch(applyNotch(stepInN, gn2f, gn2c, Fs), gn1f, gn1c, Fs); + sL_g = applyLPF(applyLPF(sigInL, glpf2t, glpf2f, Fs), glpf1t, glpf1f, Fs); + sL_d = applyLPF(applyLPF(sigInL, dlpf2t, dlpf2f, Fs), dlpf1t, dlpf1f, Fs); + sN_g = applyNotch(applyNotch(sigInN, gn2f, gn2c, Fs), gn1f, gn1c, Fs); for ri = 1:rpmNharm fc_rpm = rpmBase * ri; if fc_rpm > 0 && fc_rpm < Fs/2 @@ -407,29 +490,27 @@ function doUpdate() end end end - sN_d = applyNotch(stepInN, dnf, dnc, Fs); - % Per-RPM-harmonic steps (cascaded nMotors) + sN_d = applyNotch(sigInN, dnf, dnc, Fs); sN_rpm = cell(rpmNharm, 1); for ri = 1:rpmNharm fc_rpm = rpmBase * ri; if fc_rpm > 0 && fc_rpm < Fs/2 - tmp = stepInN; + tmp = sigInN; for mi = 1:nMotors, tmp = applyNotch_Q(tmp, fc_rpm, rpmQ, Fs); end sN_rpm{ri} = tmp; else - sN_rpm{ri} = stepInN; + sN_rpm{ri} = sigInN; end end - sN_gn1 = applyNotch(stepInN, gn1f, gn1c, Fs); - sN_gn2 = applyNotch(stepInN, gn2f, gn2c, Fs); + sN_gn1 = applyNotch(sigInN, gn1f, gn1c, Fs); + sN_gn2 = applyNotch(sigInN, gn2f, gn2c, Fs); - % Noisy step responses (for "Add noise" option) if addNoise noiseAmp = 0.03; - noisyL = stepInL + randn(stepLenL, 1) * noiseAmp; + noisyL = sigInL + randn(numel(sigInL), 1) * noiseAmp; nsL_g = applyLPF(applyLPF(noisyL, glpf2t, glpf2f, Fs), glpf1t, glpf1f, Fs); nsL_d = applyLPF(applyLPF(noisyL, dlpf2t, dlpf2f, Fs), dlpf1t, dlpf1f, Fs); - noisyN = stepInN + randn(stepLenN, 1) * noiseAmp; + noisyN = sigInN + randn(numel(sigInN), 1) * noiseAmp; nsN_g = applyNotch(applyNotch(noisyN, gn2f, gn2c, Fs), gn1f, gn1c, Fs); for ri = 1:rpmNharm fc_rpm = rpmBase * ri; @@ -450,6 +531,7 @@ function doUpdate() %% GRADIENT FREQUENCY BARS drawGradientBar(axBarL, fMax, [.2 .7 .8; .3 .9 .5], ... {glpf1f, glpf2f, dlpf1f, dlpf2f}, {colG*0.7, colG, colD*0.7, colD}, thm); + set(axBarL, 'ButtonDownFcn', @(~,~) barClick(axBarL, {h.glpf1_hz, h.glpf2_hz, h.dlpf1_hz, h.dlpf2_hz}, fMax)); notchFreqs = {}; notchCols = {}; for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); @@ -460,6 +542,7 @@ function doUpdate() if gn2f > 0, notchFreqs{end+1} = gn2f; notchCols{end+1} = colStaticN*0.85; end if dnf > 0, notchFreqs{end+1} = dnf; notchCols{end+1} = colD; end drawGradientBar(axBarN, fMax, [.8 .3 .2; .9 .7 .2], notchFreqs, notchCols, thm); + set(axBarN, 'ButtonDownFcn', @(~,~) barClick(axBarN, {h.gn1_hz, h.gn2_hz, h.dn_hz}, fMax)); %% TEST SIGNAL PSD (if sigHzHi > sigHzLo) if sigHzHi > sigHzLo && sigDur > 0 @@ -491,10 +574,10 @@ function doUpdate() ph_g2 = unwrap(angle(H_lpf2)) * 180/pi; ph_d1 = unwrap(angle(H_dlpf1)) * 180/pi; ph_d2 = unwrap(angle(H_dlpf2)) * 180/pi; - sL_g1 = applyLPF(stepInL, glpf1t, glpf1f, Fs); - sL_g2 = applyLPF(stepInL, glpf2t, glpf2f, Fs); - sL_d1 = applyLPF(stepInL, dlpf1t, dlpf1f, Fs); - sL_d2 = applyLPF(stepInL, dlpf2t, dlpf2f, Fs); + sL_g1 = applyLPF(sigInL, glpf1t, glpf1f, Fs); + sL_g2 = applyLPF(sigInL, glpf2t, glpf2f, Fs); + sL_d1 = applyLPF(sigInL, dlpf1t, dlpf1f, Fs); + sL_d2 = applyLPF(sigInL, dlpf2t, dlpf2f, Fs); g1on = glpf1t > 0 && glpf1f > 0; g2on = glpf2t > 0 && glpf2f > 0; @@ -503,152 +586,193 @@ function doUpdate() colNoise = [.9 .25 .25]; applyLayout(); + fF = fVec(fIdx); - %% LOWPASS COLUMN (+ notch overlay when showBoth) + if doLPF % --- LPF COLUMN --- - cla(axLmag); hold(axLmag, 'on'); + %% LOWPASS MAGNITUDE (pre-created lines) + li = 1; if combineLPF - plotFn(axLmag, fVec(fIdx), magY(H_gyroLPF(fIdx), usedB), 'Color', colG, 'LineWidth', 1.5); - plotFn(axLmag, fVec(fIdx), magY(H_dtermLPF(fIdx), usedB), 'Color', colD, 'LineWidth', 1.2); + li = setLine(AX_LMAG, li, fF, magY(H_gyroLPF(fIdx), usedB), colG, 1.5); + li = setLine(AX_LMAG, li, fF, magY(H_dtermLPF(fIdx), usedB), colD, 1.2); else - if g1on, plotFn(axLmag, fVec(fIdx), magY(H_lpf1(fIdx), usedB), 'Color', colG, 'LineWidth', 1.2); end - if g2on, plotFn(axLmag, fVec(fIdx), magY(H_lpf2(fIdx), usedB), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end - if d1on, plotFn(axLmag, fVec(fIdx), magY(H_dlpf1(fIdx), usedB), 'Color', colD, 'LineWidth', 1.0); end - if d2on, plotFn(axLmag, fVec(fIdx), magY(H_dlpf2(fIdx), usedB), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + if g1on, li = setLine(AX_LMAG, li, fF, magY(H_lpf1(fIdx), usedB), colG, 1.2); end + if g2on, li = setLine(AX_LMAG, li, fF, magY(H_lpf2(fIdx), usedB), colG, 1.2, '--'); end + if d1on, li = setLine(AX_LMAG, li, fF, magY(H_dlpf1(fIdx), usedB), colD, 1.0); end + if d2on, li = setLine(AX_LMAG, li, fF, magY(H_dlpf2(fIdx), usedB), colD, 1.0, '--'); end end if showBoth - plotFn(axLmag, fVec(fIdx), magY(H_gyroN(fIdx), usedB), 'Color', colCombN, 'LineWidth', 1.2); + li = setLine(AX_LMAG, li, fF, magY(H_gyroN(fIdx), usedB), colCombN, 1.2); for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); - plotFn(axLmag, fVec(fIdx), magY(H_rpm{ri}(fIdx), usedB), 'Color', rpmCols{ci}, 'LineWidth', 0.7); + li = setLine(AX_LMAG, li, fF, magY(H_rpm{ri}(fIdx), usedB), rpmCols{ci}, 0.7); end end fLo = xlimF(useLog, fMax); + refY = 0.707; if usedB, refY = -3; end + li = setLine(AX_LMAG, li, [fLo(1) fMax], [refY refY], thm.refLine3dB, 0.5, ':'); + % cutoff vertical lines (gyro + dterm) + magYL = [0 1.1]; if usedB, magYL = [-60 3]; end + if g1on && glpf1f > 0, li = setLine(AX_LMAG, li, [glpf1f glpf1f], magYL, colG*0.7, 0.5, '--'); end + if g2on && glpf2f > 0, li = setLine(AX_LMAG, li, [glpf2f glpf2f], magYL, colG, 0.5, '--'); end + if d1on && dlpf1f > 0, li = setLine(AX_LMAG, li, [dlpf1f dlpf1f], magYL, colD*0.7, 0.5, '--'); end + if d2on && dlpf2f > 0, li = setLine(AX_LMAG, li, [dlpf2f dlpf2f], magYL, colD, 0.5, '--'); end + hideRest(AX_LMAG, li); if usedB - line(axLmag, [fLo(1) fMax], [-3 -3], 'Color', thm.refLine3dB, 'LineStyle', ':', 'LineWidth', 0.5); - else - line(axLmag, [fLo(1) fMax], [0.707 0.707], 'Color', thm.refLine3dB, 'LineStyle', ':', 'LineWidth', 0.5); - end - hold(axLmag, 'off'); - PSstyleAxes(axLmag, thm); - if usedB - set(axLmag, 'XLim', xlimF(useLog, fMax), 'YLim', [-60 3], 'XTickLabel', {}); + set(axLmag, 'XLim', fLo, 'YLim', [-60 3], 'XTickLabel', {}); set(get(axLmag, 'YLabel'), 'String', 'Magnitude (dB)'); else - set(axLmag, 'XLim', xlimF(useLog, fMax), 'YLim', [0 1.1], 'XTickLabel', {}); + set(axLmag, 'XLim', fLo, 'YLim', [0 1.1], 'XTickLabel', {}); set(get(axLmag, 'YLabel'), 'String', 'Magnitude (abs)'); end - annotateLPF(axLmag, glpf1t, glpf1f, glpf2t, glpf2f, types, colG, fontsz, usedB); - - cla(axLdelay); hold(axLdelay, 'on'); - if combineLPF - plotFn(axLdelay, fVec(fIdx), gd_gL(fIdx), 'Color', colG, 'LineWidth', 1.5); - plotFn(axLdelay, fVec(fIdx), gd_dL(fIdx), 'Color', colD, 'LineWidth', 1.2); - else - if g1on, plotFn(axLdelay, fVec(fIdx), gd_g1(fIdx), 'Color', colG, 'LineWidth', 1.2); end - if g2on, plotFn(axLdelay, fVec(fIdx), gd_g2(fIdx), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end - if d1on, plotFn(axLdelay, fVec(fIdx), gd_d1(fIdx), 'Color', colD, 'LineWidth', 1.0); end - if d2on, plotFn(axLdelay, fVec(fIdx), gd_d2(fIdx), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + % annotations (text + cutoff lines via txPool) + ti = 1; + names_ = {'off', 'pt1', 'biquad', 'pt2', 'pt3'}; + if usedB, ay = -3; ayd = -6; else ay = 1.05; ayd = -0.08; end + if glpf1t > 0 && glpf1f > 0 + ti = setTxt(AX_LMAG, ti, glpf1f+10, ay, sprintf('%s: %dHz', names_{glpf1t+1}, glpf1f), colG*0.7); + ay = ay + ayd; end - if showBoth - plotFn(axLdelay, fVec(fIdx), gd_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.2); + if glpf2t > 0 && glpf2f > 0 + ti = setTxt(AX_LMAG, ti, glpf2f+10, ay, sprintf('%s: %dHz', names_{glpf2t+1}, glpf2f), colG); + ay = ay + ayd; end - hold(axLdelay, 'off'); - PSstyleAxes(axLdelay, thm); set(axLdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + if dlpf1t > 0 && dlpf1f > 0 + ti = setTxt(AX_LMAG, ti, dlpf1f+10, ay, sprintf('D %s: %dHz', names_{dlpf1t+1}, dlpf1f), colD*0.7); + ay = ay + ayd; + end + if dlpf2t > 0 && dlpf2f > 0 + ti = setTxt(AX_LMAG, ti, dlpf2f+10, ay, sprintf('D %s: %dHz', names_{dlpf2t+1}, dlpf2f), colD); + end + hideTxt(AX_LMAG, ti); + + %% LOWPASS DELAY (pre-created lines) + li = 1; if combineLPF + li = setLine(AX_LDLY, li, fF, gd_gL(fIdx), colG, 1.5); + li = setLine(AX_LDLY, li, fF, gd_dL(fIdx), colD, 1.2); gdMax = max([max(gd_gL(fIdx)) max(gd_dL(fIdx)) 0.3]) * 1.3; else allGdL = [0.3]; - if g1on, allGdL(end+1) = max(gd_g1(fIdx)); end - if g2on, allGdL(end+1) = max(gd_g2(fIdx)); end - if d1on, allGdL(end+1) = max(gd_d1(fIdx)); end - if d2on, allGdL(end+1) = max(gd_d2(fIdx)); end + if g1on, li = setLine(AX_LDLY, li, fF, gd_g1(fIdx), colG, 1.2); allGdL(end+1) = max(gd_g1(fIdx)); end + if g2on, li = setLine(AX_LDLY, li, fF, gd_g2(fIdx), colG, 1.2, '--'); allGdL(end+1) = max(gd_g2(fIdx)); end + if d1on, li = setLine(AX_LDLY, li, fF, gd_d1(fIdx), colD, 1.0); allGdL(end+1) = max(gd_d1(fIdx)); end + if d2on, li = setLine(AX_LDLY, li, fF, gd_d2(fIdx), colD, 1.0, '--'); allGdL(end+1) = max(gd_d2(fIdx)); end gdMax = max(allGdL) * 1.3; end + if showBoth + li = setLine(AX_LDLY, li, fF, gd_gN(fIdx), colCombN, 1.2); + end + % cutoff lines (gyro + dterm) + if g1on && glpf1f > 0, li = setLine(AX_LDLY, li, [glpf1f glpf1f], [0 gdMax], colG*0.7, 0.5, '--'); end + if g2on && glpf2f > 0, li = setLine(AX_LDLY, li, [glpf2f glpf2f], [0 gdMax], colG, 0.5, '--'); end + if d1on && dlpf1f > 0, li = setLine(AX_LDLY, li, [dlpf1f dlpf1f], [0 gdMax], colD*0.7, 0.5, '--'); end + if d2on && dlpf2f > 0, li = setLine(AX_LDLY, li, [dlpf2f dlpf2f], [0 gdMax], colD, 0.5, '--'); end + hideRest(AX_LDLY, li); + set(axLdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); if isfinite(gdMax) && gdMax > 0, set(axLdelay, 'YLim', [0 gdMax]); end set(get(axLdelay, 'YLabel'), 'String', 'Filter Delay (ms)'); - drawCutoffLines(axLdelay, g1on, glpf1f, g2on, glpf2f, colG); - yTxtL = gdMax * 0.92; - yTxtStp = gdMax * 0.17; + % delay annotations + ti = 1; + yTxtL = gdMax * 0.92; yTxtStp = gdMax * 0.17; if combineLPF - text(fMax*0.02, yTxtL, sprintf('gyro lpf: %.5fms', gd_gL(2)), 'Parent', axLdelay, ... - 'Color', colG, 'FontSize', fontsz-1); - text(fMax*0.02, yTxtL - yTxtStp, sprintf('dterm lpf: %.5fms', gd_dL(2)), 'Parent', axLdelay, ... - 'Color', colD, 'FontSize', fontsz-1); + ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL, sprintf('gyro lpf: %.5fms', gd_gL(2)), colG); + ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL-yTxtStp, sprintf('dterm lpf: %.5fms', gd_dL(2)), colD); else - if g1on, text(fMax*0.02, yTxtL, sprintf('gyro lpf1: %.5fms', gd_g1(2)), 'Parent', axLdelay, 'Color', colG*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if g2on, text(fMax*0.02, yTxtL, sprintf('gyro lpf2: %.5fms', gd_g2(2)), 'Parent', axLdelay, 'Color', colG, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if d1on, text(fMax*0.02, yTxtL, sprintf('dterm lpf1: %.5fms', gd_d1(2)), 'Parent', axLdelay, 'Color', colD*0.7, 'FontSize', fontsz-1); yTxtL = yTxtL - yTxtStp; end - if d2on, text(fMax*0.02, yTxtL, sprintf('dterm lpf2: %.5fms', gd_d2(2)), 'Parent', axLdelay, 'Color', colD, 'FontSize', fontsz-1); end + if g1on, ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL, sprintf('gyro lpf1: %.5fms', gd_g1(2)), colG*0.7); yTxtL=yTxtL-yTxtStp; end + if g2on, ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL, sprintf('gyro lpf2: %.5fms', gd_g2(2)), colG); yTxtL=yTxtL-yTxtStp; end + if d1on, ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL, sprintf('dterm lpf1: %.5fms', gd_d1(2)), colD*0.7); yTxtL=yTxtL-yTxtStp; end + if d2on, ti = setTxt(AX_LDLY, ti, fMax*0.02, yTxtL, sprintf('dterm lpf2: %.5fms', gd_d2(2)), colD); end end + hideTxt(AX_LDLY, ti); - cla(axLphase); hold(axLphase, 'on'); + %% LOWPASS PHASE (pre-created lines) + li = 1; if combineLPF ph_gL = unwrap(angle(H_gyroLPF)) * 180/pi; ph_dL = unwrap(angle(H_dtermLPF)) * 180/pi; - plotFn(axLphase, fVec(fIdx), ph_gL(fIdx), 'Color', colG, 'LineWidth', 1.5); - plotFn(axLphase, fVec(fIdx), ph_dL(fIdx), 'Color', colD, 'LineWidth', 1.2); + li = setLine(AX_LPH, li, fF, ph_gL(fIdx), colG, 1.5); + li = setLine(AX_LPH, li, fF, ph_dL(fIdx), colD, 1.2); else - if g1on, plotFn(axLphase, fVec(fIdx), ph_g1(fIdx), 'Color', colG, 'LineWidth', 1.2); end - if g2on, plotFn(axLphase, fVec(fIdx), ph_g2(fIdx), 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end - if d1on, plotFn(axLphase, fVec(fIdx), ph_d1(fIdx), 'Color', colD, 'LineWidth', 1.0); end - if d2on, plotFn(axLphase, fVec(fIdx), ph_d2(fIdx), 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + if g1on, li = setLine(AX_LPH, li, fF, ph_g1(fIdx), colG, 1.2); end + if g2on, li = setLine(AX_LPH, li, fF, ph_g2(fIdx), colG, 1.2, '--'); end + if d1on, li = setLine(AX_LPH, li, fF, ph_d1(fIdx), colD, 1.0); end + if d2on, li = setLine(AX_LPH, li, fF, ph_d2(fIdx), colD, 1.0, '--'); end end if showBoth - ph_gN = smooth(angle(H_gyroN1) * 180/pi, 9, 'moving'); - plotFn(axLphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.2); + ph_gN_ = smooth(angle(H_gyroN1) * 180/pi, 9, 'moving'); + li = setLine(AX_LPH, li, fF, ph_gN_(fIdx), colCombN, 1.2); end - hold(axLphase, 'off'); - PSstyleAxes(axLphase, thm); set(axLphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + if g1on && glpf1f > 0, li = setLine(AX_LPH, li, [glpf1f glpf1f], [-200 0], colG*0.7, 0.5, '--'); end + if g2on && glpf2f > 0, li = setLine(AX_LPH, li, [glpf2f glpf2f], [-200 0], colG, 0.5, '--'); end + if d1on && dlpf1f > 0, li = setLine(AX_LPH, li, [dlpf1f dlpf1f], [-200 0], colD*0.7, 0.5, '--'); end + if d2on && dlpf2f > 0, li = setLine(AX_LPH, li, [dlpf2f dlpf2f], [-200 0], colD, 0.5, '--'); end + hideRest(AX_LPH, li); + set(axLphase, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); set(get(axLphase, 'YLabel'), 'String', 'Phase Delay (deg)'); - drawCutoffLines(axLphase, g1on, glpf1f, g2on, glpf2f, colG); - - if showStep - cla(axLstep); - line(axLstep, [tStepL(1) tStepL(end)], [1 1], 'Color', colRef, 'LineWidth', 0.5, 'LineStyle', '--'); hold(axLstep, 'on'); + hideTxt(AX_LPH, 1); + + %% LOWPASS ROW 4 (pre-created lines) + li = 1; + if row4mode == 3 + li = setLine(AX_LSTP, li, tSigL, sigInL, [.5 .5 .5], 0.6); + li = setLine(AX_LSTP, li, tSigL, sL_g, [.95 .2 .2], 1.2); + hideRest(AX_LSTP, li); + set(axLstep, 'XLim', [0 sigDur], 'YLim', [-1.1 1.1]); + else + if row4mode == 1 + li = setLine(AX_LSTP, li, [tSigL(1) tSigL(end)], [1 1], colRef, 0.5, '--'); + end if combineLPF - plot(axLstep, tStepL, sL_g, 'Color', colG, 'LineWidth', 1.5); - plot(axLstep, tStepL, sL_d, 'Color', colD, 'LineWidth', 1.2); + li = setLine(AX_LSTP, li, tSigL, sL_g, colG, 1.5); + li = setLine(AX_LSTP, li, tSigL, sL_d, colD, 1.2); if addNoise - plot(axLstep, tStepL, nsL_g, 'Color', colNoise, 'LineWidth', 0.8); - plot(axLstep, tStepL, nsL_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); + li = setLine(AX_LSTP, li, tSigL, nsL_g, colNoise, 0.8); + li = setLine(AX_LSTP, li, tSigL, nsL_d, colNoise*0.7, 0.7); end else - if g1on, plot(axLstep, tStepL, sL_g1, 'Color', colG, 'LineWidth', 1.2); end - if g2on, plot(axLstep, tStepL, sL_g2, 'Color', colG, 'LineWidth', 1.2, 'LineStyle', '--'); end - if d1on, plot(axLstep, tStepL, sL_d1, 'Color', colD, 'LineWidth', 1.0); end - if d2on, plot(axLstep, tStepL, sL_d2, 'Color', colD, 'LineWidth', 1.0, 'LineStyle', '--'); end + if g1on, li = setLine(AX_LSTP, li, tSigL, sL_g1, colG, 1.2); end + if g2on, li = setLine(AX_LSTP, li, tSigL, sL_g2, colG, 1.2, '--'); end + if d1on, li = setLine(AX_LSTP, li, tSigL, sL_d1, colD, 1.0); end + if d2on, li = setLine(AX_LSTP, li, tSigL, sL_d2, colD, 1.0, '--'); end if addNoise - plot(axLstep, tStepL, nsL_g, 'Color', colNoise, 'LineWidth', 0.8); - plot(axLstep, tStepL, nsL_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); + li = setLine(AX_LSTP, li, tSigL, nsL_g, colNoise, 0.8); + li = setLine(AX_LSTP, li, tSigL, nsL_d, colNoise*0.7, 0.7); end end - hold(axLstep, 'off'); - PSstyleAxes(axLstep, thm); set(axLstep, 'XLim', [0 lpfStepMs], 'YLim', [-0.05 1.15]); - xlabel(axLstep, 'Time (ms)', 'Color', thm.textPrimary); - set(get(axLstep, 'YLabel'), 'String', 'Step Resp.'); + hideRest(AX_LSTP, li); + if row4mode == 1 + set(axLstep, 'XLim', [tSigL(1) tSigL(end)], 'YLim', [-0.05 1.15]); + else + set(axLstep, 'XLim', [tSigL(1) tSigL(end)]); + end end + xlabel(axLstep, r4labelL, 'Color', thm.textPrimary); + set(get(axLstep, 'YLabel'), 'String', r4ylabelL); + hideTxt(AX_LSTP, 1); + end % if doLPF %% NOTCH COLUMN (skip when showBoth) - if ~showBoth - cla(axNmag); hold(axNmag, 'on'); + if doNotch && ~showBoth + gridC = thm.gridColor; + + %% NOTCH MAGNITUDE + li = 1; for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); - plotFn(axNmag, fVec(fIdx), magY(H_rpm{ri}(fIdx), usedB), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + li = setLine(AX_NMAG, li, fF, magY(H_rpm{ri}(fIdx), usedB), rpmCols{ci}, 0.9); end - if gn1f > 0 - plotFn(axNmag, fVec(fIdx), magY(H_gn1(fIdx), usedB), 'Color', colStaticN, 'LineWidth', 0.8); - end - if gn2f > 0 - plotFn(axNmag, fVec(fIdx), magY(H_gn2(fIdx), usedB), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); - end - plotFn(axNmag, fVec(fIdx), magY(H_gyroN(fIdx), usedB), 'Color', colCombN, 'LineWidth', 1.5); - if dnf > 0 - plotFn(axNmag, fVec(fIdx), magY(H_dtermN(fIdx), usedB), 'Color', colD, 'LineWidth', 1.2); - end - hold(axNmag, 'off'); - PSstyleAxes(axNmag, thm); + if gn1f > 0, li = setLine(AX_NMAG, li, fF, magY(H_gn1(fIdx), usedB), colStaticN, 0.8); end + if gn2f > 0, li = setLine(AX_NMAG, li, fF, magY(H_gn2(fIdx), usedB), colStaticN*0.85, 0.8, '--'); end + li = setLine(AX_NMAG, li, fF, magY(H_gyroN(fIdx), usedB), colCombN, 1.5); + if dnf > 0, li = setLine(AX_NMAG, li, fF, magY(H_dtermN(fIdx), usedB), colD, 1.2); end + % notch vertical lines + for ri = 1:rpmNharm, fc=rpmBase*ri; if fc>0, li=setLine(AX_NMAG,li,[fc fc],[0 1.1],gridC,0.5,':'); end; end + if gn1f>0, li=setLine(AX_NMAG,li,[gn1f gn1f],[0 1.1],colStaticN,0.5,'--'); end + if gn2f>0, li=setLine(AX_NMAG,li,[gn2f gn2f],[0 1.1],colStaticN*0.85,0.5,'--'); end + if dnf>0, li=setLine(AX_NMAG,li,[dnf dnf],[0 1.1],colD,0.5,'--'); end + hideRest(AX_NMAG, li); if usedB set(axNmag, 'XLim', xlimF(useLog, fMax), 'YLim', [-40 3], 'XTickLabel', {}); set(get(axNmag, 'YLabel'), 'String', 'Magnitude (dB)'); @@ -656,110 +780,119 @@ function doUpdate() set(axNmag, 'XLim', xlimF(useLog, fMax), 'YLim', [0 1.1], 'XTickLabel', {}); set(get(axNmag, 'YLabel'), 'String', 'Magnitude (abs)'); end - annotateNotch(axNmag, gn1f, gn2f, dnf, rpmBase, rpmNharm, rpmCols, colStaticN, colCombN, colD, fontsz, usedB); + % annotations + ti = 1; + if usedB, ay0=-5; ayd=-5; else ay0=0.85; ayd=-0.07; end + ay = ay0; + for ri = 1:rpmNharm + ci = min(ri, numel(rpmCols)); fc=rpmBase*ri; + ti = setTxt(AX_NMAG, ti, fc+10, ay, sprintf('RPM: %dHz', fc), rpmCols{ci}); ay=ay+ayd; + end + if gn1f>0, ti=setTxt(AX_NMAG,ti,gn1f+10,ay,sprintf('N1: %dHz',gn1f),colStaticN); ay=ay+ayd; end + if gn2f>0, ti=setTxt(AX_NMAG,ti,gn2f+10,ay,sprintf('N2: %dHz',gn2f),colStaticN*0.85); ay=ay+ayd; end + if dnf>0, ti=setTxt(AX_NMAG,ti,dnf+10,ay,sprintf('D: %dHz',dnf),colD); end + hideTxt(AX_NMAG, ti); - cla(axNdelay); hold(axNdelay, 'on'); - % per-single-motor delay (not cascaded nMotors) + %% NOTCH DELAY + li = 1; rpmGd1 = cell(rpmNharm, 1); for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); rpmGd1{ri} = smooth(-gradient(unwrap(angle(H_rpm1{ri}))) ./ dw * 1000, 51, 'moving'); - plotFn(axNdelay, fVec(fIdx), rpmGd1{ri}(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + li = setLine(AX_NDLY, li, fF, rpmGd1{ri}(fIdx), rpmCols{ci}, 0.9); end - % static notch delay curves if gn1f > 0 gd_gn1_v = smooth(-gradient(unwrap(angle(H_gn1))) ./ dw * 1000, 51, 'moving'); - plotFn(axNdelay, fVec(fIdx), gd_gn1_v(fIdx), 'Color', colStaticN, 'LineWidth', 0.8); + li = setLine(AX_NDLY, li, fF, gd_gn1_v(fIdx), colStaticN, 0.8); end if gn2f > 0 gd_gn2_v = smooth(-gradient(unwrap(angle(H_gn2))) ./ dw * 1000, 51, 'moving'); - plotFn(axNdelay, fVec(fIdx), gd_gn2_v(fIdx), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); - end - plotFn(axNdelay, fVec(fIdx), gd_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); - if dnf > 0 - plotFn(axNdelay, fVec(fIdx), gd_dN(fIdx), 'Color', colD, 'LineWidth', 1.2); + li = setLine(AX_NDLY, li, fF, gd_gn2_v(fIdx), colStaticN*0.85, 0.8, '--'); end - hold(axNdelay, 'off'); - PSstyleAxes(axNdelay, thm); set(axNdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); + li = setLine(AX_NDLY, li, fF, gd_gN(fIdx), colCombN, 1.5); + if dnf > 0, li = setLine(AX_NDLY, li, fF, gd_dN(fIdx), colD, 1.2); end + % vertical lines + for ri=1:rpmNharm, fc=rpmBase*ri; if fc>0, li=setLine(AX_NDLY,li,[fc fc],[-1 1],gridC,0.5,':'); end; end + if gn1f>0, li=setLine(AX_NDLY,li,[gn1f gn1f],[-1 1],colStaticN,0.5,'--'); end + if gn2f>0, li=setLine(AX_NDLY,li,[gn2f gn2f],[-1 1],colStaticN*0.85,0.5,'--'); end + if dnf>0, li=setLine(AX_NDLY,li,[dnf dnf],[-1 1],colD,0.5,'--'); end + hideRest(AX_NDLY, li); gdMaxN = max(abs(gd_gN(2)) * 3, 0.5); if isfinite(gdMaxN), set(axNdelay, 'YLim', [-gdMaxN gdMaxN]); end + set(axNdelay, 'XLim', xlimF(useLog, fMax), 'XTickLabel', {}); set(get(axNdelay, 'YLabel'), 'String', 'Filter Delay (ms)'); - nAnnot = rpmNharm; - if gn1f > 0, nAnnot = nAnnot + 1; end - if gn2f > 0, nAnnot = nAnnot + 1; end - if dnf > 0, nAnnot = nAnnot + 1; end - nAnnot = max(nAnnot + 1, 2); - yTxt = gdMaxN * 0.9; - yStN = 2 * gdMaxN * 0.9 / nAnnot; - text(fMax*0.3, yTxt, sprintf('combined: %.4fms', gd_gN(2)), ... - 'Parent', axNdelay, 'Color', colCombN, 'FontSize', fontsz-1); - yTxt = yTxt - yStN; + % annotations + ti = 1; nAn = max(rpmNharm+2, 2); + yTxt = gdMaxN*0.9; yStN = 2*gdMaxN*0.9/nAn; + ti = setTxt(AX_NDLY, ti, fMax*0.3, yTxt, sprintf('combined: %.4fms', gd_gN(2)), colCombN); yTxt=yTxt-yStN; for ri = 1:rpmNharm - ci = min(ri, numel(rpmCols)); - fc_rpm = rpmBase * ri; - text(fMax*0.3, yTxt, sprintf('RPM %dHz: %.5fms', fc_rpm, rpmGd1{ri}(2)), ... - 'Parent', axNdelay, 'Color', rpmCols{ci}, 'FontSize', fontsz-1); - yTxt = yTxt - yStN; - end - if gn1f > 0 - text(fMax*0.3, yTxt, sprintf('N1 %dHz: %.5fms', gn1f, gd_gn1_v(2)), ... - 'Parent', axNdelay, 'Color', colStaticN, 'FontSize', fontsz-1); - yTxt = yTxt - yStN; - end - if gn2f > 0 - text(fMax*0.3, yTxt, sprintf('N2 %dHz: %.5fms', gn2f, gd_gn2_v(2)), ... - 'Parent', axNdelay, 'Color', colStaticN*0.85, 'FontSize', fontsz-1); - yTxt = yTxt - yStN; - end - if dnf > 0 - text(fMax*0.3, yTxt, sprintf('D %dHz: %.4fms', dnf, gd_dN(2)), ... - 'Parent', axNdelay, 'Color', colD, 'FontSize', fontsz-1); + ci = min(ri, numel(rpmCols)); fc=rpmBase*ri; + ti = setTxt(AX_NDLY, ti, fMax*0.3, yTxt, sprintf('RPM %dHz: %.5fms', fc, rpmGd1{ri}(2)), rpmCols{ci}); yTxt=yTxt-yStN; end + if gn1f>0, ti=setTxt(AX_NDLY,ti,fMax*0.3,yTxt,sprintf('N1 %dHz: %.5fms',gn1f,gd_gn1_v(2)),colStaticN); yTxt=yTxt-yStN; end + if gn2f>0, ti=setTxt(AX_NDLY,ti,fMax*0.3,yTxt,sprintf('N2 %dHz: %.5fms',gn2f,gd_gn2_v(2)),colStaticN*0.85); yTxt=yTxt-yStN; end + if dnf>0, ti=setTxt(AX_NDLY,ti,fMax*0.3,yTxt,sprintf('D %dHz: %.4fms',dnf,gd_dN(2)),colD); end + hideTxt(AX_NDLY, ti); - cla(axNphase); hold(axNphase, 'on'); + %% NOTCH PHASE + li = 1; for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); ph_ri = smooth(angle(H_rpm1{ri}) * 180/pi, 9, 'moving'); - plotFn(axNphase, fVec(fIdx), ph_ri(fIdx), 'Color', rpmCols{ci}, 'LineWidth', 0.9); + li = setLine(AX_NPH, li, fF, ph_ri(fIdx), rpmCols{ci}, 0.9); end if gn1f > 0 - ph_gn1 = smooth(angle(H_gn1) * 180/pi, 9, 'moving'); - plotFn(axNphase, fVec(fIdx), ph_gn1(fIdx), 'Color', colStaticN, 'LineWidth', 0.8); + ph_gn1_ = smooth(angle(H_gn1) * 180/pi, 9, 'moving'); + li = setLine(AX_NPH, li, fF, ph_gn1_(fIdx), colStaticN, 0.8); end if gn2f > 0 - ph_gn2 = smooth(angle(H_gn2) * 180/pi, 9, 'moving'); - plotFn(axNphase, fVec(fIdx), ph_gn2(fIdx), 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); + ph_gn2_ = smooth(angle(H_gn2) * 180/pi, 9, 'moving'); + li = setLine(AX_NPH, li, fF, ph_gn2_(fIdx), colStaticN*0.85, 0.8, '--'); end ph_gN = smooth(angle(H_gyroN1) * 180/pi, 9, 'moving'); - ph_dN = smooth(angle(H_dtermN) * 180/pi, 9, 'moving'); - plotFn(axNphase, fVec(fIdx), ph_gN(fIdx), 'Color', colCombN, 'LineWidth', 1.5); - if dnf > 0 - plotFn(axNphase, fVec(fIdx), ph_dN(fIdx), 'Color', colD, 'LineWidth', 1.2); - end - hold(axNphase, 'off'); - PSstyleAxes(axNphase, thm); set(axNphase, 'XLim', xlimF(useLog, fMax), 'YLim', [-90 90], 'XTickLabel', {}); + ph_dN_ = smooth(angle(H_dtermN) * 180/pi, 9, 'moving'); + li = setLine(AX_NPH, li, fF, ph_gN(fIdx), colCombN, 1.5); + if dnf > 0, li = setLine(AX_NPH, li, fF, ph_dN_(fIdx), colD, 1.2); end + for ri=1:rpmNharm, fc=rpmBase*ri; if fc>0, li=setLine(AX_NPH,li,[fc fc],[-90 90],gridC,0.5,':'); end; end + if gn1f>0, li=setLine(AX_NPH,li,[gn1f gn1f],[-90 90],colStaticN,0.5,'--'); end + if gn2f>0, li=setLine(AX_NPH,li,[gn2f gn2f],[-90 90],colStaticN*0.85,0.5,'--'); end + if dnf>0, li=setLine(AX_NPH,li,[dnf dnf],[-90 90],colD,0.5,'--'); end + hideRest(AX_NPH, li); + set(axNphase, 'XLim', xlimF(useLog, fMax), 'YLim', [-90 90], 'XTickLabel', {}); set(get(axNphase, 'YLabel'), 'String', 'Phase Delay (deg)'); - - if showStep - cla(axNstep); - line(axNstep, [tStepN(1) tStepN(end)], [1 1], 'Color', colRef, 'LineWidth', 0.5, 'LineStyle', '--'); hold(axNstep, 'on'); + hideTxt(AX_NPH, 1); + + %% NOTCH ROW 4 + li = 1; + if row4mode == 3 + li = setLine(AX_NSTP, li, tSigN, sigInN, [.5 .5 .5], 0.6); + li = setLine(AX_NSTP, li, tSigN, sN_g, [.95 .2 .2], 1.2); + hideRest(AX_NSTP, li); + set(axNstep, 'XLim', [0 sigDur], 'YLim', [-1.1 1.1]); + else + if row4mode == 1, li = setLine(AX_NSTP, li, [tSigN(1) tSigN(end)], [1 1], colRef, 0.5, '--'); end for ri = 1:rpmNharm ci = min(ri, numel(rpmCols)); - plot(axNstep, tStepN, sN_rpm{ri}, 'Color', rpmCols{ci}, 'LineWidth', 0.8); + li = setLine(AX_NSTP, li, tSigN, sN_rpm{ri}, rpmCols{ci}, 0.8); end - if gn1f > 0, plot(axNstep, tStepN, sN_gn1, 'Color', colStaticN, 'LineWidth', 0.8); end - if gn2f > 0, plot(axNstep, tStepN, sN_gn2, 'Color', colStaticN*0.85, 'LineWidth', 0.8, 'LineStyle', '--'); end - plot(axNstep, tStepN, sN_g, 'Color', colCombN, 'LineWidth', 1.5); - if dnf > 0, plot(axNstep, tStepN, sN_d, 'Color', colD, 'LineWidth', 1.2); end + if gn1f > 0, li = setLine(AX_NSTP, li, tSigN, sN_gn1, colStaticN, 0.8); end + if gn2f > 0, li = setLine(AX_NSTP, li, tSigN, sN_gn2, colStaticN*0.85, 0.8, '--'); end + li = setLine(AX_NSTP, li, tSigN, sN_g, colCombN, 1.5); + if dnf > 0, li = setLine(AX_NSTP, li, tSigN, sN_d, colD, 1.2); end if addNoise - plot(axNstep, tStepN, nsN_g, 'Color', colNoise, 'LineWidth', 0.8); - if dnf > 0, plot(axNstep, tStepN, nsN_d, 'Color', colNoise*0.7, 'LineWidth', 0.7); end + li = setLine(AX_NSTP, li, tSigN, nsN_g, colNoise, 0.8); + if dnf > 0, li = setLine(AX_NSTP, li, tSigN, nsN_d, colNoise*0.7, 0.7); end + end + hideRest(AX_NSTP, li); + if row4mode == 1 + set(axNstep, 'XLim', [tSigN(1) tSigN(end)], 'YLim', [0.8 1.2]); + else + set(axNstep, 'XLim', [tSigN(1) tSigN(end)]); end - hold(axNstep, 'off'); - PSstyleAxes(axNstep, thm); set(axNstep, 'XLim', [tStepN(1) tStepN(end)], 'YLim', [0.8 1.2]); - xlabel(axNstep, 'Time (ms)', 'Color', thm.textPrimary); - set(get(axNstep, 'YLabel'), 'String', 'Step Resp.'); end + xlabel(axNstep, r4labelN, 'Color', thm.textPrimary); + set(get(axNstep, 'YLabel'), 'String', r4ylabelN); + hideTxt(AX_NSTP, 1); end % Set XScale+XLim atomically @@ -772,6 +905,93 @@ function doUpdate() warning(wstate); end + function copyCLI() + typeNames = {'PT1', 'BIQUAD', 'PT2', 'PT3'}; + lines = {}; + lines{end+1} = '# PIDscope Filter Sim -> BF CLI'; + lines{end+1} = '# Gyro Lowpass'; + t = cliTypeIdx(h.glpf1_type); + lines{end+1} = sprintf('set gyro_lpf1_type = %s', typeNames{max(t,1)}); + lines{end+1} = sprintf('set gyro_lpf1_static_hz = %d', cliHz(h.glpf1_hz, t)); + t = cliTypeIdx(h.glpf2_type); + lines{end+1} = sprintf('set gyro_lpf2_type = %s', typeNames{max(t,1)}); + lines{end+1} = sprintf('set gyro_lpf2_static_hz = %d', cliHz(h.glpf2_hz, t)); + lines{end+1} = '# Gyro Notch'; + lines{end+1} = sprintf('set gyro_notch1_hz = %d', readEdit(h.gn1_hz)); + lines{end+1} = sprintf('set gyro_notch1_cutoff = %d', readEdit(h.gn1_cut)); + lines{end+1} = sprintf('set gyro_notch2_hz = %d', readEdit(h.gn2_hz)); + lines{end+1} = sprintf('set gyro_notch2_cutoff = %d', readEdit(h.gn2_cut)); + lines{end+1} = '# D-term Lowpass'; + t = cliTypeIdx(h.dlpf1_type); + lines{end+1} = sprintf('set dterm_lpf1_type = %s', typeNames{max(t,1)}); + lines{end+1} = sprintf('set dterm_lpf1_static_hz = %d', cliHz(h.dlpf1_hz, t)); + t = cliTypeIdx(h.dlpf2_type); + lines{end+1} = sprintf('set dterm_lpf2_type = %s', typeNames{max(t,1)}); + lines{end+1} = sprintf('set dterm_lpf2_static_hz = %d', cliHz(h.dlpf2_hz, t)); + lines{end+1} = '# D-term Notch'; + lines{end+1} = sprintf('set dterm_notch_hz = %d', readEdit(h.dn_hz)); + lines{end+1} = sprintf('set dterm_notch_cutoff = %d', readEdit(h.dn_cut)); + lines{end+1} = 'save'; + cliText = strjoin(lines, char(10)); + ok = copyToClipboard(cliText); + if ok + set(h.copyCLI, 'String', 'Copied!'); + else + showCLIDialog(cliText); + end + end + + function t = cliTypeIdx(hPopup) + v = get(hPopup, 'Value'); + t = max(v - 1, 0); + end + + function hz = cliHz(hEdit, typeVal) + if typeVal == 0, hz = 0; return; end + hz = readEdit(hEdit); + end + + function idx = setLine(axi, idx, x, y, col, lw, ls) + % update pre-created line from pool + if nargin < 7, ls = '-'; end + set(lnPool{axi, idx}, 'XData', x, 'YData', y, 'Color', col, ... + 'LineWidth', lw, 'LineStyle', ls, 'Visible', 'on'); + idx = idx + 1; + end + + function hideRest(axi, fromIdx) + for li = fromIdx:POOL + set(lnPool{axi, li}, 'Visible', 'off', 'XData', NaN, 'YData', NaN); + end + end + + function idx = setTxt(axi, idx, x, y, str, col) + set(txPool{axi, idx}, 'Position', [x y 0], 'String', str, 'Color', col, 'Visible', 'on'); + idx = idx + 1; + end + + function hideTxt(axi, fromIdx) + for ti = fromIdx:TPOOL + set(txPool{axi, ti}, 'Visible', 'off', 'String', ''); + end + end + + function barClick(ax, edits, fMax) + if isempty(edits), return; end + cp = get(ax, 'CurrentPoint'); + xClick = max(0, min(fMax, round(cp(1,1)))); + % find nearest edit box by current value + bestDist = inf; bestIdx = 1; + for ei = 1:numel(edits) + ev = str2double(get(edits{ei}, 'String')); + if isnan(ev), ev = 0; end + d = abs(ev - xClick); + if d < bestDist, bestDist = d; bestIdx = ei; end + end + set(edits{bestIdx}, 'String', int2str(xClick)); + autoUpdate(); + end + end %% helpers @@ -837,8 +1057,19 @@ function doUpdate() function drawCutoffLines(ax, f1on, f1, f2on, f2, col) yl = get(ax, 'YLim'); - if f1on && f1 > 0, line(ax, [f1 f1], yl, 'Color', col*0.7, 'LineStyle', '--', 'LineWidth', 0.5); end - if f2on && f2 > 0, line(ax, [f2 f2], yl, 'Color', col, 'LineStyle', '--', 'LineWidth', 0.5); end + if f1on && f1 > 0, line(ax, [f1 f1], yl, 'Color', col*0.7, 'LineStyle', '--', 'LineWidth', 0.5, 'HitTest', 'off'); end + if f2on && f2 > 0, line(ax, [f2 f2], yl, 'Color', col, 'LineStyle', '--', 'LineWidth', 0.5, 'HitTest', 'off'); end +end + +function drawNotchLines(ax, rpmBase, rpmNharm, gn1f, gn2f, dnf, gridCol) + yl = get(ax, 'YLim'); + for ri = 1:rpmNharm + fc = rpmBase * ri; + if fc > 0, line(ax, [fc fc], yl, 'Color', gridCol, 'LineStyle', ':', 'LineWidth', 0.5, 'HitTest', 'off'); end + end + if gn1f > 0, line(ax, [gn1f gn1f], yl, 'Color', gridCol, 'LineStyle', '--', 'LineWidth', 0.5, 'HitTest', 'off'); end + if gn2f > 0, line(ax, [gn2f gn2f], yl, 'Color', gridCol, 'LineStyle', '--', 'LineWidth', 0.5, 'HitTest', 'off'); end + if dnf > 0, line(ax, [dnf dnf], yl, 'Color', gridCol, 'LineStyle', '--', 'LineWidth', 0.5, 'HitTest', 'off'); end end function annotateLPF(ax, t1, f1, t2, f2, ~, col, fsz, usedB) @@ -917,7 +1148,7 @@ function mkSection(fig, txt, cpL, row, cpW, rh, bgc, col, fsz) end function [hCenter, hCutoff, rowOut] = mkNotchPair(fig, x0, row, rh, gap, cW, bgc, ibc, ifc, lc, initCenter, initCutoff, cb, fsz) - halfW = cW / 2; lblW = .03; edW = halfW - lblW - .005; + halfW = cW / 2; lblW = .03; edW = halfW - lblW - .01; uicontrol(fig, 'Style', 'text', 'String', 'Ctr:', ... 'Units', 'normalized', 'Position', [x0 row lblW rh], ... 'FontSize', fsz, 'BackgroundColor', bgc, 'ForegroundColor', lc, 'HorizontalAlignment', 'right'); @@ -950,15 +1181,18 @@ function drawGradientBar(ax, fMax, gradCols, markerFreqs, markerCols, thm) Ngr = 256; grad = zeros(1, Ngr, 3); for ch = 1:3, grad(1,:,ch) = linspace(gradCols(1,ch), gradCols(2,ch), Ngr); end - imagesc(ax, [0 fMax], [0 1], grad); + hi = imagesc(ax, [0 fMax], [0 1], grad); + set(hi, 'HitTest', 'off'); set(ax, 'YTick', [], 'XTick', [], 'XLim', [0 fMax], 'YLim', [0 1], 'Box', 'on'); set(ax, 'XColor', thm.axesFg, 'YColor', thm.axesFg); hold(ax, 'on'); for k = 1:numel(markerFreqs) f = markerFreqs{k}; if f > 0 && f <= fMax - line(ax, [f f], [0 1], 'Color', markerCols{k}, 'LineWidth', 2); - plot(ax, f, 0.5, 'v', 'Color', markerCols{k}, 'MarkerSize', 5, 'MarkerFaceColor', markerCols{k}); + line(ax, [f f], [0 1], 'Color', markerCols{k}, 'LineWidth', 2, 'HitTest', 'off'); + % slider handle: triangle top + bottom + plot(ax, f, 0.95, 'v', 'Color', markerCols{k}, 'MarkerSize', 7, 'MarkerFaceColor', markerCols{k}, 'HitTest', 'off'); + plot(ax, f, 0.05, '^', 'Color', markerCols{k}, 'MarkerSize', 7, 'MarkerFaceColor', markerCols{k}, 'HitTest', 'off'); end end hold(ax, 'off'); @@ -972,3 +1206,41 @@ function drawGradientBar(ax, fMax, gradCols, markerFreqs, markerCols, thm) phase = 2*pi * f0 * (beta.^t - 1) / log(beta); y = sin(phase); end + +function ok = copyToClipboard(str) + ok = false; + tmpf = [tempname '.txt']; + fid = fopen(tmpf, 'w'); fprintf(fid, '%s', str); fclose(fid); + if ismac() + [st, ~] = system(sprintf('pbcopy < %s 2>&1', tmpf)); + ok = (st == 0); + elseif ispc() + [st, ~] = system(sprintf('clip < %s 2>&1', tmpf)); + ok = (st == 0); + else + cmds = {'xclip -selection clipboard', 'xsel --clipboard --input', 'wl-copy'}; + for k = 1:numel(cmds) + [st, ~] = system(sprintf('%s < %s 2>&1', cmds{k}, tmpf)); + if st == 0, ok = true; break; end + end + end + delete(tmpf); +end + +function showCLIDialog(cliText) + screensz = get(0, 'ScreenSize'); + dlg = figure('Name', 'BF CLI Commands', 'NumberTitle', 'off', ... + 'Color', [.15 .15 .15], ... + 'Position', round([screensz(3)*.3 screensz(4)*.25 460 380])); + uicontrol(dlg, 'Style', 'text', 'String', 'Select All + Copy:', ... + 'Units', 'normalized', 'Position', [.05 .90 .9 .07], ... + 'FontSize', 11, 'BackgroundColor', [.15 .15 .15], 'ForegroundColor', [.9 .9 .9]); + uicontrol(dlg, 'Style', 'edit', 'Max', 100, 'String', cliText, ... + 'Units', 'normalized', 'Position', [.05 .12 .9 .76], ... + 'HorizontalAlignment', 'left', 'FontName', 'Monospace', 'FontSize', 10, ... + 'BackgroundColor', [.1 .1 .1], 'ForegroundColor', [.9 .9 .9]); + uicontrol(dlg, 'Style', 'pushbutton', 'String', 'Close', ... + 'Units', 'normalized', 'Position', [.35 .02 .3 .08], ... + 'FontSize', 11, 'BackgroundColor', [.3 .3 .3], 'ForegroundColor', [.9 .9 .9], ... + 'Callback', @(~,~) close(dlg)); +end diff --git a/src/util/PStheme.m b/src/util/PStheme.m index f2df371..a1605a4 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -59,7 +59,7 @@ th.periodMarker = [.95 .20 .20]; % diff highlight (Setup Info) -th.diffBg = [.45 .18 .18]; +th.diffBg = [.30 .30 .32]; % checkbox / input bg th.checkBg = [.18 .18 .20]; @@ -80,7 +80,7 @@ % filter section headers th.secNotch = [1.0 .70 .30]; % amber/orange th.secDtermLPF = [.40 1.0 .40]; % green -th.secDtermNotch = [1.0 .50 .50]; % salmon +th.secDtermNotch = [.40 .90 .40]; % green (matches D-term plot curves) th.refLine3dB = [.60 .60 .20]; % -3dB / 0.707 reference % Bode / chirp analysis diff --git a/tests/test_PSfilterSim.m b/tests/test_PSfilterSim.m index 5a7ac2a..2726227 100644 --- a/tests/test_PSfilterSim.m +++ b/tests/test_PSfilterSim.m @@ -115,3 +115,77 @@ %! [~, ifc] = min(abs(f - fc)); %! ph_deg = angle(H(ifc)) * 180/pi; %! assert(ph_deg, -45, 3); + +%!test +%! % Emuflight per-axis keys: dterm_lowpass_hz_roll, gyro_lowpass_hz_roll +%! si = { +%! 'Firmware revision', 'EmuFlight 0.4.1 HELIOSPRING'; +%! 'gyro_lowpass_type', '0'; +%! 'gyro_lowpass_hz_roll', '200'; +%! 'gyro_lowpass_hz_pitch', '200'; +%! 'gyro_lowpass_hz_yaw', '200'; +%! 'gyro_lowpass2_type', '0'; +%! 'gyro_lowpass2_hz_roll', '150'; +%! 'gyro_lowpass2_hz_pitch', '150'; +%! 'gyro_lowpass2_hz_yaw', '150'; +%! 'dterm_lowpass_hz_roll', '110'; +%! 'dterm_lowpass_hz_pitch', '110'; +%! 'dterm_lowpass_hz_yaw', '110'; +%! 'dterm_lowpass2_hz_roll', '185'; +%! 'dterm_lowpass2_hz_pitch', '185'; +%! 'dterm_lowpass2_hz_yaw', '185'; +%! 'dterm_notch_hz', '0'; +%! 'dterm_notch_cutoff', '0'; +%! 'gyro_notch_hz', '0,0'; +%! 'gyro_notch_cutoff', '0,0'; +%! }; +%! fp = PSparseFilterParams(si); +%! assert(fp.gyro_lpf1_hz, 200); +%! assert(fp.gyro_lpf2_hz, 150); +%! assert(fp.dterm_lpf1_hz, 110); +%! assert(fp.dterm_lpf2_hz, 185); + +%!test +%! % PSparseFilterParams must return gyro loop rate from headers +%! % looptime:125 = 125us = 8kHz gyro rate +%! si = { +%! 'looptime', '125'; +%! 'gyro_sync_denom', '1'; +%! 'pid_process_denom', '1'; +%! 'gyro_lowpass_type', '0'; +%! 'gyro_lowpass_hz', '300'; +%! 'dterm_lowpass_hz', '100'; +%! 'gyro_notch_hz', '0,0'; +%! 'gyro_notch_cutoff', '0,0'; +%! 'dterm_notch_hz', '0'; +%! 'dterm_notch_cutoff', '0'; +%! }; +%! fp = PSparseFilterParams(si); +%! assert(fp.gyro_rate_hz, 8000, 'looptime:125 should give 8kHz gyro rate'); + +%!test +%! % looptime:250 with gyro_sync_denom:2 = 500us gyro, 1000us pid -> gyro 4kHz +%! si = { +%! 'looptime', '250'; +%! 'gyro_sync_denom', '2'; +%! 'pid_process_denom', '2'; +%! 'gyro_lowpass_hz', '300'; +%! 'gyro_notch_hz', '0,0'; +%! 'gyro_notch_cutoff', '0,0'; +%! 'dterm_notch_hz', '0'; +%! 'dterm_notch_cutoff', '0'; +%! }; +%! fp = PSparseFilterParams(si); +%! assert(fp.gyro_rate_hz, 4000, 'looptime 250us / gyro_sync_denom 2 = 4kHz'); + +%!test +%! % No looptime header -> gyro_rate_hz should be 0 (use log rate as fallback) +%! si = { +%! 'gyro_lowpass_hz', '300'; +%! 'gyro_notch_hz', '0,0'; +%! 'gyro_notch_cutoff', '0,0'; +%! 'dterm_notch_hz', '0'; +%! 'dterm_notch_cutoff', '0'; +%! }; +%! fp = PSparseFilterParams(si); +%! assert(fp.gyro_rate_hz, 0, 'no looptime header -> gyro_rate_hz = 0'); From dd49b98b0f9b0ca3f8a1b7787192f9423f81bee5 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 29 Mar 2026 20:56:40 +0200 Subject: [PATCH 49/80] feat(spectral): Motor Noise panels, RPM overlay, hex/octo, perf cache --- src/plot/PSfreqTime.m | 41 +++++-- src/plot/PSplotRPMOverlay.m | 6 +- src/plot/PSplotSpec.m | 41 +++++-- src/plot/PSplotSpec2D.m | 208 ++++++++++++++++++++++++++++++++++- src/ui/PSfreqTimeUIcontrol.m | 15 ++- src/ui/PSspec2DUIcontrol.m | 66 +++++++++-- src/ui/PSspecUIcontrol.m | 14 ++- src/util/PSresizeCP.m | 1 + 8 files changed, 349 insertions(+), 43 deletions(-) diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 0dbcad7..48703df 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -38,10 +38,13 @@ try rpmShowEst = get(guiHandlesSpec3.rpmEstChk, 'Value'); catch, end try rpmMotors = []; - if get(guiHandlesSpec3.rpmMotor1, 'Value'), rpmMotors(end+1) = 1; end - if get(guiHandlesSpec3.rpmMotor2, 'Value'), rpmMotors(end+1) = 2; end - if get(guiHandlesSpec3.rpmMotor3, 'Value'), rpmMotors(end+1) = 3; end - if get(guiHandlesSpec3.rpmMotor4, 'Value'), rpmMotors(end+1) = 4; end + nMot_ = 4; try nMot_ = guiHandlesSpec3.nMotors; catch, end + for mi_ = 1:4 + if get(guiHandlesSpec3.(sprintf('rpmMotor%d',mi_)), 'Value') + rpmMotors(end+1) = mi_; + if nMot_ > 4, rpmMotors(end+1) = mi_ + nMot_/2; end + end + end catch, rpmMotors = [1 2 3 4]; end try harmSel = get(guiHandlesSpec3.rpmHarmDd, 'Value'); @@ -80,6 +83,28 @@ try rpmFilterData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; catch, rpmFilterData{k_} = []; end end + % eRPM fallback when RPM_FILTER debug mode not active + if (numel(rpmFilterData) < k_ || isempty(rpmFilterData{k_})) && isfield(T{k_}, 'eRPM_0_') + mPoles_ = 14; + try mp_ = find(strcmp(SetupInfo{k_}(:,1), 'motor_poles')); + if ~isempty(mp_), mPoles_ = str2double(SetupInfo{k_}(mp_(1),2)); end + catch, end + if mPoles_ < 2, mPoles_ = 14; end + try + nEm_ = 0; + for mi_ = 0:7 + if isfield(T{k_}, ['eRPM_' int2str(mi_) '_']), nEm_ = mi_+1; end + end + rpmHz_ = zeros(sum(tIND{k_}), nEm_); + for mi_ = 0:nEm_-1 + ef_ = ['eRPM_' int2str(mi_) '_']; + if isfield(T{k_}, ef_) + rpmHz_(:, mi_+1) = T{k_}.(ef_)(tIND{k_}) * 100 / (mPoles_/2) / 60; + end + end + rpmFilterData{k_} = rpmHz_; + catch, rpmFilterData{k_} = []; end + end end end @@ -148,12 +173,8 @@ end %% RPM filter overlay (motor frequencies + harmonics) - if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') - tmpRPMft = 46; - if numel(debugIdx) >= tmpFileVal3 - tmpRPMft = debugIdx{tmpFileVal3}.RPM_FILTER; - end - if debugmode(tmpFileVal3) == tmpRPMft && numel(rpmFilterData) >= tmpFileVal3 && ~isempty(rpmFilterData{tmpFileVal3}) + if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') + if numel(rpmFilterData) >= tmpFileVal3 && ~isempty(rpmFilterData{tmpFileVal3}) PSplotRPMOverlay(gca, rpmFilterData{tmpFileVal3}, size(specMat{i}, 2), size(specMat{i}, 1), F(end), 'time', 3, rpmMotors, rpmHarms, rpmLw); end end diff --git a/src/plot/PSplotRPMOverlay.m b/src/plot/PSplotRPMOverlay.m index 85d5586..4549552 100644 --- a/src/plot/PSplotRPMOverlay.m +++ b/src/plot/PSplotRPMOverlay.m @@ -12,7 +12,7 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic % lw - line width (default 1) if nargin < 7 || isempty(nHarmonics), nHarmonics = 3; end -if nargin < 8 || isempty(motors), motors = 1:min(4, size(rpmMat, 2)); end +if nargin < 8 || isempty(motors), motors = 1:size(rpmMat, 2); end if nargin < 9 || isempty(harmonics), harmonics = 1:nHarmonics; end if nargin < 10 || isempty(lw), lw = 1; end if isempty(rpmMat) || imgHeight < 2 || freqMax <= 0 @@ -29,7 +29,7 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic for mi = 1:numel(motors) m = motors(mi); if m > size(rpmMat, 2), continue; end - ci = min(m, size(motorCol, 1)); + ci = mod(m-1, size(motorCol, 1)) + 1; for hi = 1:numel(harmonics) harm = harmonics(hi); xPts = []; @@ -67,7 +67,7 @@ function PSplotRPMOverlay(ax, rpmMat, xData, imgHeight, freqMax, mode, nHarmonic for mi = 1:numel(motors) m = motors(mi); if m > size(rpmMat, 2), continue; end - ci = min(m, size(motorCol, 1)); + ci = mod(m-1, size(motorCol, 1)) + 1; for hi = 1:numel(harmonics) harm = harmonics(hi); xPts = []; diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index c470b89..d154758 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -18,10 +18,13 @@ try rpmShowDN = get(guiHandlesSpec.rpmDynNotch, 'Value'); catch, end try rpmMotors = []; - if get(guiHandlesSpec.rpmMotor1, 'Value'), rpmMotors(end+1) = 1; end - if get(guiHandlesSpec.rpmMotor2, 'Value'), rpmMotors(end+1) = 2; end - if get(guiHandlesSpec.rpmMotor3, 'Value'), rpmMotors(end+1) = 3; end - if get(guiHandlesSpec.rpmMotor4, 'Value'), rpmMotors(end+1) = 4; end + nMot_ = 4; try nMot_ = guiHandlesSpec.nMotors; catch, end + for mi_ = 1:4 + if get(guiHandlesSpec.(sprintf('rpmMotor%d',mi_)), 'Value') + rpmMotors(end+1) = mi_; + if nMot_ > 4, rpmMotors(end+1) = mi_ + nMot_/2; end + end + end catch, rpmMotors = [1 2 3 4]; end try harmSel = get(guiHandlesSpec.rpmHarmDd, 'Value'); @@ -61,6 +64,28 @@ try rpmFilterData{k_} = [T{k_}.debug_0_(tIND{k_}), T{k_}.debug_1_(tIND{k_}), T{k_}.debug_2_(tIND{k_}), T{k_}.debug_3_(tIND{k_})]; catch, rpmFilterData{k_} = []; end end + % eRPM fallback when RPM_FILTER debug mode not active + if (numel(rpmFilterData) < k_ || isempty(rpmFilterData{k_})) && isfield(T{k_}, 'eRPM_0_') + mPoles_ = 14; + try mp_ = find(strcmp(SetupInfo{k_}(:,1), 'motor_poles')); + if ~isempty(mp_), mPoles_ = str2double(SetupInfo{k_}(mp_(1),2)); end + catch, end + if mPoles_ < 2, mPoles_ = 14; end + try + nEm_ = 0; + for mi_ = 0:7 + if isfield(T{k_}, ['eRPM_' int2str(mi_) '_']), nEm_ = mi_+1; end + end + rpmHz_ = zeros(sum(tIND{k_}), nEm_); + for mi_ = 0:nEm_-1 + ef_ = ['eRPM_' int2str(mi_) '_']; + if isfield(T{k_}, ef_) + rpmHz_(:, mi_+1) = T{k_}.(ef_)(tIND{k_}) * 100 / (mPoles_/2) / 60; + end + end + rpmFilterData{k_} = rpmHz_; + catch, rpmFilterData{k_} = []; end + end end end end @@ -226,13 +251,9 @@ end %% RPM filter overlay (motor frequencies + harmonics) - if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') && exist('debugmode','var') && exist('debugIdx','var') + if ~isempty(rpmHarms) && ~isempty(rpmMotors) && exist('rpmFilterData','var') tmpFileK2 = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); - tmpRPMk = 46; - if numel(debugIdx) >= tmpFileK2 - tmpRPMk = debugIdx{tmpFileK2}.RPM_FILTER; - end - if debugmode(tmpFileK2) == tmpRPMk && numel(rpmFilterData) >= tmpFileK2 && ~isempty(rpmFilterData{tmpFileK2}) + if numel(rpmFilterData) >= tmpFileK2 && ~isempty(rpmFilterData{tmpFileK2}) maxHzOverlay2 = (A_lograte(tmpFileK2) / 2) * 1000; PSplotRPMOverlay(gca, rpmFilterData{tmpFileK2}, T{tmpFileK2}.setpoint_3_(tIND{tmpFileK2}) / 10, size(img, 1), maxHzOverlay2, 'throttle', 3, rpmMotors, rpmHarms, rpmLw); end diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 079d48f..dbb2534 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -17,7 +17,7 @@ %% -s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; +s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'axisF';'pidsum';'motorAvg'}; if isfield(T{1}, 'testSignal_0_'), s1{end+1} = 'testSignal'; end datSelectionString=[s1]; @@ -172,6 +172,29 @@ smat{p}=[];%string amp2d2{p}=[];%spec 2d freq2d2{p}=[];% freq2d2 + elseif strcmp(s, 'motorAvg') + p = p + 1; + mAvg = zeros(sum(tIND{tmpFileVal(f)}), 1); + nMot = 0; + for mi = 0:3 + mf = ['motor_' int2str(mi) '_']; + if isfield(T{tmpFileVal(f)}, mf) + mAvg = mAvg + T{tmpFileVal(f)}.(mf)(tIND{tmpFileVal(f)}); + nMot = nMot + 1; + end + end + if nMot > 0, mAvg = mAvg / nMot; end + dat = mAvg'; + lograte = A_lograte(tmpFileVal(f)); + smat{p} = s; + [tmpF tmpA] = PSSpec2d(dat, lograte, tmpPSDVal); + if isempty(tmpF) + smat{p}=[]; amp2d2{p}=[]; freq2d2{p}=[]; + else + ff = ['f' int2str(f)]; + freq2d2{p}.(ff) = tmpF; + amp2d2{p}.(ff) = tmpA; + end elseif ~isfield(T{tmpFileVal(f)}, [s '_' int2str(a-1) '_']) p=p+1; smat{p}=[]; amp2d2{p}=[]; freq2d2{p}=[]; @@ -201,8 +224,10 @@ multilineStyle = {'-' ; ':'; '--'}; rpyLineStyle = {'-' ; '--'; ':'}; -for di_=1:6, h_del=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_del), delete(h_del); end; end +% cla instead of delete to avoid Qt rendering artifacts (stale white pixels) +for di_=1:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end h_del=findobj(PSspecfig2,'Type','axes','Tag','PSspec2_combo'); if ~isempty(h_del), delete(h_del); end +h_del=findobj(PSspecfig2,'Type','axes','Tag','legend'); if ~isempty(h_del), delete(h_del); end %%%%% plot 2d amp spec axLabel={'Roll';'Pitch';'Yaw'}; @@ -229,7 +254,7 @@ stag_ = sprintf('PSspec2_%d', a); h2 = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag_); if isempty(h2), h2 = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(a,:), 'Tag', stag_); - else set(PSspecfig2, 'CurrentAxes', h2); end + else set(h2, 'Position', posInfo.Spec2Pos(a,:)); set(PSspecfig2, 'CurrentAxes', h2); end ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -239,7 +264,11 @@ set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; set(h2,'xtick',[0:m/10:m], 'yminortick','on') - axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + if ~strcmp(s, 'motorAvg') + axis([0 m climScale1(get(guiHandlesSpec2.checkboxPSD, 'Value')+1) climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)]) + else + set(h2, 'XLim', [0 m]); + end xlabel('Frequency (Hz)','fontweight','bold','Color',th.textPrimary); if get(guiHandlesSpec2.checkboxPSD, 'Value') ylabel(['Power Spectral Density (dB)'],'fontweight','bold','Color',th.textPrimary); @@ -255,10 +284,20 @@ end grid on + rightMode_ = 1; + try rightMode_ = get(guiHandlesSpec2.rightColMode, 'Value'); catch, end + + if rightMode_ ~= 2 stag2_ = sprintf('PSspec2_%d', a+3); h2 = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag2_); if isempty(h2), h2 = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(a+3,:), 'Tag', stag2_); - else set(PSspecfig2, 'CurrentAxes', h2); end + else set(h2, 'Position', posInfo.Spec2Pos(a+3,:)); set(PSspecfig2, 'CurrentAxes', h2); end + + if strcmp(s, 'motorAvg') + set(h2, 'Visible', 'on'); + PSstyleAxes(h2, th); + else + % Sub 100Hz PSD ff = ['f' int2str(f)]; h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on @@ -282,6 +321,8 @@ h=text(1,climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1)*.92,axLabel{a}); set(h,'Color',th.textPrimary,'fontsize',fontsz,'fontweight','bold'); end + end % motorAvg + end % rightMode_ %%%%%%%%%%%%%%%%%%% Plot Latencies %%%%%%%%%%%%%%% tmpFileSelVals = get(guiHandlesSpec2.FileSelect, 'Value'); @@ -405,6 +446,163 @@ catch, end +% Motor Noise panels (right column, when rightColMode == 2) +rightMode_final = 1; +try rightMode_final = get(guiHandlesSpec2.rightColMode, 'Value'); catch, end +if rightMode_final == 2 + axLabel_mn = {'Roll','Pitch','Yaw'}; + axesOpt_mn = find([get(guiHandlesSpec2.plotR, 'Value') get(guiHandlesSpec2.plotP, 'Value') get(guiHandlesSpec2.plotY, 'Value')]); + tmpFileVal_mn = get(guiHandlesSpec2.FileSelect, 'Value'); + + % read RPM controls + rpmMotors_mn = [1 2 3 4]; + try + rpmMotors_mn = []; + nMot_mn = 4; try nMot_mn = guiHandlesSpec2.nMotors; catch, end + for mi_ = 1:4 + if get(guiHandlesSpec2.(sprintf('rpmMotor%d', mi_)), 'Value') + rpmMotors_mn(end+1) = mi_; + if nMot_mn > 4, rpmMotors_mn(end+1) = mi_ + nMot_mn/2; end + end + end + catch, rpmMotors_mn = [1 2 3 4]; end + + nHarm_sel = [1 2 3]; + try + harmSel_ = get(guiHandlesSpec2.rpmHarmDd, 'Value'); + harmMap_ = {[1 2 3], [1], [2], [3], [1 2], [1 3], [2 3]}; + nHarm_sel = harmMap_{harmSel_}; + catch, end + + rpmLw_mn = 1.5; + try + lwSel_ = get(guiHandlesSpec2.rpmLwDd, 'Value'); + lwMap_ = [0.5 1 1.5 2]; + rpmLw_mn = lwMap_(lwSel_); + catch, end + + for ai = axesOpt_mn + stag_mn = sprintf('PSspec2_%d', ai+3); + h_mn = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag_mn); + if isempty(h_mn), h_mn = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(ai+3,:), 'Tag', stag_mn); + else cla(h_mn); set(h_mn, 'Position', posInfo.Spec2Pos(ai+3,:)); set(PSspecfig2, 'CurrentAxes', h_mn); title(h_mn, ''); xlabel(h_mn, ''); ylabel(h_mn, ''); end + + for fi_mn = 1:numel(tmpFileVal_mn) + fIdx_mn = tmpFileVal_mn(fi_mn); + fCol_mn = multiLineCols(fi_mn, :); + try + gyroFld_mn = ['gyroADC_' int2str(ai-1) '_']; + if ~isfield(T{fIdx_mn}, gyroFld_mn) || ~isfield(T{fIdx_mn}, 'eRPM_0_') + if fi_mn == 1 + text(0.5, 0.5, 'No RPM data', 'Parent', h_mn, 'Units', 'normalized', ... + 'HorizontalAlignment', 'center', 'Color', th.textSecondary, 'FontSize', fontsz); + end + continue; + end + gSig_mn = T{fIdx_mn}.(gyroFld_mn)(tIND{fIdx_mn}); + nSamp_mn = numel(gSig_mn); + mPoles_mn = 14; + try mp_mn = find(strcmp(SetupInfo{fIdx_mn}(:,1), 'motor_poles')); + if ~isempty(mp_mn), mPoles_mn = str2double(SetupInfo{fIdx_mn}(mp_mn(1),2)); end + catch, end + if mPoles_mn < 2, mPoles_mn = 14; end + nEm_mn = 0; + for mi_mn = 0:7 + if isfield(T{fIdx_mn}, ['eRPM_' int2str(mi_mn) '_']), nEm_mn = mi_mn+1; end + end + rpmHz_mn = zeros(nSamp_mn, nEm_mn); + for mi_mn = 0:nEm_mn-1 + ef_mn = ['eRPM_' int2str(mi_mn) '_']; + if isfield(T{fIdx_mn}, ef_mn) + rpmHz_mn(:, mi_mn+1) = T{fIdx_mn}.(ef_mn)(tIND{fIdx_mn}) * 100 / (mPoles_mn/2) / 60; + end + end + selCols_mn = rpmMotors_mn(rpmMotors_mn <= nEm_mn); + if isempty(selCols_mn), selCols_mn = 1:min(4, nEm_mn); end + nHarm_mn = 3; + winLen_mn = min(512, floor(nSamp_mn/4)); + nWin_mn = floor(nSamp_mn / winLen_mn); + if nWin_mn < 2, nWin_mn = 1; winLen_mn = nSamp_mn; end + noiseVals_mn = NaN(nWin_mn, nHarm_mn); + lr_mn = A_lograte(fIdx_mn); + for wi_mn = 1:nWin_mn + i1 = (wi_mn-1)*winLen_mn+1; i2 = min(wi_mn*winLen_mn, nSamp_mn); + [pf_mn, pa_mn] = PSSpec2d(gSig_mn(i1:i2)', lr_mn, 1); + mFreq_mn = mean(rpmHz_mn(i1:i2, selCols_mn), 'all'); + for hi_mn = 1:nHarm_mn + ft_mn = mFreq_mn * hi_mn; + if ft_mn > 0 && ft_mn < lr_mn*500 && ~isempty(pf_mn) + noiseVals_mn(wi_mn, hi_mn) = interp1(pf_mn, pa_mn, ft_mn, 'linear', NaN); + end + end + end + avgN_mn = nanmean(noiseVals_mn, 1); + stdN_mn = nanstd(noiseVals_mn, 0, 1); + % pre-filter (dotted) + preFld_mn = ['gyroUnfilt_' int2str(ai-1) '_']; + hasPre_mn = isfield(T{fIdx_mn}, preFld_mn); + if ~hasPre_mn + preFld_mn = ['debug_' int2str(ai-1) '_']; + hasPre_mn = isfield(T{fIdx_mn}, preFld_mn) && exist('debugmode','var') && ... + numel(debugmode) >= fIdx_mn && any(debugmode(fIdx_mn) == [3 6]); + end + if hasPre_mn + preSig_mn = T{fIdx_mn}.(preFld_mn)(tIND{fIdx_mn}); + noisePre_mn = NaN(nWin_mn, nHarm_mn); + for wi_mn2 = 1:nWin_mn + i1p = (wi_mn2-1)*winLen_mn+1; i2p = min(wi_mn2*winLen_mn, nSamp_mn); + [pfp_, pap_] = PSSpec2d(preSig_mn(i1p:i2p)', lr_mn, 1); + mfp_ = mean(rpmHz_mn(i1p:i2p, selCols_mn), 'all'); + for hip_ = 1:nHarm_mn + ftp_ = mfp_ * hip_; + if ftp_ > 0 && ftp_ < lr_mn*500 && ~isempty(pfp_) + noisePre_mn(wi_mn2, hip_) = interp1(pfp_, pap_, ftp_, 'linear', NaN); + end + end + end + avgPre_mn = nanmean(noisePre_mn, 1); + stdPre_mn = nanstd(noisePre_mn, 0, 1); + h_eb = errorbar(h_mn, nHarm_sel, avgPre_mn(nHarm_sel), stdPre_mn(nHarm_sel), 'o:'); + set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn, 'MarkerSize', 8); + hold(h_mn, 'on'); + end + % post-filter (solid) + h_eb = errorbar(h_mn, nHarm_sel, avgN_mn(nHarm_sel), stdN_mn(nHarm_sel), 'o-'); + set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn+0.5, 'MarkerFaceColor', fCol_mn, 'MarkerSize', 8); + hold(h_mn, 'on'); + catch + if fi_mn == 1 + text(0.5, 0.5, 'Error', 'Parent', h_mn, 'Units', 'normalized', ... + 'HorizontalAlignment', 'center', 'Color', th.textSecondary, 'FontSize', fontsz); + end + end + end + set(h_mn, 'XTick', 1:3, 'XTickLabel', {'1st','2nd','3rd'}); + axis(h_mn, [0.5 3.5 -60 20]); + xlabel(h_mn, 'Motor Harmonic', 'fontweight', 'bold', 'Color', th.textPrimary); + ylabel(h_mn, [axLabel_mn{ai} ' | Avg Motor Noise (dB)'], 'fontweight', 'bold', 'Color', th.textPrimary); + if ai == axesOpt_mn(1) + fIdx1_mn = tmpFileVal_mn(1); + try + fwStr = ''; qStr = ''; + fwRow = find(strcmp(SetupInfo{fIdx1_mn}(:,1), 'rpm_filter_weights')); + if ~isempty(fwRow), fwStr = SetupInfo{fIdx1_mn}{fwRow(1),2}; end + qRow = find(strcmp(SetupInfo{fIdx1_mn}(:,1), 'rpm_filter_q')); + if ~isempty(qRow), qStr = ['Q' SetupInfo{fIdx1_mn}{qRow(1),2}]; end + if ~isempty(fwStr) || ~isempty(qStr) + fCol1_mn = multiLineCols(1,:); + text(0.98, 0.95, ['Filter Weights | ' qStr], 'Parent', h_mn, 'Units', 'normalized', ... + 'HorizontalAlignment', 'right', 'Color', fCol1_mn, 'FontSize', fontsz-1, 'FontWeight', 'bold'); + text(0.98, 0.85, fwStr, 'Parent', h_mn, 'Units', 'normalized', ... + 'HorizontalAlignment', 'right', 'Color', fCol1_mn, 'FontSize', fontsz-1); + end + catch, end + end + grid(h_mn, 'on'); + PSstyleAxes(h_mn, th); + end +end + allax = findobj(PSspecfig2, 'Type', 'axes'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end PSdatatipSetup(PSspecfig2); diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index c8f36ee..5349b38 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -137,9 +137,20 @@ guiHandlesSpec3.sub100HzfreqTime = uicontrol(PSspecfig3,'Style','checkbox','String','sub 100Hz','fontsize',fontsz,'ForegroundColor',panelFg,'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.sub100HzfreqTime],'callback','@selection2;updateSpec=1; PSfreqTime;'); -% RPM overlay motor checkboxes +% RPM overlay motor checkboxes - detect hex/octo motorCols = PStheme().sigMotor; -motorNames = {'M1','M2','M3','M4'}; +nMot_ = 4; +if exist('T','var') && ~isempty(T) + for mi_ = 4:7 + if isfield(T{1}, ['motor_' int2str(mi_) '_']), nMot_ = mi_+1; end + end +end +if nMot_ > 4 + motorNames = {sprintf('M1/%d',nMot_/2+1), sprintf('M2/%d',nMot_/2+2), sprintf('M3/%d',nMot_/2+3), sprintf('M4/%d',nMot_/2+4)}; +else + motorNames = {'M1','M2','M3','M4'}; +end +guiHandlesSpec3.nMotors = nMot_; rpmCb = 'updateSpec=1; PSfreqTime;'; for mi = 1:4 fld = sprintf('rpmMotor%d', mi); diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 4a87355..8755df8 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -71,7 +71,15 @@ posInfo.climMax1_text = [cpL+cpM yTop-rhs cpW/4 rhs]; posInfo.climMax2_text = [cpL+cpW/2 yTop-rhs cpW/4 rhs]; yTop=yTop-rhs-gap; posInfo.climMax1_input = [cpL+cpM yTop-rh cpW/4 rh]; -posInfo.climMax2_input = [cpL+cpW/2 yTop-rh cpW/4 rh]; +posInfo.climMax2_input = [cpL+cpW/2 yTop-rh cpW/4 rh]; yTop=yTop-rh-gap; + +qw = fw/4; +posInfo.rpmMotor1 = [cpL+cpM yTop-rh qw rh]; +posInfo.rpmMotor2 = [cpL+cpM+qw yTop-rh qw rh]; +posInfo.rpmMotor3 = [cpL+cpM+2*qw yTop-rh qw rh]; +posInfo.rpmMotor4 = [cpL+cpM+3*qw yTop-rh qw rh]; yTop=yTop-rh-gap; +posInfo.rpmHarmDd = [cpL+cpM yTop-ddh hw ddh]; +posInfo.rpmLwDd = [cpL+cpW/2 yTop-ddh hw ddh]; yTop=yTop-ddh-gap; climScale1=[0 ; -50 ]; climScale2=[0.5 ; 20]; @@ -119,7 +127,7 @@ set(guiHandlesSpec2.saveSettings2, 'ForegroundColor', saveCol); % create string list for SpecSelect -sA={'Gyro','Gyro prefilt','Dterm','Dterm prefilt','Pterm','PID error','Set point','PIDsum'}; +sA={'Gyro','Gyro prefilt','Dterm','Dterm prefilt','Pterm','PID error','Set point','Fterm','PIDsum','Motors'}; if isfield(T{1}, 'testSignal_0_'), sA{end+1} = 'Test Signal'; end guiHandlesSpec2.SpecList = uicontrol(PSspecfig2,'Style','listbox','string',[sA],'max',3,'min',1, 'fontsize',fontsz, 'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.TermListWindowSpec], 'callback', 'if length(get(guiHandlesSpec2.SpecList, ''Value'')) > 2, set(guiHandlesSpec2.SpecList, ''Value'', 1); end;'); @@ -138,15 +146,13 @@ 'callback','PSfreqTimeUIcontrol;'); set(guiHandlesSpec2.spectrogramButton3, 'ForegroundColor', th.btnDash2); -guiHandlesSpec2.motorNoiseButton = uicontrol(PSspecfig2,'string','Motor Noise','fontsize',fontsz,... - 'TooltipString','Per-motor spectral analysis and noise comparison','units','normalized',... - 'Position',[posInfo.motorNoiseButton],... - 'callback',['try,' ... - 'tmpFcnt=get(guiHandlesSpec2.FileSelect,''Value'');tmpFcnt=tmpFcnt(1);' ... - 'PSplotMotorNoise(T{tmpFcnt},tmpFcnt,tIND{tmpFcnt},1000*A_lograte(tmpFcnt));' ... - 'clear tmpFcnt;' ... - 'catch e,warndlg([''Motor Noise: '' e.message]),end']); -set(guiHandlesSpec2.motorNoiseButton, 'ForegroundColor', th.btnMotNoise); +guiHandlesSpec2.rightColMode = uicontrol(PSspecfig2,'Style','popupmenu','String',{'sub 100Hz','Motor Noise'},... + 'fontsize',fontsz,'TooltipString','Right column: sub 100Hz PSD or Motor Noise per-harmonic',... + 'units','normalized','Position',[posInfo.motorNoiseButton],... + 'callback',['vis_=''off'';if get(guiHandlesSpec2.rightColMode,''Value'')==2,vis_=''on'';end;' ... + 'flds_={''rpmMotor1'',''rpmMotor2'',''rpmMotor3'',''rpmMotor4'',''rpmHarmDd'',''rpmLwDd''};' ... + 'for fi_=1:6,set(guiHandlesSpec2.(flds_{fi_}),''Visible'',vis_);end;' ... + 'try PSresizeCP(PSspecfig2,[]);catch,end;updateSpec=1;PSplotSpec2D;']); guiHandlesSpec2.chirpButton = uicontrol(PSspecfig2,'string','Chirp Analysis','fontsize',fontsz,... 'TooltipString','Frequency response from chirp log (BF 2025.12+, debug_mode=CHIRP)','units','normalized',... @@ -187,6 +193,36 @@ guiHandlesSpec2.climMax2_input = uicontrol(PSspecfig2,'style','edit','string',[num2str(climScale2(get(guiHandlesSpec2.checkboxPSD, 'Value')+1, 1))],'fontsize',fontsz,'TooltipString',['Y max'],'units','normalized','Position',[posInfo.climMax2_input],... 'callback','@textinput_call2; climScale2(get(guiHandlesSpec2.checkboxPSD, ''Value'')+1, 1)=str2double(get(guiHandlesSpec2.climMax2_input, ''String''));PSplotSpec2D;'); +motorCols = PStheme().sigMotor; +nMot_ = 4; +if exist('T','var') && ~isempty(T) + for mi_ = 4:7 + if isfield(T{1}, ['motor_' int2str(mi_) '_']), nMot_ = mi_+1; end + end +end +if nMot_ > 4 + motorNames = {sprintf('M1/%d',nMot_/2+1), sprintf('M2/%d',nMot_/2+2), sprintf('M3/%d',nMot_/2+3), sprintf('M4/%d',nMot_/2+4)}; +else + motorNames = {'M1','M2','M3','M4'}; +end +guiHandlesSpec2.nMotors = nMot_; +rpmCb2 = 'updateSpec=1; PSplotSpec2D;'; +for mi = 1:4 + fld = sprintf('rpmMotor%d', mi); + guiHandlesSpec2.(fld) = uicontrol(PSspecfig2, 'Style','checkbox', 'String', motorNames{mi}, ... + 'fontsize', fontsz-1, 'Value', 1, 'Visible', 'off', ... + 'ForegroundColor', motorCols{mi}, 'BackgroundColor', bgcolor, ... + 'units','normalized', 'Position', posInfo.(fld), 'callback', rpmCb2); +end +guiHandlesSpec2.rpmHarmDd = uicontrol(PSspecfig2, 'Style','popupmenu', ... + 'String', {'All harm.','1st','2nd','3rd','1st & 2nd','1st & 3rd','2nd & 3rd'}, ... + 'fontsize', fontsz, 'Value', 1, 'Visible', 'off', ... + 'units','normalized', 'Position', posInfo.rpmHarmDd, 'callback', rpmCb2); +guiHandlesSpec2.rpmLwDd = uicontrol(PSspecfig2, 'Style','popupmenu', ... + 'String', {'lw 0.5','lw 1','lw 1.5','lw 2'}, ... + 'fontsize', fontsz, 'Value', 3, 'Visible', 'off', ... + 'units','normalized', 'Position', posInfo.rpmLwDd, 'callback', rpmCb2); + end % ishandle(spec2Crtlpanel) % Register CP for fixed-pixel resize @@ -201,7 +237,7 @@ cpI{end+1} = struct('h', guiHandlesSpec2.resetSpec, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton2, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.spectrogramButton3, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); -cpI{end+1} = struct('h', guiHandlesSpec2.motorNoiseButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rightColMode, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.chirpButton, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.saveFig2, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.saveSettings2, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); @@ -216,6 +252,12 @@ cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_text, 'type','text_right', 'row',0, 'col',0, 'hpx',rhs_px); cpI{end+1} = struct('h', guiHandlesSpec2.climMax1_input, 'type','input_left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesSpec2.climMax2_input, 'type','input_right', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmMotor1, 'type','quarter1', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmMotor2, 'type','quarter2', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmMotor3, 'type','quarter3', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmMotor4, 'type','quarter4', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmHarmDd, 'type','dd_left', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesSpec2.rpmLwDd, 'type','dd_right', 'row',0, 'col',0, 'hpx',ddh_px); setappdata(PSspecfig2, 'PSplotGrid', struct('plotL',plotL2, 'colGap',colGap2, ... 'ncols',2, 'rows',rows, 'rowH',0.25, 'margin',0.04)); PSregisterResize(PSspecfig2, cpPx, cpI, 'seq'); diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 5a8d529..25fdeb6 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -179,7 +179,19 @@ set(guiHandlesSpec.checkboxPSD, 'Value', 0); motorCols = PStheme().sigMotor; -motorNames = {'M1','M2','M3','M4'}; +% detect motor count and label pairs for hex/octo +nMot_ = 4; +if exist('T','var') && ~isempty(T) + for mi_ = 4:7 + if isfield(T{1}, ['motor_' int2str(mi_) '_']), nMot_ = mi_+1; end + end +end +if nMot_ > 4 + motorNames = {sprintf('M1/%d',nMot_/2+1), sprintf('M2/%d',nMot_/2+2), sprintf('M3/%d',nMot_/2+3), sprintf('M4/%d',nMot_/2+4)}; +else + motorNames = {'M1','M2','M3','M4'}; +end +guiHandlesSpec.nMotors = nMot_; rpmCb = 'updateSpec=1;PSplotSpec;'; for mi = 1:4 fld = sprintf('rpmMotor%d', mi); diff --git a/src/util/PSresizeCP.m b/src/util/PSresizeCP.m index 0346142..0eedf77 100644 --- a/src/util/PSresizeCP.m +++ b/src/util/PSresizeCP.m @@ -36,6 +36,7 @@ function PSresizeCP(fig, ~) for i = 1:numel(cpd.items) it = cpd.items{i}; if ~ishandle(it.h), continue; end + if ~strcmp(it.type, 'panel') && strcmp(get(it.h, 'Visible'), 'off'), continue; end try hpx = it.hpx; h = hpx / figH; From 476dd27c6ec2d6356b6489d3bdf540860e426e57 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 29 Mar 2026 22:23:54 +0200 Subject: [PATCH 50/80] perf(spectral): PSD/render cache, Motor Noise FFT cache, hex/octo eRPM, axis auto-scale --- src/plot/PSplotLogViewer.m | 12 +- src/plot/PSplotSpec2D.m | 295 ++++++++++++++++++++++++++----------- src/ui/PSspec2DUIcontrol.m | 4 +- src/ui/PSviewerUIcontrol.m | 10 +- 4 files changed, 230 insertions(+), 91 deletions(-) diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index 4b3f623..a804a31 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -366,14 +366,20 @@ hold(rpmAx, 'on'); rpmColors = th.sigRPM; + nEm_ = 0; + for mk = 0:7 + if isfield(T{fileIdx_}, ['eRPM_' int2str(mk) '_']), nEm_ = mk+1; end + end rpmMax = 0; - for mk = 0:3 - if ~rpmEnabled_(mk+1), continue; end + for mk = 0:nEm_-1 + ci_ = mod(mk, 4) + 1; + if ~rpmEnabled_(ci_), continue; end fld = ['eRPM_' int2str(mk) '_']; if isfield(T{fileIdx_}, fld) raw = PSsmoothLV(PSfig, T{fileIdx_}, fileIdx_, fld, sFactor_); hz = raw * 100 / (mPoles/2) / 60; - plot(rpmAx, tSec_, hz, 'Color', rpmColors{mk+1}, 'LineWidth', lwVal_, 'LineStyle', ':', 'HitTest', 'off'); + ls_ = ':'; if mk >= 4, ls_ = '--'; end + plot(rpmAx, tSec_, hz, 'Color', rpmColors{ci_}, 'LineWidth', lwVal_, 'LineStyle', ls_, 'HitTest', 'off'); rpmMax = max(rpmMax, max(hz)); end end diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index dbb2534..4cb36c9 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -155,14 +155,25 @@ try close(hw_delay); catch, end end +tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); +tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); +tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); + +% skip PSD recompute when only right-column controls changed +needFFT_ = true; +if exist('prevPsdKey_','var') && exist('freq2d2','var') && ~isempty(freq2d2) && ~updateSpec + if isequal(tmpSpecVal, prevPsdKey_.specVal) && isequal(tmpFileVal, prevPsdKey_.fileVal) && ... + tmpPSDVal == prevPsdKey_.psdVal && isequal(axesOptionsSpec, prevPsdKey_.axes) + needFFT_ = false; + end +end + +if needFFT_ clear s dat a RC smat amp2d2 freq2d2 freq2d2 = {}; amp2d2 = {}; p=0; hw_fft = waitbar(0, 'computing FFT...'); -tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); -tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); -tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); for k = 1 : length(tmpSpecVal) s = char(datSelectionString(tmpSpecVal(k))); for f = 1 : size(tmpFileVal,2) @@ -218,12 +229,27 @@ end end try close(hw_fft); catch, end +prevPsdKey_ = struct('specVal', tmpSpecVal, 'fileVal', tmpFileVal, 'psdVal', tmpPSDVal, 'axes', axesOptionsSpec); +end figure(PSspecfig2); baselineYlines = [0 -50]; multilineStyle = {'-' ; ':'; '--'}; rpyLineStyle = {'-' ; '--'; ':'}; +% skip left-column rerender when only right-column controls changed +tmpSmoothVal = get(guiHandlesSpec2.smoothFactor_select, 'Value'); +rightMode_chk_ = 1; try rightMode_chk_ = get(guiHandlesSpec2.rightColMode, 'Value'); catch, end +leftKey_ = struct('specVal', tmpSpecVal, 'fileVal', tmpFileVal, 'psdVal', tmpPSDVal, ... + 'axes', axesOptionsSpec, 'smooth', tmpSmoothVal, 'rightCol', rightMode_chk_); +try leftKey_.clim1 = get(guiHandlesSpec2.climMax1_input, 'String'); + leftKey_.clim2 = get(guiHandlesSpec2.climMax2_input, 'String'); + leftKey_.delay = get(guiHandlesSpec2.Delay, 'Value'); + leftKey_.combo = get(guiHandlesSpec2.RPYcomboSpec, 'Value'); +catch, end +skipLeftRender_ = ~needFFT_ && exist('prevLeftKey_','var') && isequal(leftKey_, prevLeftKey_); + +if ~skipLeftRender_ % cla instead of delete to avoid Qt rendering artifacts (stale white pixels) for di_=1:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end h_del=findobj(PSspecfig2,'Type','axes','Tag','PSspec2_combo'); if ~isempty(h_del), delete(h_del); end @@ -235,7 +261,6 @@ tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); tmpFileVal = get(guiHandlesSpec2.FileSelect, 'Value'); tmpPSDVal = get(guiHandlesSpec2.checkboxPSD, 'Value'); -tmpSmoothVal = get(guiHandlesSpec2.smoothFactor_select, 'Value'); for k = 1 : length(tmpSpecVal) s = char(datSelectionString(tmpSpecVal(k))); for f = 1 : size(tmpFileVal,2) @@ -444,6 +469,11 @@ try PSstyleLegend(h, th); catch, end end catch, end +prevLeftKey_ = leftKey_; +else + % right-column only: clear just axes 4-6 + for di_=4:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end +end % skipLeftRender_ % Motor Noise panels (right column, when rightColMode == 2) @@ -481,104 +511,199 @@ rpmLw_mn = lwMap_(lwSel_); catch, end - for ai = axesOpt_mn - stag_mn = sprintf('PSspec2_%d', ai+3); - h_mn = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag_mn); - if isempty(h_mn), h_mn = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(ai+3,:), 'Tag', stag_mn); - else cla(h_mn); set(h_mn, 'Position', posInfo.Spec2Pos(ai+3,:)); set(PSspecfig2, 'CurrentAxes', h_mn); title(h_mn, ''); xlabel(h_mn, ''); ylabel(h_mn, ''); end - + % two-level cache: L1=FFT matrices (file/epoch), L2=interp results (motor sel) + fftKey_ = struct('fileVal', tmpFileVal_mn); + fftKey_.nSamp = zeros(1, numel(tmpFileVal_mn)); + for fi_ = 1:numel(tmpFileVal_mn), fftKey_.nSamp(fi_) = sum(tIND{tmpFileVal_mn(fi_)}); end + fftCache_ = []; + try fftCache_ = getappdata(PSspecfig2, 'mnFftCache'); catch, end + fftHit_ = ~isempty(fftCache_) && isstruct(fftCache_) && isfield(fftCache_, 'key') && ... + isequal(fftCache_.key.fileVal, fftKey_.fileVal) && isequal(fftCache_.key.nSamp, fftKey_.nSamp); + + if ~fftHit_ + % L1: compute RPM Hz (all motors) + PSD matrices per file per axis + fftData_ = cell(numel(tmpFileVal_mn), 1); + nHarm_mn = 3; for fi_mn = 1:numel(tmpFileVal_mn) fIdx_mn = tmpFileVal_mn(fi_mn); - fCol_mn = multiLineCols(fi_mn, :); + fd_ = struct('valid', false); try - gyroFld_mn = ['gyroADC_' int2str(ai-1) '_']; - if ~isfield(T{fIdx_mn}, gyroFld_mn) || ~isfield(T{fIdx_mn}, 'eRPM_0_') - if fi_mn == 1 - text(0.5, 0.5, 'No RPM data', 'Parent', h_mn, 'Units', 'normalized', ... - 'HorizontalAlignment', 'center', 'Color', th.textSecondary, 'FontSize', fontsz); - end - continue; - end - gSig_mn = T{fIdx_mn}.(gyroFld_mn)(tIND{fIdx_mn}); - nSamp_mn = numel(gSig_mn); - mPoles_mn = 14; - try mp_mn = find(strcmp(SetupInfo{fIdx_mn}(:,1), 'motor_poles')); - if ~isempty(mp_mn), mPoles_mn = str2double(SetupInfo{fIdx_mn}(mp_mn(1),2)); end + if ~isfield(T{fIdx_mn}, 'eRPM_0_'), fftData_{fi_mn} = fd_; continue; end + nSamp_ = sum(tIND{fIdx_mn}); + mPoles_ = 14; + try mp_ = find(strcmp(SetupInfo{fIdx_mn}(:,1), 'motor_poles')); + if ~isempty(mp_), mPoles_ = str2double(SetupInfo{fIdx_mn}(mp_(1),2)); end catch, end - if mPoles_mn < 2, mPoles_mn = 14; end - nEm_mn = 0; - for mi_mn = 0:7 - if isfield(T{fIdx_mn}, ['eRPM_' int2str(mi_mn) '_']), nEm_mn = mi_mn+1; end + if mPoles_ < 2, mPoles_ = 14; end + nEm_ = 0; + for mi_ = 0:7 + if isfield(T{fIdx_mn}, ['eRPM_' int2str(mi_) '_']), nEm_ = mi_+1; end end - rpmHz_mn = zeros(nSamp_mn, nEm_mn); - for mi_mn = 0:nEm_mn-1 - ef_mn = ['eRPM_' int2str(mi_mn) '_']; - if isfield(T{fIdx_mn}, ef_mn) - rpmHz_mn(:, mi_mn+1) = T{fIdx_mn}.(ef_mn)(tIND{fIdx_mn}) * 100 / (mPoles_mn/2) / 60; + rpmHz_ = zeros(nSamp_, nEm_); + for mi_ = 0:nEm_-1 + ef_ = ['eRPM_' int2str(mi_) '_']; + if isfield(T{fIdx_mn}, ef_) + rpmHz_(:, mi_+1) = T{fIdx_mn}.(ef_)(tIND{fIdx_mn}) * 100 / (mPoles_/2) / 60; end end - selCols_mn = rpmMotors_mn(rpmMotors_mn <= nEm_mn); - if isempty(selCols_mn), selCols_mn = 1:min(4, nEm_mn); end - nHarm_mn = 3; - winLen_mn = min(512, floor(nSamp_mn/4)); - nWin_mn = floor(nSamp_mn / winLen_mn); - if nWin_mn < 2, nWin_mn = 1; winLen_mn = nSamp_mn; end - noiseVals_mn = NaN(nWin_mn, nHarm_mn); - lr_mn = A_lograte(fIdx_mn); - for wi_mn = 1:nWin_mn - i1 = (wi_mn-1)*winLen_mn+1; i2 = min(wi_mn*winLen_mn, nSamp_mn); - [pf_mn, pa_mn] = PSSpec2d(gSig_mn(i1:i2)', lr_mn, 1); - mFreq_mn = mean(rpmHz_mn(i1:i2, selCols_mn), 'all'); - for hi_mn = 1:nHarm_mn - ft_mn = mFreq_mn * hi_mn; - if ft_mn > 0 && ft_mn < lr_mn*500 && ~isempty(pf_mn) - noiseVals_mn(wi_mn, hi_mn) = interp1(pf_mn, pa_mn, ft_mn, 'linear', NaN); - end + winLen_ = min(512, floor(nSamp_/4)); + nWin_ = floor(nSamp_ / winLen_); + if nWin_ < 2, nWin_ = 1; winLen_ = nSamp_; end + fd_.rpmHz = rpmHz_(1:nWin_*winLen_, :); + fd_.nEm = nEm_; fd_.nSamp = nSamp_; fd_.lr = A_lograte(fIdx_mn); + fd_.winLen = winLen_; fd_.nWin = nWin_; + hannW_ = hann(winLen_); + Fs_ = fd_.lr * 1000; + halfN_ = floor(winLen_/2) + 1; + fd_.halfN = halfN_; + fd_.df = Fs_ * (1) / winLen_; + fd_.Fs = Fs_; + fd_.psd = cell(1, 3); + fd_.prePsd = cell(1, 3); + fd_.hasPre = false(1, 3); + for ai_ = 1:3 + gyroFld_ = ['gyroADC_' int2str(ai_-1) '_']; + if ~isfield(T{fIdx_mn}, gyroFld_), continue; end + gSig_ = T{fIdx_mn}.(gyroFld_)(tIND{fIdx_mn}); + gSig_ = gSig_(:); + sigMat_ = reshape(gSig_(1:nWin_*winLen_), winLen_, nWin_) .* hannW_; + fftMat_ = fft(sigMat_); + psdMat_ = abs(fftMat_(1:halfN_, :)).^2 / (Fs_ * winLen_); + psdMat_(2:end-1, :) = 2 * psdMat_(2:end-1, :); + fd_.psd{ai_} = 10*log10(psdMat_); + % pre-filter + preFld_ = ['gyroUnfilt_' int2str(ai_-1) '_']; + hp_ = isfield(T{fIdx_mn}, preFld_); + if ~hp_ + preFld_ = ['debug_' int2str(ai_-1) '_']; + hp_ = isfield(T{fIdx_mn}, preFld_) && exist('debugmode','var') && ... + numel(debugmode) >= fIdx_mn && any(debugmode(fIdx_mn) == [3 6]); + end + fd_.hasPre(ai_) = hp_; + if hp_ + preSig_ = T{fIdx_mn}.(preFld_)(tIND{fIdx_mn}); + preSig_ = preSig_(:); + preMat_ = reshape(preSig_(1:nWin_*winLen_), winLen_, nWin_) .* hannW_; + prePsd_ = abs(fft(preMat_)(1:halfN_, :)).^2 / (Fs_ * winLen_); + prePsd_(2:end-1, :) = 2 * prePsd_(2:end-1, :); + fd_.prePsd{ai_} = 10*log10(prePsd_); end end - avgN_mn = nanmean(noiseVals_mn, 1); - stdN_mn = nanstd(noiseVals_mn, 0, 1); - % pre-filter (dotted) - preFld_mn = ['gyroUnfilt_' int2str(ai-1) '_']; - hasPre_mn = isfield(T{fIdx_mn}, preFld_mn); - if ~hasPre_mn - preFld_mn = ['debug_' int2str(ai-1) '_']; - hasPre_mn = isfield(T{fIdx_mn}, preFld_mn) && exist('debugmode','var') && ... - numel(debugmode) >= fIdx_mn && any(debugmode(fIdx_mn) == [3 6]); + fd_.valid = true; + catch + end + fftData_{fi_mn} = fd_; + end + fftS_ = struct(); fftS_.key = fftKey_; fftS_.data = fftData_; + setappdata(PSspecfig2, 'mnFftCache', fftS_); + else + fftData_ = fftCache_.data; + end + + % L2: interp from cached PSD using current motor selection (cheap) + nHarm_mn = 3; + mnData_ = cell(numel(tmpFileVal_mn), 3); + for fi_mn = 1:numel(tmpFileVal_mn) + fd_ = fftData_{fi_mn}; + if ~isstruct(fd_) || ~fd_.valid, continue; end + selCols_ = rpmMotors_mn(rpmMotors_mn <= fd_.nEm); + if isempty(selCols_), selCols_ = 1:min(4, fd_.nEm); end + rpmSel_ = fd_.rpmHz(:, selCols_); + winRpmMean_ = mean(reshape(mean(rpmSel_, 2), fd_.winLen, fd_.nWin), 1)'; + wIdx_ = (1:fd_.nWin)'; + for ai_ = 1:3 + d_ = struct('valid', false, 'hasPre', fd_.hasPre(ai_), 'avgN', [], 'stdN', [], 'avgPre', [], 'stdPre', []); + if isempty(fd_.psd{ai_}), mnData_{fi_mn, ai_} = d_; continue; end + psd_ = fd_.psd{ai_}; + noisePost_ = NaN(fd_.nWin, nHarm_mn); + for hi_ = 1:nHarm_mn + ft_ = winRpmMean_ * hi_; + bin_ = ft_ / fd_.df; + lo_ = floor(bin_) + 1; + frac_ = bin_ - floor(bin_); + ok_ = ft_ > 0 & lo_ >= 1 & lo_ < fd_.halfN; + vi_ = wIdx_(ok_); + if ~isempty(vi_) + iL_ = sub2ind(size(psd_), lo_(ok_), vi_); + iH_ = sub2ind(size(psd_), lo_(ok_)+1, vi_); + noisePost_(vi_, hi_) = psd_(iL_) .* (1-frac_(ok_)) + psd_(iH_) .* frac_(ok_); end - if hasPre_mn - preSig_mn = T{fIdx_mn}.(preFld_mn)(tIND{fIdx_mn}); - noisePre_mn = NaN(nWin_mn, nHarm_mn); - for wi_mn2 = 1:nWin_mn - i1p = (wi_mn2-1)*winLen_mn+1; i2p = min(wi_mn2*winLen_mn, nSamp_mn); - [pfp_, pap_] = PSSpec2d(preSig_mn(i1p:i2p)', lr_mn, 1); - mfp_ = mean(rpmHz_mn(i1p:i2p, selCols_mn), 'all'); - for hip_ = 1:nHarm_mn - ftp_ = mfp_ * hip_; - if ftp_ > 0 && ftp_ < lr_mn*500 && ~isempty(pfp_) - noisePre_mn(wi_mn2, hip_) = interp1(pfp_, pap_, ftp_, 'linear', NaN); - end - end + end + d_.avgN = nanmean(noisePost_, 1); + d_.stdN = nanstd(noisePost_, 0, 1); + if fd_.hasPre(ai_) + pp_ = fd_.prePsd{ai_}; + noisePre_ = NaN(fd_.nWin, nHarm_mn); + for hi_ = 1:nHarm_mn + ft_ = winRpmMean_ * hi_; + bin_ = ft_ / fd_.df; + lo_ = floor(bin_) + 1; + frac_ = bin_ - floor(bin_); + ok_ = ft_ > 0 & lo_ >= 1 & lo_ < fd_.halfN; + vi_ = wIdx_(ok_); + if ~isempty(vi_) + iL_ = sub2ind(size(pp_), lo_(ok_), vi_); + iH_ = sub2ind(size(pp_), lo_(ok_)+1, vi_); + noisePre_(vi_, hi_) = pp_(iL_) .* (1-frac_(ok_)) + pp_(iH_) .* frac_(ok_); end - avgPre_mn = nanmean(noisePre_mn, 1); - stdPre_mn = nanstd(noisePre_mn, 0, 1); - h_eb = errorbar(h_mn, nHarm_sel, avgPre_mn(nHarm_sel), stdPre_mn(nHarm_sel), 'o:'); - set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn, 'MarkerSize', 8); - hold(h_mn, 'on'); end - % post-filter (solid) - h_eb = errorbar(h_mn, nHarm_sel, avgN_mn(nHarm_sel), stdN_mn(nHarm_sel), 'o-'); - set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn+0.5, 'MarkerFaceColor', fCol_mn, 'MarkerSize', 8); - hold(h_mn, 'on'); - catch + d_.avgPre = nanmean(noisePre_, 1); + d_.stdPre = nanstd(noisePre_, 0, 1); + end + d_.valid = true; + mnData_{fi_mn, ai_} = d_; + end + end + + % plot from cached data + for ai = axesOpt_mn + stag_mn = sprintf('PSspec2_%d', ai+3); + h_mn = findobj(PSspecfig2, 'Type', 'axes', 'Tag', stag_mn); + if isempty(h_mn), h_mn = axes('Parent', PSspecfig2, 'Position', posInfo.Spec2Pos(ai+3,:), 'Tag', stag_mn); + else cla(h_mn); set(h_mn, 'Position', posInfo.Spec2Pos(ai+3,:)); set(PSspecfig2, 'CurrentAxes', h_mn); title(h_mn, ''); xlabel(h_mn, ''); ylabel(h_mn, ''); end + for fi_mn = 1:numel(tmpFileVal_mn) + fCol_mn = multiLineCols(fi_mn, :); + d_ = mnData_{fi_mn, ai}; + if isempty(d_) || ~isstruct(d_) || ~d_.valid if fi_mn == 1 - text(0.5, 0.5, 'Error', 'Parent', h_mn, 'Units', 'normalized', ... + text(0.5, 0.5, 'No RPM data', 'Parent', h_mn, 'Units', 'normalized', ... 'HorizontalAlignment', 'center', 'Color', th.textSecondary, 'FontSize', fontsz); end + continue; end + if d_.hasPre + h_eb = errorbar(h_mn, nHarm_sel, d_.avgPre(nHarm_sel), d_.stdPre(nHarm_sel), 'o:'); + set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn, 'MarkerSize', 8); + hold(h_mn, 'on'); + end + h_eb = errorbar(h_mn, nHarm_sel, d_.avgN(nHarm_sel), d_.stdN(nHarm_sel), 'o-'); + set(h_eb, 'Color', fCol_mn, 'LineWidth', rpmLw_mn+0.5, 'MarkerFaceColor', fCol_mn, 'MarkerSize', 8); + hold(h_mn, 'on'); + end + harmLabels_ = {'1st','2nd','3rd'}; + set(h_mn, 'XTick', nHarm_sel, 'XTickLabel', harmLabels_(nHarm_sel)); + % auto-scale Y to data + error bars + yVals_ = []; + yErr_ = []; + for fi2_ = 1:numel(tmpFileVal_mn) + d2_ = mnData_{fi2_, ai}; + if isempty(d2_) || ~isstruct(d2_) || ~d2_.valid, continue; end + yVals_ = [yVals_; d2_.avgN(nHarm_sel)(:)]; + yErr_ = [yErr_; d2_.stdN(nHarm_sel)(:)]; + if d2_.hasPre + yVals_ = [yVals_; d2_.avgPre(nHarm_sel)(:)]; + yErr_ = [yErr_; d2_.stdPre(nHarm_sel)(:)]; + end + end + ok_ = isfinite(yVals_) & isfinite(yErr_); + if any(ok_) + yLo_ = min(yVals_(ok_) - yErr_(ok_)); + yHi_ = max(yVals_(ok_) + yErr_(ok_)); + yPad_ = max(3, (yHi_ - yLo_) * 0.15); + axis(h_mn, [min(nHarm_sel)-0.5 max(nHarm_sel)+0.5 yLo_-yPad_ yHi_+yPad_]); + else + axis(h_mn, [min(nHarm_sel)-0.5 max(nHarm_sel)+0.5 -60 20]); end - set(h_mn, 'XTick', 1:3, 'XTickLabel', {'1st','2nd','3rd'}); - axis(h_mn, [0.5 3.5 -60 20]); xlabel(h_mn, 'Motor Harmonic', 'fontweight', 'bold', 'Color', th.textPrimary); ylabel(h_mn, [axLabel_mn{ai} ' | Avg Motor Noise (dB)'], 'fontweight', 'bold', 'Color', th.textPrimary); if ai == axesOpt_mn(1) diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 8755df8..5020697 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -152,7 +152,7 @@ 'callback',['vis_=''off'';if get(guiHandlesSpec2.rightColMode,''Value'')==2,vis_=''on'';end;' ... 'flds_={''rpmMotor1'',''rpmMotor2'',''rpmMotor3'',''rpmMotor4'',''rpmHarmDd'',''rpmLwDd''};' ... 'for fi_=1:6,set(guiHandlesSpec2.(flds_{fi_}),''Visible'',vis_);end;' ... - 'try PSresizeCP(PSspecfig2,[]);catch,end;updateSpec=1;PSplotSpec2D;']); + 'try PSresizeCP(PSspecfig2,[]);catch,end;PSplotSpec2D;']); guiHandlesSpec2.chirpButton = uicontrol(PSspecfig2,'string','Chirp Analysis','fontsize',fontsz,... 'TooltipString','Frequency response from chirp log (BF 2025.12+, debug_mode=CHIRP)','units','normalized',... @@ -206,7 +206,7 @@ motorNames = {'M1','M2','M3','M4'}; end guiHandlesSpec2.nMotors = nMot_; -rpmCb2 = 'updateSpec=1; PSplotSpec2D;'; +rpmCb2 = 'PSplotSpec2D;'; for mi = 1:4 fld = sprintf('rpmMotor%d', mi); guiHandlesSpec2.(fld) = uicontrol(PSspecfig2, 'Style','checkbox', 'String', motorNames{mi}, ... diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 1b20671..fd790ff 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -130,7 +130,15 @@ guiHandles.checkbox15=uicontrol(PSfig,'Style','checkbox','String','All','fontsize',fontsz,'TooltipString', ['Plot or clear all lines '],'ForegroundColor',[linec.col15],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox15],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), plotall_flag=get(guiHandles.checkbox15, ''Value''); PSplotLogViewer; end'); rpmColors_ = th.sigRPM; -rpmLabels_ = {'RPM 1', 'RPM 2', 'RPM 3', 'RPM 4'}; +nEmLV_ = 0; +if exist('T','var') && ~isempty(T) + for mi_ = 4:7, if isfield(T{1}, ['eRPM_' int2str(mi_) '_']), nEmLV_ = mi_+1; end, end +end +if nEmLV_ > 4 + rpmLabels_ = {sprintf('RPM 1/%d',nEmLV_/2+1), sprintf('RPM 2/%d',nEmLV_/2+2), sprintf('RPM 3/%d',nEmLV_/2+3), sprintf('RPM 4/%d',nEmLV_/2+4)}; +else + rpmLabels_ = {'RPM 1', 'RPM 2', 'RPM 3', 'RPM 4'}; +end rpmTip_ = 'Motor eRPM trace (Hz) on motor subplot'; for rk_ = 1:4 guiHandles.(['checkboxRPM' int2str(rk_)]) = uicontrol(PSfig,'Style','checkbox','String',rpmLabels_{rk_},'fontsize',fontsz,'TooltipString',rpmTip_,'ForegroundColor',rpmColors_{rk_},'BackgroundColor',bgcolor,... From e2460378ca9519ff5e0448b64213cdb50e2d4e6a Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 30 Mar 2026 13:47:04 +0200 Subject: [PATCH 51/80] perf(motor-noise): pre-compute coherence for all 3 gyro axes, cache in appdata --- src/plot/PSplotMotorNoise.m | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/plot/PSplotMotorNoise.m b/src/plot/PSplotMotorNoise.m index b4266c1..00789df 100644 --- a/src/plot/PSplotMotorNoise.m +++ b/src/plot/PSplotMotorNoise.m @@ -115,12 +115,23 @@ function PSplotMotorNoise(T, f, tIND, Fs) h_leg = legend(axGyro, gyroAxLbl, 'Location', 'northeast'); try PSstyleLegend(h_leg, th); catch, end -% --- MID LEFT: Motor-Gyro coherence --- +% --- MID LEFT: Motor-Gyro coherence (pre-compute all 3 axes) --- axCoh = axes('Parent', fig, 'Units', 'normalized', 'Position', [.06 .39 .42 .25]); dat.axCoh = axCoh; -plotCoherence(axCoh, motorData, gyroData{1}, nMotors, mCol, motorLbl, Fs, fMax, 'Roll'); +cohCache = cell(3, 1); +for gi_ = 1:3 + cc_ = struct(); cc_.Cxy = cell(nMotors, 1); + gdat_ac = gyroData{gi_} - mean(gyroData{gi_}); + for mi_ = 1:nMotors + mdat_ac = motorData{mi_} - mean(motorData{mi_}); + [cc_.Cxy{mi_}, cc_.f] = mscohere_simple(mdat_ac, gdat_ac, Fs, 1024); + end + cohCache{gi_} = cc_; +end +dat.cohCache = cohCache; +plotCohFromCache(axCoh, cohCache{1}, nMotors, mCol, motorLbl, fMax, 'Roll'); -% gyro axis dropdown (right of title, inside plot area) +% gyro axis dropdown uicontrol(fig, 'Style', 'popupmenu', 'String', {'Roll', 'Pitch', 'Yaw'}, ... 'Units', 'normalized', 'Position', [.06 .64 .10 .025], ... 'FontSize', fontsz, 'Callback', {@cohAxisCb, fig}); @@ -177,21 +188,16 @@ function PSplotMotorNoise(T, f, tIND, Fs) function cohAxisCb(src, ~, fig) dat = getappdata(fig, 'mndat'); gIdx = get(src, 'Value'); - gdat = dat.gyroData{gIdx}; - axLbl = dat.gyroAxLbl{gIdx}; - plotCoherence(dat.axCoh, dat.motorData, gdat, dat.nMotors, ... - dat.mCol{1}, dat.motorLbl{1}, dat.Fs, dat.fMax, axLbl); + plotCohFromCache(dat.axCoh, dat.cohCache{gIdx}, dat.nMotors, ... + dat.mCol{1}, dat.motorLbl{1}, dat.fMax, dat.gyroAxLbl{gIdx}); end -function plotCoherence(ax, motorData, gdat, nMotors, mCol, motorLbl, Fs, fMax, axLabel) +function plotCohFromCache(ax, cc, nMotors, mCol, motorLbl, fMax, axLabel) cla(ax); thm = PStheme(); - gdat_ac = gdat - mean(gdat); for m = 1:nMotors - mdat = motorData{m} - mean(motorData{m}); - [Cxy, fCoh] = mscohere_simple(mdat, gdat_ac, Fs, 1024); - plot(ax, fCoh, Cxy, 'Color', mCol{m}, 'LineWidth', 1.0); + plot(ax, cc.f, cc.Cxy{m}, 'Color', mCol{m}, 'LineWidth', 1.0); hold(ax, 'on'); end hold(ax, 'off'); From 21116893e634bfb9b57985fe926c73d517a8580c Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 30 Mar 2026 21:03:25 +0200 Subject: [PATCH 52/80] feat(spectral): use gyroUnfilt for Gyro prefilt on BF 4.5+, fallback to debug for older FW --- src/core/PSload.m | 12 ++++++++++++ src/plot/PSfreqTime.m | 2 +- src/plot/PSplotSpec.m | 2 +- src/plot/PSplotSpec2D.m | 17 ++++++++--------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index 89bb313..bd60b6f 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -200,6 +200,18 @@ gyro_debug_axis(fcnt) = 0; end + % BF 4.5+: gyroUnfilt → gyroPrefilt; fallback to debug if GYRO_SCALED + for ax_ = 0:2 + uf_ = ['gyroUnfilt_' int2str(ax_) '_']; + pf_ = ['gyroPrefilt_' int2str(ax_) '_']; + if isfield(T{fcnt}, uf_) + T{fcnt}.(pf_) = T{fcnt}.(uf_); + elseif debugmode(fcnt) == debugIdx{fcnt}.GYRO_SCALED && debugIdx{fcnt}.GYRO_SCALED > 0 + df_ = ['debug_' int2str(ax_) '_']; + if isfield(T{fcnt}, df_), T{fcnt}.(pf_) = T{fcnt}.(df_); end + end + end + try r = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'rollPID')),2)); p = (SetupInfo{fcnt}(find(strcmp(SetupInfo{fcnt}(:,1), 'pitchPID')),2)); diff --git a/src/plot/PSfreqTime.m b/src/plot/PSfreqTime.m index 48703df..b08c325 100644 --- a/src/plot/PSfreqTime.m +++ b/src/plot/PSfreqTime.m @@ -23,7 +23,7 @@ fLim_freqTime = 1000; end -s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; +s1={'gyroADC';'gyroPrefilt';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'pidsum'}; datSelectionString=[s1]; axisLabel ={'Roll'; 'Pitch' ; 'Yaw'}; tmpFileVal3 = get(guiHandlesSpec3.FileSelect, 'Value'); diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index d154758..71a5e7b 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -98,7 +98,7 @@ %% -s1={'';'gyroADC';'debug';'piderr';'setpoint';'axisP';'axisD';'axisDpf';'pidsum'}; +s1={'';'gyroADC';'gyroPrefilt';'piderr';'setpoint';'axisP';'axisD';'axisDpf';'pidsum'}; datSelectionString=[s1]; diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 4cb36c9..e71958b 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -17,7 +17,7 @@ %% -s1={'gyroADC';'debug';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'axisF';'pidsum';'motorAvg'}; +s1={'gyroADC';'gyroPrefilt';'axisD';'axisDpf';'axisP';'piderr';'setpoint';'axisF';'pidsum';'motorAvg'}; if isfield(T{1}, 'testSignal_0_'), s1{end+1} = 'testSignal'; end datSelectionString=[s1]; @@ -65,7 +65,11 @@ maxlag=round(30000/Fs); try - pg = smooth(T{k}.debug_0_(tIND{k}),50); + if isfield(T{k}, 'gyroPrefilt_0_') + pg = smooth(T{k}.gyroPrefilt_0_(tIND{k}),50); + else + pg = smooth(T{k}.debug_0_(tIND{k}),50); + end catch pg = []; end @@ -571,14 +575,9 @@ psdMat_ = abs(fftMat_(1:halfN_, :)).^2 / (Fs_ * winLen_); psdMat_(2:end-1, :) = 2 * psdMat_(2:end-1, :); fd_.psd{ai_} = 10*log10(psdMat_); - % pre-filter - preFld_ = ['gyroUnfilt_' int2str(ai_-1) '_']; + % pre-filter (gyroPrefilt synthesized in PSload) + preFld_ = ['gyroPrefilt_' int2str(ai_-1) '_']; hp_ = isfield(T{fIdx_mn}, preFld_); - if ~hp_ - preFld_ = ['debug_' int2str(ai_-1) '_']; - hp_ = isfield(T{fIdx_mn}, preFld_) && exist('debugmode','var') && ... - numel(debugmode) >= fIdx_mn && any(debugmode(fIdx_mn) == [3 6]); - end fd_.hasPre(ai_) = hp_; if hp_ preSig_ = T{fIdx_mn}.(preFld_)(tIND{fIdx_mn}); From 81e7aaf4154754078ea0bc333f95a5f7028b74a2 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 2 Apr 2026 17:42:32 +0200 Subject: [PATCH 53/80] feat(step-response): vectorized FFT, subsample/threshold controls, snap maneuvers, auto-hide --- src/core/PSload.m | 4 +- src/core/PSstepcalc.m | 94 ++++++------ src/plot/PSplotSpec2D.m | 2 + src/plot/PStuningParams.m | 309 ++++++++++++++++++++++++++++++++------ src/ui/PStuneUIcontrol.m | 70 +++++++-- src/util/PSresetData.m | 2 +- src/util/PSsaveSettings.m | 6 + tests/test_PSstepcalc.m | 68 +++++++++ 8 files changed, 442 insertions(+), 113 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index bd60b6f..304d611 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -40,7 +40,7 @@ setappdata(PSfig, 'PSoverlay', []); end figs=findobj('Type','figure'); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end - clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; + clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel fcntSR; set(guiHandles.FileNum, 'String', ' '); try, set(guiHandles.Epoch1_A_Input, 'String', ' '); set(guiHandles.Epoch2_A_Input, 'String', ' '); catch, end try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end @@ -162,7 +162,7 @@ setappdata(PSfig, 'PSoverlay', []); end figs=findobj('Type','figure'); for fi=1:numel(figs), if figs(fi)~=PSfig, try, close(figs(fi)); catch, end; end; end - clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel; + clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel freqTimeCrtlpanel tuneCrtlpanel fcntSR; set(guiHandles.FileNum, 'String', ' '); try, set(guiHandles.Epoch1_A_Input, 'String', ' '); set(guiHandles.Epoch2_A_Input, 'String', ' '); catch, end try setappdata(PSfig, 'smoothCacheLV', struct()); catch, end diff --git a/src/core/PSstepcalc.m b/src/core/PSstepcalc.m index 25c43a2..e51ff02 100644 --- a/src/core/PSstepcalc.m +++ b/src/core/PSstepcalc.m @@ -1,5 +1,5 @@ -function [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection, smoothFactor) -%% [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection) +function [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection, smoothFactor, subsampleFactor, minRate, maxRate) +%% [stepresponse, t] = PSstepcalc(SP, GY, lograte, Ycorrection, smoothFactor, subsampleFactor, minRate, maxRate) % this function deconvolves the step response function using % SP = set point (input), GY = filtered gyro (output) % returns matrix/stack of etimated stepresponse functions, time [t] in ms @@ -12,24 +12,18 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- smoothVals = [1 20 40 60]; -GY = smooth(GY, smoothVals(smoothFactor),'lowess'); +if smoothFactor > 1 + GY = smooth(GY, smoothVals(smoothFactor),'lowess'); +end + +if nargin < 6 || isempty(subsampleFactor), subsampleFactor = 5; end +if nargin < 7 || isempty(minRate), minRate = 40; end +if nargin < 8 || isempty(maxRate), maxRate = 500; end -minInput = 20; segment_length = (lograte*2000); % 2 sec segments -wnd = (lograte*1000) * .5; % 500ms step response function, length will depend on lograte +wnd = (lograte*1000) * .5; % 500ms step response function, length will depend on lograte StepRespDuration_ms = 500; % max dur of step resp in ms for plotting -t = 0 : 1/lograte : StepRespDuration_ms;% time in ms - -fileDurSec = length(SP) / (lograte*1000); -subsampleFactor = 1; -switch subsampleFactor - case fileDurSec <= 20 - subsampleFactor = 10; - case fileDurSec > 20 & fileDurSec <= 60 - subsampleFactor = 7; - case fileDurSec > 60 - subsampleFactor = 3; -end +t = 0 : 1/lograte : StepRespDuration_ms;% time in ms segment_vector = 1 : round(segment_length/subsampleFactor) : length(SP); tmp = find((segment_vector+segment_length) < segment_vector(end)); @@ -38,48 +32,48 @@ SPseg = zeros(NSegs, segment_length+1); GYseg = zeros(NSegs, segment_length+1); j = 0; for i = 1 : NSegs - if max(abs(SP(segment_vector(i):segment_vector(i)+segment_length))) >= minInput + peakRate = max(abs(SP(segment_vector(i):segment_vector(i)+segment_length))); + if peakRate >= minRate && peakRate <= maxRate j=j+1; SPseg(j,:) = SP(segment_vector(i):segment_vector(i)+segment_length); GYseg(j,:) = GY(segment_vector(i):segment_vector(i)+segment_length); end end - padLength = 100;% 2^nextpow2(length(SPseg(i,:))); - clear resp resp2 G H Hcon imp impf a b - j=0; - if ~isempty(SPseg) - for i = 1 : size(SPseg,1) - a = GYseg(i,:).*hann(length(GYseg(i,:)))'; - b = SPseg(i,:).*hann(length(SPseg(i,:)))'; - a = fft([zeros(1,padLength) a zeros(1,padLength)]); - b = fft([zeros(1,padLength) b zeros(1,padLength)]); - G = a / length(a); - H = b / length(b); - Hcon = conj(H); + nValid = j; + if nValid > 0 + SPseg = SPseg(1:nValid,:); + GYseg = GYseg(1:nValid,:); - imp = real(ifft((G .* Hcon) ./ (H .* Hcon + 0.0001 )))'; % impulse response function - resptmp(i,:) = cumsum(imp);% integrate impulse resp function - - clear a steadyStateWindow steadyStateResp yoffset - steadyStateWindow = find(t > 200 & t < StepRespDuration_ms); - steadyStateResp = resptmp(i, steadyStateWindow); + padLength = 100; + segW = size(SPseg, 2); + w = hann(segW)'; + + % vectorized FFT: all segments at once + A = [zeros(nValid, padLength), GYseg .* w, zeros(nValid, padLength)]; + B = [zeros(nValid, padLength), SPseg .* w, zeros(nValid, padLength)]; + fftLen = size(A, 2); + A = fft(A, [], 2) / fftLen; + B = fft(B, [], 2) / fftLen; + Bcon = conj(B); + resptmp = cumsum(real(ifft((A .* Bcon) ./ (B .* Bcon + 0.0001), [], 2)), 2); + + steadyStateWindow = find(t > 200 & t < StepRespDuration_ms); + j = 0; + for i = 1:nValid + steadyStateResp = resptmp(i, steadyStateWindow); if Ycorrection - if nanmean(steadyStateResp) < 1 || nanmean(steadyStateResp) > 1 - yoffset = 1 - nanmean(steadyStateResp); - resptmp(i,:) = resptmp(i,:) * (yoffset+1); - end - steadyStateResp = resptmp(i, steadyStateWindow); - else + ssm = nanmean(steadyStateResp); + if ssm ~= 1 + resptmp(i,:) = resptmp(i,:) * (2 - ssm); + end + steadyStateResp = resptmp(i, steadyStateWindow); end - - if min(steadyStateResp) > 0.5 && max(steadyStateResp) < 3 % Quality control - j=j+1; - stepresponse(j,:)=resptmp(i,1:1+wnd); - - end - end - else + if min(steadyStateResp) > 0.5 && max(steadyStateResp) < 3 + j = j + 1; + stepresponse(j,:) = resptmp(i, 1:1+wnd); + end + end end else end diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index e71958b..e58fd48 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -252,6 +252,8 @@ leftKey_.combo = get(guiHandlesSpec2.RPYcomboSpec, 'Value'); catch, end skipLeftRender_ = ~needFFT_ && exist('prevLeftKey_','var') && isequal(leftKey_, prevLeftKey_); +% invalidate if axes were deleted externally +if skipLeftRender_ && isempty(findobj(PSspecfig2, 'Type', 'axes', 'Tag', 'PSspec2_1')), skipLeftRender_ = false; end if ~skipLeftRender_ % cla instead of delete to avoid Qt rendering artifacts (stale white pixels) diff --git a/src/plot/PStuningParams.m b/src/plot/PStuningParams.m index 030ada5..681ddeb 100644 --- a/src/plot/PStuningParams.m +++ b/src/plot/PStuningParams.m @@ -16,6 +16,16 @@ ylab={'Roll';'Pitch';'Yaw'}; ylab2={'roll';'pitch';'yaw'}; +% re-enable axes auto-hidden by previous run (only on fresh start) +if ~updateStep && fcntSR == 0 + rpyH_ = {guiHandlesTune.plotR, guiHandlesTune.plotP, guiHandlesTune.plotY}; + for pi_ = 1:3 + if strcmp(get(rpyH_{pi_}, 'Enable'), 'off') + set(rpyH_{pi_}, 'Value', 1, 'Enable', 'on'); + end + end +end + axesOptions = find([get(guiHandlesTune.plotR, 'Value') get(guiHandlesTune.plotP, 'Value') get(guiHandlesTune.plotY, 'Value')]); % scale row heights to fill space when fewer than 3 RPY axes @@ -55,10 +65,111 @@ ymax = str2double(get(guiHandlesTune.maxYStepInput, 'String')); ypos = [(ymax/3)*2.9 (ymax/3)*1.85 (ymax/3)*.8]; hwarn=[]; -if ~get(guiHandlesTune.clearPlots, 'Value') +showBFsliders = isfield(guiHandlesTune, 'bfSliders') && get(guiHandlesTune.bfSliders, 'Value') == 2; + +if ~get(guiHandlesTune.clearPlots, 'Value') && showBFsliders + % BF Sliders view: read simplified_* headers and draw horizontal bars + set(PStunefig, 'pointer', 'watch'); drawnow; + sliderKeys_ = {'simplified_d_gain','simplified_pi_gain','simplified_feedforward_gain',... + 'simplified_d_max_gain','simplified_i_gain','simplified_pitch_d_gain',... + 'simplified_pitch_pi_gain','simplified_master_multiplier'}; + sliderLabels_ = {'Damping','Tracking','Stick Resp.','Dynamic D.',... + 'Drift-Wobble','Pitch D.','Pitch T.','Master'}; + nSliders_ = numel(sliderKeys_); + selFiles_ = get(guiHandlesTune.fileListWindowStep, 'Value'); + + h_sl = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_bfsliders'); + if isempty(h_sl) + h_sl = axes('Parent', PStunefig, 'Position', [0.07 0.10 plotR-0.09 0.84], 'Tag', 'PSstep_bfsliders'); + else + set(PStunefig, 'CurrentAxes', h_sl); cla; + end + hold on; + + % horizontal bar lines + for si_ = 1:nSliders_ + plot([0 2], [si_ si_], '-', 'Color', th.textSecondary, 'LineWidth', 1.5); + end + + hasData_ = false; + for fi_ = 1:numel(selFiles_) + f_ = selFiles_(fi_); + if f_ > numel(SetupInfo), continue; end + colIdx_ = min(fi_, size(multiLineCols,1)); + vals_ = ones(1, nSliders_); + anyFound_ = false; + for si_ = 1:nSliders_ + idx_ = find(strcmp(SetupInfo{f_}(:,1), sliderKeys_{si_})); + if ~isempty(idx_) + v_ = str2double(SetupInfo{f_}{idx_(1), 2}); + if ~isnan(v_), vals_(si_) = v_ / 100; anyFound_ = true; end + end + end + if anyFound_ + hasData_ = true; + for si_ = 1:nSliders_ + plot(vals_(si_), si_, 'o', 'MarkerSize', 10, 'MarkerFaceColor', multiLineCols(colIdx_,:), ... + 'MarkerEdgeColor', 'none'); + end + end + end + + if ~hasData_ + text(1, nSliders_/2+0.5, 'No BF slider data in headers', 'fontsize', fontsz+2, ... + 'HorizontalAlignment', 'center', 'Color', th.textSecondary); + end + + set(h_sl, 'YTick', 1:nSliders_, 'YTickLabel', sliderLabels_, 'YDir', 'reverse', ... + 'XLim', [0 2], 'YLim', [0.5 nSliders_+0.5], ... + 'XTick', 0:0.2:2, 'fontsize', fontsz, 'TickDir', 'out'); + xlabel('Slider Position', 'fontweight', 'bold'); + title('Betaflight PID Sliders', 'fontweight', 'bold'); + grid on; box off; + PSstyleAxes(h_sl, th); + + % legend: file names with colors + for fi_ = 1:numel(selFiles_) + colIdx_ = min(fi_, size(multiLineCols,1)); + text(1.85, 0.7 + fi_*0.06, fnameMaster{selFiles_(fi_)}, 'fontsize', max(fontsz-1,6), ... + 'Color', multiLineCols(colIdx_,:), 'Units', 'normalized', ... + 'HorizontalAlignment', 'right', 'fontweight', 'bold'); + end + + % disable irrelevant controls in BF sliders mode + bfOff_ = {'plotR','plotP','plotY','snapManeuver','RPYcombo','rawTraces',... + 'Ycorrection','maxYStepInput','smoothFactor_select','srLatency',... + 'subsample','minRateInput','maxRateInput','minRateTxt','maxYStepTxt',... + 'period','markup'}; + for bi_ = 1:numel(bfOff_) + if isfield(guiHandlesTune, bfOff_{bi_}), set(guiHandlesTune.(bfOff_{bi_}), 'Enable', 'off'); end + end + + try PSresizeCP(PStunefig, []); catch, end + set(PStunefig, 'pointer', 'arrow'); + updateStep = 0; + +elseif ~get(guiHandlesTune.clearPlots, 'Value') + % delete BF sliders axes if switching back to normal view + h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_bfsliders'); + if ~isempty(h_del), delete(h_del); end + + % re-enable controls when leaving BF sliders mode + bfOff_ = {'plotR','plotP','plotY','snapManeuver','RPYcombo','rawTraces',... + 'Ycorrection','maxYStepInput','smoothFactor_select','srLatency',... + 'subsample','minRateInput','maxRateInput','minRateTxt','maxYStepTxt',... + 'period','markup'}; + for bi_ = 1:numel(bfOff_) + if isfield(guiHandlesTune, bfOff_{bi_}), set(guiHandlesTune.(bfOff_{bi_}), 'Enable', 'on'); end + end + + if ~updateStep && fcntSR == 0 + peakresp = []; peakresp_std = []; peaktime = []; + latencyHalfHeight = []; latencyHalfHeight_std = []; + settlingMin = []; settlingMax = []; + end cnt = 0; set(PStunefig, 'pointer', 'watch') - pause(.05); + drawnow; for f = get(guiHandlesTune.fileListWindowStep, 'Value') fcntSR = fcntSR + 1; @@ -69,10 +180,24 @@ cnt2 = cnt2 + 1; try if ~updateStep - clear H G L H = T{f}.(['setpoint_' int2str(p-1) '_'])(tIND{f}); G = T{f}.(['gyroADC_' int2str(p-1) '_'])(tIND{f}); - [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value')); + subMap_ = [0 1 3 5 7 10]; + subVal_ = subMap_(get(guiHandlesTune.subsample, 'Value')); + if subVal_ == 0 + fileDurSec_ = length(H) / (A_lograte(f)*1000); + if fileDurSec_ <= 20, subVal_ = 10; + elseif fileDurSec_ <= 60, subVal_ = 7; + else subVal_ = 3; end + end + minR_ = str2double(get(guiHandlesTune.minRateInput, 'String')); + maxR_ = str2double(get(guiHandlesTune.maxRateInput, 'String')); + if isnan(minR_), minR_ = 40; end + if isnan(maxR_), maxR_ = 500; end + if get(guiHandlesTune.snapManeuver, 'Value') + minR_ = maxR_; maxR_ = Inf; + end + [stepresp_A{p} tA] = PSstepcalc(H, G, A_lograte(f), get(guiHandlesTune.Ycorrection, 'Value'), get(guiHandlesTune.smoothFactor_select, 'Value'), subVal_, minR_, maxR_); try xcorrLag_cache(p) = finddelay(H, G) / A_lograte(f); catch, xcorrLag_cache(p) = nan; end end catch @@ -102,9 +227,10 @@ % raw segment traces - blend toward bg to simulate low alpha if isfield(guiHandlesTune, 'rawTraces') && get(guiHandlesTune.rawTraces, 'Value') col_raw = col_i * 0.12 + th.axesBg * 0.88; - for si = 1:size(s,1) - plot(tA, s(si,:), 'Color', col_raw, 'LineWidth', 0.5); - end + nSeg_ = size(s,1); + tAll_ = repmat([tA NaN], 1, nSeg_); + sAll_ = reshape([s'; NaN(1, nSeg_)], 1, []); + plot(tAll_, sAll_, 'Color', col_raw, 'LineWidth', 0.5); end h1=plot(tA,m); @@ -112,27 +238,37 @@ if get(guiHandlesTune.srLatency, 'Value') == 2 && exist('xcorrLag_cache', 'var') latencyHalfHeight(p, fcntSR) = xcorrLag_cache(p); else - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; - end - peakresp(p, fcntSR)=max(m(find(tA<150))); - peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); - - % per-segment metrics for error bars - peakIdx = find(tA < 150); - segPeaks = zeros(1, size(s,1)); - segLats = zeros(1, size(s,1)); - for si = 1:size(s,1) - segPeaks(si) = max(s(si, peakIdx)); - idx50 = find(s(si,:) > 0.5, 1); - if ~isempty(idx50) - segLats(si) = (idx50 / A_lograte(f)) - 1; + idx50_ = find(m > .5, 1); + if ~isempty(idx50_) + latencyHalfHeight(p, fcntSR) = (idx50_ / A_lograte(f)) - 1; else - segLats(si) = nan; + latencyHalfHeight(p, fcntSR) = nan; end end + peakIdx = find(tA < 150); + peakresp(p, fcntSR) = max(m(peakIdx)); + [~, pkI_] = max(m(peakIdx)); + peaktime(p, fcntSR) = pkI_(1) / A_lograte(f); + + % per-segment metrics for error bars (vectorized) + segPeaks = max(s(:, peakIdx), [], 2)'; + above50 = s > 0.5; + [~, firstAbove] = max(above50, [], 2); + segLats = (firstAbove' / A_lograte(f)) - 1; + segLats(~any(above50, 2)') = nan; peakresp_std(p, fcntSR) = nanstd(segPeaks); latencyHalfHeight_std(p, fcntSR) = nanstd(segLats); + % settling metrics (200-500ms window) + settleIdx_ = find(tA > 200 & tA < 500); + if ~isempty(settleIdx_) + settlingMin(p, fcntSR) = min(m(settleIdx_)); + settlingMax(p, fcntSR) = max(m(settleIdx_)); + else + settlingMin(p, fcntSR) = nan; + settlingMax(p, fcntSR) = nan; + end + pidvar = [ylab2{p} 'PIDF']; PID = eval([pidvar '{f}']); else @@ -141,6 +277,8 @@ latencyHalfHeight(p, fcntSR) = nan; peakresp_std(p, fcntSR) = nan; latencyHalfHeight_std(p, fcntSR) = nan; + settlingMin(p, fcntSR) = nan; + settlingMax(p, fcntSR) = nan; PID = ''; end @@ -165,15 +303,23 @@ hold on if size(stepresp_A{p},1)>1 if cnt <= 3, h=text(0.05, 0.97, [pidlabels],'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(0.05, 0.97-(fcntSR*.09), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')'],'fontsize',fontsz); + yBase_ = 0.88 - (fcntSR-1)*0.11; + h=text(0.05, yBase_, [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')'],'fontsize',fontsz); set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') + metStr_ = sprintf('Pk=%.2f @%dms L=%dms S=[%.2f %.2f]', ... + peakresp(p,fcntSR), round(peaktime(p,fcntSR)), round(latencyHalfHeight(p,fcntSR)), ... + settlingMin(p,fcntSR), settlingMax(p,fcntSR)); + h=text(0.07, yBase_-0.045, metStr_,'fontsize',max(fontsz-1,6)); + set(h, 'Color',[multiLineCols(fcntSR,:)]) else if cnt <= 3, h=text(0.05, 0.97, [pidlabels],'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(0.05, 0.97-(fcntSR*.09), [int2str(fcntSR) ') insufficient data'],'fontsize',fontsz); + yBase_ = 0.88 - (fcntSR-1)*0.11; + h=text(0.05, yBase_, [int2str(fcntSR) ') insufficient data'],'fontsize',fontsz); set(h,'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') end - % Col 3: Peak + % Col 3: Peak (skip if no data) + if ~isnan(peakresp(p, fcntSR)) stag_ = sprintf('PSstep_%d', p+6); h2 = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); if isempty(h2), h2 = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p+6,:), 'Tag', stag_); @@ -195,15 +341,17 @@ ymnP = min(ymnP, peakresp(p, ei) - peakresp_std(p, ei)); end end - ymxP = ymxP + 0.05; ymnP = max(0, ymnP - 0.05); + ymnP = floor(ymnP * 10) / 10; + ymxP = ceil(ymxP * 10) / 10; set(gca,'fontsize',fontsz, 'ylim',[ymnP ymxP],'ytick',[ymnP:.1:ymxP],'xlim',[0.5 fcntSR+0.5],'xtick',[1:fcntSR]) - ylabel([ylab{p} ' Peak '], 'fontweight','bold'); - xlabel('test', 'fontweight','bold'); + if cnt <= 3, title([ylab{p} ' Peak'], 'fontweight','bold'); end hold on grid on plot([0 10],[1 1],'--','Color',th.axesFg) + end - % Col 4: Latency + % Col 4: Latency (skip if no data) + if ~isnan(latencyHalfHeight(p, fcntSR)) stag_ = sprintf('PSstep_%d', p+9); h3 = findobj(PStunefig, 'Type', 'axes', 'Tag', stag_); if isempty(h3), h3 = axes('Parent', PStunefig, 'Position', posInfo.TparamsPos(p+9,:), 'Tag', stag_); @@ -219,24 +367,30 @@ line([fcntSR-capW fcntSR+capW], [yc+ey yc+ey], 'Color', multiLineCols(fcntSR,:), 'LineWidth', 1.2); end - mn = min(latencyHalfHeight(p, :))-rem(min(latencyHalfHeight(p, :)),2); - mx = max(latencyHalfHeight(p, :))+rem(max(latencyHalfHeight(p, :)),2); + mn = min(latencyHalfHeight(p, :)); + mx = max(latencyHalfHeight(p, :)); for ei = 1:size(latencyHalfHeight_std, 2) if ~isnan(latencyHalfHeight_std(p, ei)) mn = min(mn, latencyHalfHeight(p, ei) - latencyHalfHeight_std(p, ei)); mx = max(mx, latencyHalfHeight(p, ei) + latencyHalfHeight_std(p, ei)); end end - ymaxLat = mx+4; - yminLat = mn-4; + yminLat = 2*floor((mn-2)/2); + ymaxLat = 2*ceil((mx+2)/2); + latRange_ = ymaxLat - yminLat; + if latRange_ > 40, latStep_ = 10; + elseif latRange_ > 20, latStep_ = 5; + else latStep_ = 2; end + yminLat = latStep_*floor((mn-2)/latStep_); + ymaxLat = latStep_*ceil((mx+2)/latStep_); try - set(gca,'fontsize',fontsz,'ylim',[yminLat ymaxLat],'ytick',[yminLat:2:ymaxLat], 'xtick',[1:fcntSR],'xlim',[0.5 fcntSR+0.5]) + set(gca,'fontsize',fontsz,'ylim',[yminLat ymaxLat],'ytick',[yminLat:latStep_:ymaxLat], 'xtick',[1:fcntSR],'xlim',[0.5 fcntSR+0.5]) catch end - ylabel([ylab{p} ' Latency (ms) '], 'fontweight','bold'); - xlabel('Test', 'fontweight','bold'); + if cnt <= 3, title([ylab{p} ' Latency (ms)'], 'fontweight','bold'); end hold on grid on + end end @@ -263,9 +417,10 @@ if isfield(guiHandlesTune, 'rawTraces') && get(guiHandlesTune.rawTraces, 'Value') col_raw = col_i * 0.12 + th.axesBg * 0.88; - for si = 1:size(s,1) - plot(tA, s(si,:), 'Color', col_raw, 'LineWidth', 0.5); - end + nSeg_ = size(s,1); + tAll_ = repmat([tA NaN], 1, nSeg_); + sAll_ = reshape([s'; NaN(1, nSeg_)], 1, []); + plot(tAll_, sAll_, 'Color', col_raw, 'LineWidth', 0.5); end h1=plot(tA,m); @@ -273,10 +428,25 @@ if get(guiHandlesTune.srLatency, 'Value') == 2 && exist('xcorrLag_cache', 'var') latencyHalfHeight(p, fcntSR) = xcorrLag_cache(p); else - latencyHalfHeight(p, fcntSR) = (find(m>.5,1) / A_lograte(f)) - 1; + idx50_ = find(m > .5, 1); + if ~isempty(idx50_) + latencyHalfHeight(p, fcntSR) = (idx50_ / A_lograte(f)) - 1; + else + latencyHalfHeight(p, fcntSR) = nan; + end + end + peakIdx_ = find(tA < 150); + peakresp(p, fcntSR) = max(m(peakIdx_)); + [~, pkI_] = max(m(peakIdx_)); + peaktime(p, fcntSR) = pkI_(1) / A_lograte(f); + settleIdx_ = find(tA > 200 & tA < 500); + if ~isempty(settleIdx_) + settlingMin(p, fcntSR) = min(m(settleIdx_)); + settlingMax(p, fcntSR) = max(m(settleIdx_)); + else + settlingMin(p, fcntSR) = nan; + settlingMax(p, fcntSR) = nan; end - peakresp(p, fcntSR)=max(m(find(tA<150))); - peaktime(p, fcntSR)=find(m == max(m(find(tA<150)))) / A_lograte(f); PID = eval([ylab2{p} 'PIDF{f}']); if cnt <= 3 @@ -290,13 +460,19 @@ h=text(505, ypos(p), [pidlabels]); set(h,'fontsize',fontsz,'fontweight','bold','Color',th.textPrimary); end - h=text(505, ypos(p)-(fcntSR*.044), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']);set(h,'fontsize',fontsz); - set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold') - set(h,'fontsize',fontsz) + h=text(505, ypos(p)-(fcntSR*.07), [int2str(fcntSR) ') ' PID ' (n=' int2str(size(stepresp_A{p},1)) ')']); + set(h, 'Color',[multiLineCols(fcntSR,:)],'fontweight','bold','fontsize',fontsz) + metStr_ = sprintf('Pk=%.2f @%dms L=%dms S=[%.2f-%.2f]', ... + peakresp(p,fcntSR), round(peaktime(p,fcntSR)), round(latencyHalfHeight(p,fcntSR)), ... + settlingMin(p,fcntSR), settlingMax(p,fcntSR)); + h=text(510, ypos(p)-(fcntSR*.07)-0.03, metStr_,'fontsize',max(fontsz-1,6)); + set(h, 'Color',[multiLineCols(fcntSR,:)]) else peakresp(p, fcntSR) = nan; peaktime(p, fcntSR) = nan; latencyHalfHeight(p, fcntSR) = nan; + settlingMin(p, fcntSR) = nan; + settlingMax(p, fcntSR) = nan; if cnt <= 3 if size(axesOptions,2) < 2 h=text(505, ypos(p)+0.04, [ylab{p}]); @@ -331,6 +507,45 @@ warndlg('10 files maximum. Click reset.'); end end + % auto-hide axes where ALL files had insufficient data + if ~updateStep && ~isempty(peakresp) + rpyH_ = {guiHandlesTune.plotR, guiHandlesTune.plotP, guiHandlesTune.plotY}; + needRedraw_ = false; + for pi_ = 1:3 + if get(rpyH_{pi_}, 'Value') == 1 && size(peakresp,1) >= pi_ + if all(isnan(peakresp(pi_, :))) + set(rpyH_{pi_}, 'Value', 0, 'Enable', 'off'); + for off_ = [0 3 6 9] + h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', sprintf('PSstep_%d', pi_+off_)); + if ~isempty(h_del), delete(h_del); end + end + needRedraw_ = true; + end + end + end + if needRedraw_ + axesOptions = find([get(guiHandlesTune.plotR,'Value') get(guiHandlesTune.plotP,'Value') get(guiHandlesTune.plotY,'Value')]); + nActiveTune = numel(axesOptions); + if nActiveTune > 0 && nActiveTune < 3 + topY_t = stdRows_t(1) + stdRowH_t; botY_t = stdRows_t(3); gapT = 0.05; + rowH_t = (topY_t - botY_t - (nActiveTune-1)*gapT) / nActiveTune; + ci = 0; + for jj = axesOptions + ci = ci + 1; + yy = topY_t - ci*rowH_t - (ci-1)*gapT; + for cc = 0:3 + htmp_ = findobj(PStunefig,'Type','axes','Tag',sprintf('PSstep_%d',jj+cc*3)); + if ~isempty(htmp_) + pos_ = get(htmp_(1),'Position'); + pos_(2) = yy; pos_(4) = rowH_t; + set(htmp_(1),'Position',pos_); + end + end + end + end + end + end + allax = findobj(PStunefig, 'Type', 'axes', 'Visible', 'on'); for axi = 1:numel(allax), PSstyleAxes(allax(axi), th); end try PSresizeCP(PStunefig, []); catch, end @@ -349,9 +564,13 @@ latencyHalfHeight = []; latencyHalfHeight_std = []; peakresp_std = []; + settlingMin = []; + settlingMax = []; end h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_combo'); if ~isempty(h_del), delete(h_del); end + h_del = findobj(PStunefig, 'Type', 'axes', 'Tag', 'PSstep_bfsliders'); + if ~isempty(h_del), delete(h_del); end end diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 57ce892..8f4f43d 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -72,12 +72,19 @@ posInfo.markup= [cpL+cpW/2 yTop-rh hw rh]; yTop=yTop-rh-gap; posInfo.smooth_tuning= [cpL+cpM yTop-ddh fw/2-gap ddh]; posInfo.srLatency= [cpL+cpM+fw/2 yTop-ddh fw/2 ddh]; yTop=yTop-ddh-gap; +posInfo.subsample= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; +w3_ = (fw - 2*gap) / 3; +posInfo.minRateTxt= [cpL+cpM yTop-rhs w3_ rhs]; +posInfo.minRateInput= [cpL+cpM+w3_+gap yTop-rh w3_ rh]; +posInfo.maxRateInput= [cpL+cpM+2*(w3_+gap) yTop-rh w3_ rh]; yTop=yTop-rh-gap; +posInfo.snapManeuver= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; posInfo.plotR= [cpL+cpM yTop-rh cbW rh]; posInfo.plotP= [cpL+cpM+cbW yTop-rh cbW rh]; posInfo.plotY= [cpL+cpM+2*cbW yTop-rh cbW rh]; yTop=yTop-rh-gap; posInfo.RPYcombo= [cpL+cpM yTop-rh fw/2-gap rh]; posInfo.rawTraces= [cpL+cpM+fw/2 yTop-rh fw/2 rh]; yTop=yTop-rh-gap; posInfo.Ycorrection= [cpL+cpM yTop-rh fw rh]; yTop=yTop-rh-gap; +posInfo.bfSliders= [cpL+cpM yTop-ddh fw ddh]; yTop=yTop-ddh-gap; posInfo.maxYStepInput= [cpL+cpM yTop-rh cpW/3 rh]; posInfo.maxYStepTxt= [cpL+cpW/3+cpM yTop-rhs cpW/2 rhs]; @@ -88,11 +95,11 @@ 'Position',[cpL yTop-rh-gap cpW vPos-(yTop-rh-gap)+cpTitleH+cpMv]); guiHandlesTune.run4 = uicontrol(PStunefig,'string','Run','fontsize',fontsz,'TooltipString',[TooltipString_steprun],'units','normalized','Position',[posInfo.run4],... - 'callback','PStuningParams;'); + 'callback','updateStep = 0; PStuningParams;'); set(guiHandlesTune.run4, 'ForegroundColor', colRun); guiHandlesTune.fileListWindowStep = uicontrol(PStunefig,'Style','listbox','string',[fnameMaster],'max',10,'min',1,... - 'fontsize',fontsz,'TooltipString', [TooltipString_fileListWindowStep],'units','normalized','Position', [posInfo.fileListWindowStep],'callback','@selection2;'); + 'fontsize',fontsz,'TooltipString', [TooltipString_fileListWindowStep],'units','normalized','Position', [posInfo.fileListWindowStep]); set(guiHandlesTune.fileListWindowStep, 'Value', 1); guiHandlesTune.saveFig4 = uicontrol(PStunefig,'string','Save Fig','fontsize',fontsz,'TooltipString',[TooltipString_saveFig],'units','normalized','Position',[posInfo.saveFig4],... @@ -112,18 +119,38 @@ 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.smoothFactor_select, 'Value', 1); guiHandlesTune.srLatency = uicontrol(PStunefig,'style','popupmenu','string',{'SR Latency' 'Xcorr Latency'},'fontsize',fontsz,'TooltipString', 'Latency measurement method', 'units','normalized','Position',[posInfo.srLatency],... - 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + +guiHandlesTune.subsample = uicontrol(PStunefig,'style','popupmenu','string',{'sub auto' 'sub low (fastest)' 'sub med-low' 'sub medium' 'sub med-high' 'sub high (slowest)'},... + 'fontsize',fontsz,'TooltipString', [TooltipString_steprun], 'units','normalized','Position',[posInfo.subsample],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.subsample, 'Value', 1); + +guiHandlesTune.minRateTxt = uicontrol(PStunefig,'style','text','string','deg/s','fontsize',fontsz,... + 'TooltipString', [TooltipString_minRate], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.minRateTxt]); +guiHandlesTune.minRateInput = uicontrol(PStunefig,'style','edit','string','40','fontsize',fontsz,... + 'TooltipString', [TooltipString_minRate], 'units','normalized','Position',[posInfo.minRateInput],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +guiHandlesTune.maxRateInput = uicontrol(PStunefig,'style','edit','string','500','fontsize',fontsz,... + 'TooltipString', [TooltipString_maxRate], 'units','normalized','Position',[posInfo.maxRateInput],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + +guiHandlesTune.snapManeuver = uicontrol(PStunefig,'Style','checkbox','String','Snap maneuvers','fontsize',fontsz,... + 'TooltipString', [TooltipString_FastStepResp], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.snapManeuver],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.snapManeuver, 'Value', 0); guiHandlesTune.plotR =uicontrol(PStunefig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... - 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.plotR, 'Value', 1); guiHandlesTune.plotP =uicontrol(PStunefig,'Style','checkbox','String','P','fontsize',fontsz,'TooltipString', ['Plot Pitch '],... - 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisPitch,'Position',[posInfo.plotP],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisPitch,'Position',[posInfo.plotP],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.plotP, 'Value', 1); guiHandlesTune.plotY =uicontrol(PStunefig,'Style','checkbox','String','Y','fontsize',fontsz,'TooltipString', ['Plot Yaw '],... - 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisYaw,'Position',[posInfo.plotY],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); -set(guiHandlesTune.plotY, 'Value', 0); + 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisYaw,'Position',[posInfo.plotY],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); +set(guiHandlesTune.plotY, 'Value', 1); guiHandlesTune.clearPlots = uicontrol(PStunefig,'string','Reset','fontsize',fontsz,'TooltipString',[TooltipString_clearPlot],'units','normalized','Position',[posInfo.clearPlots],... 'callback','set(guiHandlesTune.clearPlots, ''Value'', 1); set(guiHandlesTune.clearPlots, ''FontWeight'', ''bold''); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); set(PStunefig, ''pointer'', ''arrow'');'); @@ -134,17 +161,21 @@ set(guiHandlesTune.Ycorrection, 'Value', 0); guiHandlesTune.RPYcombo =uicontrol(PStunefig,'Style','checkbox','String','Single Panel','fontsize',fontsz,'TooltipString', ['Plot RPY in same panel '],... - 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.RPYcombo],'callback', 'delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.RPYcombo, 'Value', 0); guiHandlesTune.rawTraces =uicontrol(PStunefig,'Style','checkbox','String','Raw','fontsize',fontsz,'TooltipString', ['Show individual segment traces'],... 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.rawTraces],... - 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); set(guiHandlesTune.rawTraces, 'Value', 0); +guiHandlesTune.bfSliders =uicontrol(PStunefig,'Style','popupmenu','String',{'Peak-Latency' 'BF sliders'},'fontsize',fontsz,'TooltipString', ['Switch between Peak/Latency scatter and BF slider positions'],... + 'units','normalized','Position',[posInfo.bfSliders],... + 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + guiHandlesTune.maxYStepTxt = uicontrol(PStunefig,'style','text','string','Y max ','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYStepTxt]); guiHandlesTune.maxYStepInput = uicontrol(PStunefig,'style','edit','string','1.75','fontsize',fontsz,'TooltipString', ['Y scale max'],'units','normalized','Position',[posInfo.maxYStepInput],... - 'callback','@textinput_call3; set(guiHandlesTune.clearPlots, ''Value'', 1); fcntSR = 0; PStuningParams; set(guiHandlesTune.clearPlots, ''Value'', 0); set(guiHandlesTune.clearPlots, ''FontWeight'', ''normal''); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); + 'callback','@textinput_call3; delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 1; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); tuneCrtlpanel_init = true; end % ishandle(tuneCrtlpanel) @@ -163,23 +194,32 @@ cpI{end+1} = struct('h', guiHandlesTune.markup, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.smoothFactor_select, 'type','left', 'row',0, 'col',0, 'hpx',ddh_px); cpI{end+1} = struct('h', guiHandlesTune.srLatency, 'type','right', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesTune.subsample, 'type','full', 'row',0, 'col',0, 'hpx',ddh_px); +cpI{end+1} = struct('h', guiHandlesTune.minRateTxt, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.minRateInput, 'type','cb', 'row',0, 'col',1, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.maxRateInput, 'type','cb_end', 'row',0, 'col',2, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.snapManeuver, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.plotR, 'type','cb', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.plotP, 'type','cb', 'row',0, 'col',1, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.plotY, 'type','cb_end', 'row',0, 'col',2, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.RPYcombo, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.rawTraces, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.Ycorrection, 'type','full', 'row',0, 'col',0, 'hpx',rh_px); +cpI{end+1} = struct('h', guiHandlesTune.bfSliders, 'type','full', 'row',0, 'col',0, 'hpx',ddh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepInput, 'type','left', 'row',0, 'col',0, 'hpx',rh_px); cpI{end+1} = struct('h', guiHandlesTune.maxYStepTxt, 'type','right', 'row',0, 'col',0, 'hpx',rh_px); setappdata(PStunefig, 'PSplotGrid', struct('plotL',plotLt, 'colGap',colGapT, ... 'ncols',4, 'rows',rows, 'rowH',0.245, 'margin',0.02, 'colWidthFracs',colFracs)); PSregisterResize(PStunefig, cpPx, cpI, 'seq'); -try set(guiHandlesTune.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotR')))), catch, set(guiHandlesTune.plotR, 'Value', 1), end -try set(guiHandlesTune.plotP, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotP')))), catch, set(guiHandlesTune.plotP, 'Value', 1), end -try set(guiHandlesTune.plotY, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-plotY')))), catch, set(guiHandlesTune.plotY, 'Value', 1), end -try set(guiHandlesTune.RPYcombo, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-SinglePanel')))), catch, set(guiHandlesTune.RPYcombo, 'Value', 0), end -try set(guiHandlesTune.maxYStepInput, 'String', num2str(defaults.Values(find(strcmp(defaults.Parameters, 'StepResp-Ymax'))))), catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-plotR')); if ~isempty(idx_), set(guiHandlesTune.plotR,'Value',defaults.Values(idx_)); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-plotP')); if ~isempty(idx_), set(guiHandlesTune.plotP,'Value',defaults.Values(idx_)); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-plotY')); if ~isempty(idx_), set(guiHandlesTune.plotY,'Value',defaults.Values(idx_)); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-SinglePanel')); if ~isempty(idx_), set(guiHandlesTune.RPYcombo,'Value',defaults.Values(idx_)); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-Ymax')); if ~isempty(idx_), set(guiHandlesTune.maxYStepInput,'String',num2str(defaults.Values(idx_))); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-Subsample')); if ~isempty(idx_), set(guiHandlesTune.subsample,'Value',defaults.Values(idx_)); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-MinRate')); if ~isempty(idx_), set(guiHandlesTune.minRateInput,'String',num2str(defaults.Values(idx_))); end, catch, end +try idx_=find(strcmp(defaults.Parameters,'StepResp-MaxRate')); if ~isempty(idx_), set(guiHandlesTune.maxRateInput,'String',num2str(defaults.Values(idx_))); end, catch, end else warndlg('Please select file(s)'); diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m index 5e512ce..3616a71 100644 --- a/src/util/PSresetData.m +++ b/src/util/PSresetData.m @@ -36,7 +36,7 @@ % clear secondary figure and panel handles clear PSspecfig PSspecfig2 PSspecfig3 PStunefig PSerrfig PSstatsfig PSdisp; clear errCrtlpanel statsCrtlpanel spec2Crtlpanel specCrtlpanel; -clear freqTimeCrtlpanel tuneCrtlpanel; +clear freqTimeCrtlpanel tuneCrtlpanel fcntSR; % reset UI set(guiHandles.FileNum, 'String', ' '); diff --git a/src/util/PSsaveSettings.m b/src/util/PSsaveSettings.m index e6d91ec..185c1fb 100644 --- a/src/util/PSsaveSettings.m +++ b/src/util/PSsaveSettings.m @@ -51,6 +51,9 @@ var(31,:) = [{'StepResp-plotY' 1}]; var(32,:) = [{'StepResp-SinglePanel' 0}]; var(33,:) = [{'StepResp-Ymax' 1.75}]; + var(34,:) = [{'StepResp-Subsample' 1}]; + var(35,:) = [{'StepResp-MinRate' 40}]; + var(36,:) = [{'StepResp-MaxRate' 500}]; defaults = cell2table(var, 'VariableNames',{'Parameters' ; 'Values'}); else @@ -107,6 +110,9 @@ defaults.Values(31) = get(guiHandlesTune.plotY, 'Value'); defaults.Values(32) = get(guiHandlesTune.RPYcombo, 'Value'); defaults.Values(33) = str2double(get(guiHandlesTune.maxYStepInput, 'String')); + defaults.Values(34) = get(guiHandlesTune.subsample, 'Value'); + defaults.Values(35) = str2double(get(guiHandlesTune.minRateInput, 'String')); + defaults.Values(36) = str2double(get(guiHandlesTune.maxRateInput, 'String')); catch end diff --git a/tests/test_PSstepcalc.m b/tests/test_PSstepcalc.m index 3144101..e5f4657 100644 --- a/tests/test_PSstepcalc.m +++ b/tests/test_PSstepcalc.m @@ -52,3 +52,71 @@ %! if ~isempty(stepresponse) %! assert(all(isfinite(stepresponse(:)))); %! end + +%!test +%! % Higher subsampleFactor yields more segments (more overlap) +%! Fs_khz = 4; Fs = Fs_khz * 1000; N = Fs * 10; +%! sp = zeros(N, 1); gy = zeros(N, 1); +%! for k = 1:8 +%! st = round(k * Fs * 1.1 + 100); en = min(st + round(0.3*Fs), N); +%! sp(st:en) = 200; gy(st:en) = 190; +%! end +%! [s_low, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 1, 40, 500); +%! [s_high, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 10, 40, 500); +%! n_low = 0; n_high = 0; +%! if exist('s_low','var') && ~isempty(s_low), n_low = size(s_low, 1); end +%! if exist('s_high','var') && ~isempty(s_high), n_high = size(s_high, 1); end +%! assert(n_high >= n_low, 'high subsample should yield >= segments than low'); + +%!test +%! % minRate filters out low-amplitude segments +%! Fs_khz = 4; Fs = Fs_khz * 1000; N = Fs * 10; +%! sp = zeros(N, 1); gy = zeros(N, 1); +%! % low-amplitude segments (30 deg/s) +%! for k = 1:4 +%! st = round(k * Fs * 1.1 + 100); en = min(st + round(0.3*Fs), N); +%! sp(st:en) = 30; gy(st:en) = 28; +%! end +%! % high-amplitude segments (200 deg/s) +%! for k = 5:8 +%! st = round(k * Fs * 1.1 + 100); en = min(st + round(0.3*Fs), N); +%! sp(st:en) = 200; gy(st:en) = 190; +%! end +%! [s_low, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 5, 10, 500); +%! [s_high, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 5, 100, 500); +%! n_low = 0; n_high = 0; +%! if ~isempty(s_low), n_low = size(s_low, 1); end +%! if ~isempty(s_high), n_high = size(s_high, 1); end +%! assert(n_low >= n_high, 'lower minRate should include more segments'); + +%!test +%! % maxRate excludes extreme maneuvers +%! Fs_khz = 4; Fs = Fs_khz * 1000; N = Fs * 10; +%! sp = zeros(N, 1); gy = zeros(N, 1); +%! % moderate segments (200 deg/s) +%! for k = 1:4 +%! st = round(k * Fs * 1.1 + 100); en = min(st + round(0.3*Fs), N); +%! sp(st:en) = 200; gy(st:en) = 190; +%! end +%! % extreme segments (800 deg/s) +%! for k = 5:8 +%! st = round(k * Fs * 1.1 + 100); en = min(st + round(0.3*Fs), N); +%! sp(st:en) = 800; gy(st:en) = 750; +%! end +%! [s_limited, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 5, 40, 500); +%! [s_all, ~] = PSstepcalc(sp, gy, Fs_khz, 0, 1, 5, 40, Inf); +%! n_limited = 0; n_all = 0; +%! if ~isempty(s_limited), n_limited = size(s_limited, 1); end +%! if ~isempty(s_all), n_all = size(s_all, 1); end +%! assert(n_all >= n_limited, 'unlimited maxRate should include more segments'); + +%!test +%! % Backward compat: old 5-arg call still works +%! Fs_khz = 4; Fs = Fs_khz * 1000; N = Fs * 5; +%! sp = zeros(N, 1); gy = zeros(N, 1); +%! for k = 1:3 +%! st = round(k * Fs + 100); en = min(st + round(0.4*Fs), N); +%! sp(st:en) = 300; gy(st:en) = 285; +%! end +%! [stepresponse, t] = PSstepcalc(sp, gy, Fs_khz, 0, 1); +%! assert(~isempty(t), 'old 5-arg signature must still work'); From 279a268147f7dd37c0cbd7afcec27f13af01b82d Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 2 Apr 2026 23:22:53 +0200 Subject: [PATCH 54/80] feat(spectral): pre/post filter PSD overlay polish --- src/plot/PSplotSpec2D.m | 26 ++++++++++++++++++++++++-- src/ui/PSspec2DUIcontrol.m | 4 +++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index e58fd48..b336af4 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -290,7 +290,9 @@ h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on lsty = multilineStyle{k}; if isTS, lsty = '-'; end - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',lsty) + lw_ = get(guiHandles.linewidth, 'Value')/2; + if k > 1, lw_ = lw_ * 0.6; end + set(h, 'linewidth', lw_,'linestyle',lsty) set(h2,'fontsize',fontsz) set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; @@ -333,7 +335,9 @@ h=plot(freq2d2{p}.(ff), smooth(amp2d2{p}.(ff), log10(size(amp2d2{p}.(ff),1)) * (tmpSmoothVal^3), 'lowess')); hold on hold on lsty = multilineStyle{k}; if isTS, lsty = '-'; end - set(h, 'linewidth', get(guiHandles.linewidth, 'Value')/2,'linestyle',lsty) + lw_ = get(guiHandles.linewidth, 'Value')/2; + if k > 1, lw_ = lw_ * 0.6; end + set(h, 'linewidth', lw_,'linestyle',lsty) set(h2,'fontsize',fontsz) set(h,'Color',[lineCol]) m = (A_lograte(tmpFileVal(f)) * 1000) / 2; @@ -475,6 +479,24 @@ try PSstyleLegend(h, th); catch, end end catch, end +% warn if "Gyro prefilt" selected but no data available +if any(tmpSpecVal == 2) + hasPF_ = false; + for fi_ = 1:size(tmpFileVal,2) + if isfield(T{tmpFileVal(fi_)}, 'gyroPrefilt_0_'), hasPF_ = true; break; end + end + if ~hasPF_ + delete(findobj(PSspecfig2, 'Tag', 'prefiltWarn')); + axW_ = findobj(PSspecfig2, 'Type', 'axes', 'Tag', 'PSspec2_1'); + if ~isempty(axW_) + text(axW_, 0.5, 0.5, {'No pre-filter gyro data.', 'Requires gyroUnfilt (BF 4.5+) or debug\_mode = GYRO\_SCALED.'}, ... + 'Units', 'normalized', 'HorizontalAlignment', 'center', 'FontSize', fontsz+1, ... + 'Color', [.8 .3 .3], 'Tag', 'prefiltWarn'); + end + else + delete(findobj(PSspecfig2, 'Tag', 'prefiltWarn')); + end +end prevLeftKey_ = leftKey_; else % right-column only: clear just axes 4-6 diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 5020697..9d2b195 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -13,7 +13,9 @@ TooltipString_specRun=['Run current spectral configuration']; TooltipString_cmap=['Choose from a selection of colormaps']; TooltipString_smooth=['Choose amount of smoothing']; -TooltipString_user=['Choose the variable you wish to plot']; +TooltipString_user=['Choose signal(s) to plot. Select up to 2.' ... + newline, 'Gyro prefilt = pre-filter gyro (dotted line).' ... + newline, 'BF 4.5+: uses gyroUnfilt (logged by default). Older BF: requires debug_mode = GYRO_SCALED.']; TooltipString_sub100=['Zoom data to show sub 100Hz details',... newline, 'Typically used to see propwash or mid-throttle vibration in e.g. Gyro/Pterm/PIDerror']; From f60eb96db9aaf38b7471c744bcdeb1b5b77f9c1e Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 4 Apr 2026 14:40:57 +0200 Subject: [PATCH 55/80] feat(spectral): TPA diagnostic annotation on Freq x Throttle spectrograms --- src/core/PStpaDetect.m | 53 +++++++++++++++++++++++++++++++++++++++ src/plot/PSplotSpec.m | 56 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/core/PStpaDetect.m diff --git a/src/core/PStpaDetect.m b/src/core/PStpaDetect.m new file mode 100644 index 0000000..b7c52d2 --- /dev/null +++ b/src/core/PStpaDetect.m @@ -0,0 +1,53 @@ +function [tpaFlag, onsetPct, ratioDB] = PStpaDetect(ampMat, freqRow, psd) +%% Detect throttle-correlated noise escalation in 30-80 Hz band +% ampMat - 100xM throttle-binned spectrum (from PSthrSpec) +% freqRow - 1xM frequency vector (Hz) +% psd - true if ampMat values are in dB + + tpaFlag = false; onsetPct = 0; ratioDB = 0; + + if isempty(ampMat) || size(ampMat,2) < 2, return; end + if isempty(freqRow), return; end + + % diagnostic band: 30-80 Hz (D-term 60-80, P-term 30-50) + fIdx = find(freqRow >= 30 & freqRow <= 80); + if isempty(fIdx), return; end + + % throttle regions + loThr = 10:30; % hover + hiThr = 50:90; % cruise to punch-out + loThr = loThr(loThr <= size(ampMat,1)); + hiThr = hiThr(hiThr <= size(ampMat,1)); + if isempty(loThr) || isempty(hiThr), return; end + + bandLo = ampMat(loThr, fIdx); + bandHi = ampMat(hiThr, fIdx); + + Elo = nanmean(bandLo(:)); + Ehi = nanmean(bandHi(:)); + + if psd + ratioDB = Ehi - Elo; + else + if Elo <= 0, return; end + ratioDB = 20 * log10(Ehi / Elo); + end + + tpaFlag = ratioDB > 6; + + if ~tpaFlag, return; end + + % onset: per-bin energy, find first bin exceeding baseline + if psd + thresh = Elo + 3; + else + thresh = Elo * 1.5; + end + for t = 1:size(ampMat,1) + Et = nanmean(ampMat(t, fIdx)); + if Et > thresh + onsetPct = t; + break; + end + end +end diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 71a5e7b..2f81725 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -198,7 +198,33 @@ set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); end h=text(xticks(1)+1,(size(ampmat{p},2)-30)+1,axLabel{c2(p)}); - set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + % TPA diagnostic annotation + if (c2(p) == 1 || c2(p) == 2) && ~isempty(smat{p}) && any(strcmp(smat{p}, {'gyroADC','gyroPrefilt','piderr'})) + try + freqAx_ = []; + for rr_ = 1:size(freq{p},1) + if any(freq{p}(rr_,:) > 0), freqAx_ = freq{p}(rr_,:); break; end + end + if ~isempty(freqAx_) + [tpaF_, tpaOn_, tpaR_] = PStpaDetect(ampmat{p}, freqAx_, tmpPSDVal); + if tpaF_ + tpaRate_ = 0; + tmpFK_ = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); + try idx_ = find(strcmp(SetupInfo{tmpFK_}(:,1), 'tpa_rate')); + if ~isempty(idx_), tpaRate_ = str2double(SetupInfo{tmpFK_}(idx_(1),2)); end + catch, end + if tpaRate_ == 0 + tpaStr_ = sprintf('TPA: +%ddB >%d%%thr', round(tpaR_), tpaOn_); + else + tpaStr_ = sprintf('TPA active, +%ddB', round(tpaR_)); + end + h = text(36, (size(ampmat{p},2)-30)+5, tpaStr_); + set(h, 'Color', [1 .85 0], 'fontsize', fontsz, 'fontweight', 'bold', 'Tag', 'tpaDiag'); + end + end + catch, end + end else % full scaling xticks=round([1 size(ampmat{p},1)/5:size(ampmat{p},1)/5:size(ampmat{p},1)]); yticks=round([1:(size(ampmat{p},2))/10:size(ampmat{p},2) size(ampmat{p},2)]); @@ -222,7 +248,33 @@ set(h,'Color','w','fontsize',fontsz,'fontweight','bold'); end h=text(xticks(1)+1,size(ampmat{p},2)*.04,axLabel{c2(p)}); - set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + set(h,'Color',[1 1 1],'fontsize',fontsz,'fontweight','bold') + % TPA diagnostic annotation (full scale) + if (c2(p) == 1 || c2(p) == 2) && ~isempty(smat{p}) && any(strcmp(smat{p}, {'gyroADC','gyroPrefilt','piderr'})) + try + freqAx_ = []; + for rr_ = 1:size(freq{p},1) + if any(freq{p}(rr_,:) > 0), freqAx_ = freq{p}(rr_,:); break; end + end + if ~isempty(freqAx_) + [tpaF_, tpaOn_, tpaR_] = PStpaDetect(ampmat{p}, freqAx_, tmpPSDVal); + if tpaF_ + tpaRate_ = 0; + tmpFK_ = get(guiHandlesSpec.FileSelect{c1(p)}, 'Value'); + try idx_ = find(strcmp(SetupInfo{tmpFK_}(:,1), 'tpa_rate')); + if ~isempty(idx_), tpaRate_ = str2double(SetupInfo{tmpFK_}(idx_(1),2)); end + catch, end + if tpaRate_ == 0 + tpaStr_ = sprintf('TPA: +%ddB >%d%%thr', round(tpaR_), tpaOn_); + else + tpaStr_ = sprintf('TPA active, +%ddB', round(tpaR_)); + end + h = text(36, size(ampmat{p},2)*.22, tpaStr_); + set(h, 'Color', [1 .85 0], 'fontsize', fontsz, 'fontweight', 'bold', 'Tag', 'tpaDiag'); + end + end + catch, end + end end From 223fa5bfdf065b436070b60c663fd0de3c3d65ef Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 4 Apr 2026 21:01:00 +0200 Subject: [PATCH 56/80] fix(compat): old BF logs crash on missing gyroADC fields --- src/core/PSload.m | 11 +++++++++++ src/plot/PSplotLogViewer.m | 20 ++++++++++---------- src/util/PSsmoothLV.m | 4 +++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index 304d611..3392b98 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -264,6 +264,17 @@ end end + % gyroUnfilt -> gyroADC fallback (old BF logs lack gyroADC) + for ax = 0:2 + adc = sprintf('gyroADC_%d_', ax); + uf = sprintf('gyroUnfilt_%d_', ax); + if ~isfield(T{fcnt}, adc) && isfield(T{fcnt}, uf) + T{fcnt}.(adc) = T{fcnt}.(uf); + elseif ~isfield(T{fcnt}, adc) + T{fcnt}.(adc) = zeros(length(T{fcnt}.loopIteration), 1); + end + end + isArduPilot = strcmpi(sfext, '.bin'); Nsamples = length(T{fcnt}.loopIteration); diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index a804a31..fb5be05 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -217,16 +217,16 @@ lwVal = get(guiHandles.linewidth, 'Value')/2; tSec = tta{fileIdx}/us2sec; - if get(guiHandles.checkbox0, 'Value'), hch1=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['debug_' int2str(ii-1) '_'], sFactor));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox1, 'Value'), hch2=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['gyroADC_' int2str(ii-1) '_'], sFactor));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox2, 'Value'), hch3=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisP_' int2str(ii-1) '_'], sFactor));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox3, 'Value'), hch4=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisI_' int2str(ii-1) '_'], sFactor));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox4, 'Value') && ii<3, hch5=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisDpf_' int2str(ii-1) '_'], sFactor));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox5, 'Value') && ii<3, hch6=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisD_' int2str(ii-1) '_'], sFactor));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox6, 'Value'), hch7=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisF_' int2str(ii-1) '_'], sFactor));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox7, 'Value'), hch8=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['setpoint_' int2str(ii-1) '_'], sFactor));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox8, 'Value'), hch9=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['pidsum_' int2str(ii-1) '_'], sFactor));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end - if get(guiHandles.checkbox9, 'Value'), hch10=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['piderr_' int2str(ii-1) '_'], sFactor));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), end + if get(guiHandles.checkbox0, 'Value'), try hch1=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['debug_' int2str(ii-1) '_'], sFactor));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox1, 'Value'), try hch2=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['gyroADC_' int2str(ii-1) '_'], sFactor));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox2, 'Value'), try hch3=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisP_' int2str(ii-1) '_'], sFactor));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox3, 'Value'), try hch4=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisI_' int2str(ii-1) '_'], sFactor));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox4, 'Value') && ii<3, try hch5=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisDpf_' int2str(ii-1) '_'], sFactor));hold on;set(hch5,'color', [linec.col4],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox5, 'Value') && ii<3, try hch6=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisD_' int2str(ii-1) '_'], sFactor));hold on;set(hch6,'color', [linec.col5],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox6, 'Value'), try hch7=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisF_' int2str(ii-1) '_'], sFactor));hold on;set(hch7,'color', [linec.col6],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox7, 'Value'), try hch8=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['setpoint_' int2str(ii-1) '_'], sFactor));hold on;set(hch8,'color', [linec.col7],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox8, 'Value'), try hch9=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['pidsum_' int2str(ii-1) '_'], sFactor));hold on;set(hch9,'color', [linec.col8],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + if get(guiHandles.checkbox9, 'Value'), try hch10=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['piderr_' int2str(ii-1) '_'], sFactor));hold on;set(hch10,'color', [linec.col9],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end if get(guiHandles.checkboxTS, 'Value') && isfield(T{fileIdx}, ['testSignal_' int2str(ii-1) '_']), try hchTS=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['testSignal_' int2str(ii-1) '_'], sFactor));hold on;set(hchTS,'color', th.sigTestSignal,'LineWidth',lwVal,'linestyle','--'), catch, end, end diff --git a/src/util/PSsmoothLV.m b/src/util/PSsmoothLV.m index 73a7b25..101fb43 100644 --- a/src/util/PSsmoothLV.m +++ b/src/util/PSsmoothLV.m @@ -9,11 +9,13 @@ sc = struct('fIdx', fileIdx); end +if ~isfield(Tfile, fieldName), y = []; return; end + cacheKey = [fieldName '_s' int2str(sFactor)]; if isfield(sc, cacheKey) y = sc.(cacheKey); if numel(y) == numel(Tfile.(fieldName)), return; end - sc = struct('fIdx', fileIdx); % stale cache — different data length + sc = struct('fIdx', fileIdx); % stale cache - different data length end raw = Tfile.(fieldName); From 59c9504111cdd5cda137e3a60204779f10f06c6f Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 4 Apr 2026 22:13:34 +0200 Subject: [PATCH 57/80] fix(spectral): delete unchecked RPY panels instead of cla-only --- src/plot/PSplotSpec2D.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index b336af4..3273a87 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -256,8 +256,16 @@ if skipLeftRender_ && isempty(findobj(PSspecfig2, 'Type', 'axes', 'Tag', 'PSspec2_1')), skipLeftRender_ = false; end if ~skipLeftRender_ -% cla instead of delete to avoid Qt rendering artifacts (stale white pixels) -for di_=1:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end +% cla active panels, delete unchecked ones so they don't linger at old positions +for di_=1:3 + for si_=[di_ di_+3] + h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',si_)); + if ~isempty(h_cla) + if any(axesOptionsSpec == di_), cla(h_cla); hold(h_cla,'off'); + else delete(h_cla); end + end + end +end h_del=findobj(PSspecfig2,'Type','axes','Tag','PSspec2_combo'); if ~isempty(h_del), delete(h_del); end h_del=findobj(PSspecfig2,'Type','axes','Tag','legend'); if ~isempty(h_del), delete(h_del); end %%%%% plot 2d amp spec From a88a11f5d01f4d643050321478505a7c989f6ec1 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 5 Apr 2026 09:45:59 +0200 Subject: [PATCH 58/80] feat(ui): add Gyro(pf) checkbox to Log Viewer --- PIDscope.m | 1 + src/plot/PSplotLogViewer.m | 9 +++++++++ src/ui/PSviewerUIcontrol.m | 12 ++++++++---- src/util/PStheme.m | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index 9ce53e8..03d6b29 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -236,6 +236,7 @@ for k=0:15 linec.(['col' int2str(k)]) = ColorSet(k+1,:); end +linec.colGyroPF = th.sigGyroPrefilt; %%% tooltips TooltipString_files=['Select the .BBL or .BFL file you wish to analyze. ']; diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index fb5be05..ef896f3 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -29,6 +29,7 @@ if plotall_flag>=0 allVal = get(guiHandles.checkbox15, 'Value'); set(guiHandles.checkbox0, 'Value', allVal); + try set(guiHandles.checkboxGyroPF, 'Value', allVal); catch, end set(guiHandles.checkbox1, 'Value', allVal); set(guiHandles.checkbox2, 'Value', allVal); set(guiHandles.checkbox3, 'Value', allVal); @@ -74,6 +75,13 @@ catch, end end + % enable/disable Gyro(pf) checkbox based on gyroPrefilt data + hasPF_ = exist('T','var') && iscell(T) && numel(T) >= fileIdx && isfield(T{fileIdx}, 'gyroPrefilt_0_'); + pfEn_ = 'off'; if hasPF_, pfEn_ = 'on'; end + try set(guiHandles.checkboxGyroPF, 'Enable', pfEn_); + if ~hasPF_, set(guiHandles.checkboxGyroPF, 'Value', 0); end + catch, end + % Update Debug checkbox label for RC_INTERPOLATION mode (version-aware) tmpRCidx = RC_INTERPOLATION; % global default if exist('debugIdx','var') && numel(debugIdx) >= fileIdx @@ -218,6 +226,7 @@ tSec = tta{fileIdx}/us2sec; if get(guiHandles.checkbox0, 'Value'), try hch1=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['debug_' int2str(ii-1) '_'], sFactor));hold on;set(hch1,'color', [linec.col0],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end + try if get(guiHandles.checkboxGyroPF, 'Value'), try hchPF=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['gyroPrefilt_' int2str(ii-1) '_'], sFactor));hold on;set(hchPF,'color', [linec.colGyroPF],'LineWidth',lwVal,'linestyle','--'), catch, end, end, catch, end if get(guiHandles.checkbox1, 'Value'), try hch2=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['gyroADC_' int2str(ii-1) '_'], sFactor));hold on;set(hch2,'color', [linec.col1],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end if get(guiHandles.checkbox2, 'Value'), try hch3=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisP_' int2str(ii-1) '_'], sFactor));hold on;set(hch3,'color', [linec.col2],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end if get(guiHandles.checkbox3, 'Value'), try hch4=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, ['axisI_' int2str(ii-1) '_'], sFactor));hold on;set(hch4,'color', [linec.col3],'LineWidth',lwVal,'linestyle',[lnstyle{cntLV}]), catch, end, end diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index fd790ff..8384672 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -17,7 +17,7 @@ tbOff = 40/figH; chkRow1 = 1 - tbOff; chkRow2 = chkRow1 - rs; chkX = 0.10; -posInfo.checkbox0=[chkX chkRow1 chkW chkH]; +posInfo.checkboxGyroPF=[chkX chkRow1 chkW chkH]; posInfo.checkbox1=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; posInfo.checkbox2=[chkX chkRow1 chkW chkH]; posInfo.checkbox3=[chkX chkRow2 chkW chkH]; chkX=chkX+chkW; @@ -33,7 +33,8 @@ posInfo.checkbox10=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; posInfo.checkbox14=[chkX chkRow1 chkMotW chkH]; posInfo.checkboxTS=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; -posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; chkX=chkX+chkMotW; +posInfo.checkbox15=[chkX chkRow1 chkMotW chkH]; +posInfo.checkbox0=[chkX chkRow2 chkMotW chkH]; chkX=chkX+chkMotW; chkRpmW_px = 80; chkRpmW = chkRpmW_px/figW; posInfo.checkboxRPM4=[chkX chkRow1 chkRpmW chkH]; posInfo.checkboxRPM3=[chkX chkRow2 chkRpmW chkH]; chkX=chkX+chkRpmW; @@ -78,6 +79,8 @@ 'HighlightColor',panelBorder,... 'Position',[0.096 chkRow2-cpMv chkPanelW chkRow1+rh+cpMv-chkRow2+cpMv]); +guiHandles.checkboxGyroPF=uicontrol(PSfig,'Style','checkbox','String','Gyro(pf)','fontsize',fontsz,'ForegroundColor',[linec.colGyroPF],'BackgroundColor',bgcolor,... + 'units','normalized','Position',[posInfo.checkboxGyroPF],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.checkbox0=uicontrol(PSfig,'Style','checkbox','String','Debug','fontsize',fontsz,'ForegroundColor',[linec.col0],'BackgroundColor',bgcolor,... 'units','normalized','Position',[posInfo.checkbox0],'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); guiHandles.checkbox1=uicontrol(PSfig,'Style','checkbox','String','Gyro','fontsize',fontsz,'ForegroundColor',[linec.col1],'BackgroundColor',bgcolor,... @@ -159,7 +162,7 @@ % Register checkbox bar for pixel-based resize chkBarItems = {}; -chkBarItems{end+1} = struct('h', guiHandles.checkbox0, 'wpx', chkW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkboxGyroPF, 'wpx', chkW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkbox1, 'wpx', chkW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkbox2, 'wpx', chkW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkbox3, 'wpx', chkW_px, 'row', 2, 'advance', true); @@ -175,7 +178,8 @@ chkBarItems{end+1} = struct('h', guiHandles.checkbox10, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkbox14, 'wpx', chkMotW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkboxTS, 'wpx', chkMotW_px, 'row', 2, 'advance', true); -chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', true); +chkBarItems{end+1} = struct('h', guiHandles.checkbox15, 'wpx', chkMotW_px, 'row', 1, 'advance', false); +chkBarItems{end+1} = struct('h', guiHandles.checkbox0, 'wpx', chkMotW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM4, 'wpx', chkRpmW_px, 'row', 1, 'advance', false); chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM3, 'wpx', chkRpmW_px, 'row', 2, 'advance', true); chkBarItems{end+1} = struct('h', guiHandles.checkboxRPM2, 'wpx', chkRpmW_px, 'row', 1, 'advance', false); diff --git a/src/util/PStheme.m b/src/util/PStheme.m index a1605a4..0c84d42 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -97,6 +97,7 @@ % signal colors (bright for dark bg) th.sigDebug = [.50 .50 .50]; th.sigGyro = [.85 .85 .85]; +th.sigGyroPrefilt = [.65 .65 .65]; th.sigPterm = [.20 .85 .20]; th.sigIterm = [.90 .75 .20]; th.sigDprefilt = [.45 .80 .95]; From 66a25d2af03abba137507f81775fa91555b13c18 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 6 Apr 2026 10:45:49 +0200 Subject: [PATCH 59/80] fix(spectral): Sub 100Hz column vanishes on re-render --- src/plot/PSplotSpec2D.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index 3273a87..efee3ac 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -507,8 +507,10 @@ end prevLeftKey_ = leftKey_; else - % right-column only: clear just axes 4-6 - for di_=4:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end + % right-column only: clear axes 4-6 when Motor Noise will repopulate + if rightMode_chk_ == 2 + for di_=4:6, h_cla=findobj(PSspecfig2,'Type','axes','Tag',sprintf('PSspec2_%d',di_)); if ~isempty(h_cla), cla(h_cla); hold(h_cla,'off'); end; end + end end % skipLeftRender_ From 6c24cb17912eb57569ce385ba11c609a1ff00e53 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 6 Apr 2026 11:37:41 +0200 Subject: [PATCH 60/80] fix(spectral): skip gyroPrefilt default when data unavailable --- src/plot/PSplotSpec2D.m | 2 ++ src/ui/PSspec2DUIcontrol.m | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index efee3ac..cd7955a 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -504,6 +504,8 @@ else delete(findobj(PSspecfig2, 'Tag', 'prefiltWarn')); end +else + delete(findobj(PSspecfig2, 'Tag', 'prefiltWarn')); end prevLeftKey_ = leftKey_; else diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index 9d2b195..c5a0bb0 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -133,7 +133,8 @@ if isfield(T{1}, 'testSignal_0_'), sA{end+1} = 'Test Signal'; end guiHandlesSpec2.SpecList = uicontrol(PSspecfig2,'Style','listbox','string',[sA],'max',3,'min',1, 'fontsize',fontsz, 'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.TermListWindowSpec], 'callback', 'if length(get(guiHandlesSpec2.SpecList, ''Value'')) > 2, set(guiHandlesSpec2.SpecList, ''Value'', 1); end;'); -set(guiHandlesSpec2.SpecList, 'Value', [1 2]); +specDef_ = [1]; if isfield(T{1}, 'gyroPrefilt_0_'), specDef_ = [1 2]; end +set(guiHandlesSpec2.SpecList, 'Value', specDef_); guiHandlesSpec2.FileSelect = uicontrol(PSspecfig2,'Style','listbox','string',[fnameMaster],'max', 10, 'min', 1, 'fontsize',fontsz,'TooltipString',[TooltipString_user],'units','normalized','Position', [posInfo.fileListWindowSpec], 'callback', 'if length(get(guiHandlesSpec2.FileSelect, ''Value'')) > 10, set(guiHandlesSpec2.FileSelect, ''Value'', 1); end;'); @@ -264,7 +265,11 @@ 'ncols',2, 'rows',rows, 'rowH',0.25, 'margin',0.04)); PSregisterResize(PSspecfig2, cpPx, cpI, 'seq'); -try set(guiHandlesSpec2.SpecList, 'Value', [defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term1'))) defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term2')))]), catch, set(guiHandlesSpec2.SpecList, 'Value', [1 2]), end +try specSaved_ = [defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term1'))) defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-term2')))]; + if ~isfield(T{1}, 'gyroPrefilt_0_'), specSaved_(specSaved_ == 2) = []; end + if isempty(specSaved_), specSaved_ = 1; end + set(guiHandlesSpec2.SpecList, 'Value', specSaved_); +catch, set(guiHandlesSpec2.SpecList, 'Value', specDef_), end try set(guiHandlesSpec2.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-smoothing')))), catch, set(guiHandlesSpec2.smoothFactor_select, 'Value', 3), end try set(guiHandlesSpec2.Delay, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-delay')))), catch, set(guiHandlesSpec2.Delay, 'Value', 1), end try set(guiHandlesSpec2.plotR, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'spec2D-plotR')))), catch, set(guiHandlesSpec2.plotR, 'Value', 1), end From 314c5587b4b805c416fe9244e8e94b8525adef03 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 6 Apr 2026 14:19:32 +0200 Subject: [PATCH 61/80] chore: bump version to 26.04.0-rc1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3768931..cdc31b8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.03.1 +26.04.0-rc1 From 3d51de128c8cc7c495f8b18e767f58fe1e02515c Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Mon, 6 Apr 2026 14:30:38 +0200 Subject: [PATCH 62/80] ci: update macOS runners and all actions to latest --- .github/workflows/build-builders.yml | 24 ++++++++--------- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 40 ++++++++++++++-------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build-builders.yml b/.github/workflows/build-builders.yml index b6f24af..d39f4e8 100644 --- a/.github/workflows/build-builders.yml +++ b/.github/workflows/build-builders.yml @@ -15,17 +15,17 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./packaging/appimage push: true @@ -37,17 +37,17 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./packaging/windows push: true @@ -59,17 +59,17 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./packaging/macos push: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f52b439..288ec1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run unit tests run: octave --no-gui --eval "run('tests/run_tests.m')" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ba9b66..0858099 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run unit tests run: octave --no-gui --eval "run('tests/run_tests.m')" @@ -37,9 +37,9 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -57,9 +57,9 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -75,9 +75,9 @@ jobs: strategy: matrix: include: - - runner: macos-14 + - runner: macos-15 arch_name: arm64 - - runner: macos-13 + - runner: macos-15-intel arch_name: x86_64 runs-on: ${{ matrix.runner }} permissions: @@ -91,7 +91,7 @@ jobs: cp obj/blackbox_decode ../blackbox_decode.${{ matrix.arch_name }} codesign --force --sign - ../blackbox_decode.${{ matrix.arch_name }} - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: blackbox-decode-${{ matrix.arch_name }} path: blackbox_decode.${{ matrix.arch_name }} @@ -104,9 +104,9 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -121,7 +121,7 @@ jobs: -v ${{ github.workspace }}/dist:/dist \ ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-appimage:latest - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: appimage path: dist/PIDscope-*-x86_64.AppImage @@ -133,9 +133,9 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -150,22 +150,22 @@ jobs: -v ${{ github.workspace }}/dist:/dist \ ${{ env.REGISTRY }}/${{ github.repository_owner }}/pidscope-builder-windows:latest - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: windows-zip path: dist/PIDscope-*-windows-x86_64.zip build-macos: needs: build-macos-blackbox - runs-on: macos-14 + runs-on: macos-15 permissions: contents: read env: INAV_BB_VERSION: "9.0.0" steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: blackbox-decode-* merge-multiple: true @@ -207,7 +207,7 @@ jobs: mkdir -p dist cd /tmp && zip -r -q "$GITHUB_WORKSPACE/dist/PIDscope-${VERSION}-macos-universal.zip" "PIDscope-${VERSION}-macos" - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: macos-zip path: dist/PIDscope-*-macos-universal.zip @@ -220,12 +220,12 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: artifacts - name: Create GitHub Release - uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: files: | artifacts/appimage/* From 95d539b405ba279627f72c4f193c182430c957f8 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 19 Apr 2026 09:44:07 +0200 Subject: [PATCH 63/80] fix(parser): guard INAV rcData_3_ and setpoint_3_ plot --- src/core/PSload.m | 6 +++++- src/plot/PSplotLogViewer.m | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/PSload.m b/src/core/PSload.m index 3392b98..c93cfd6 100644 --- a/src/core/PSload.m +++ b/src/core/PSload.m @@ -245,7 +245,11 @@ T{fcnt}.setpoint_0_ = T{fcnt}.axisRate_0_; T{fcnt}.setpoint_1_ = T{fcnt}.axisRate_1_; T{fcnt}.setpoint_2_ = T{fcnt}.axisRate_2_; - T{fcnt}.setpoint_3_ = (T{fcnt}.rcData_3_ - 1000); + if isfield(T{fcnt}, 'rcData_3_') + T{fcnt}.setpoint_3_ = (T{fcnt}.rcData_3_ - 1000); + else + T{fcnt}.setpoint_3_ = zeros(length(T{fcnt}.loopIteration), 1); + end end if isRF % setpoint_3_ is collective, use rcCommand[4] as throttle if isfield(T{fcnt}, 'rcCommand_4_') diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index ef896f3..ef269ff 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -280,7 +280,7 @@ if get(guiHandles.checkbox12, 'Value'), try hch17=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_6_', sFactor));hold on;set(hch17,'color', [linec.col12],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end if get(guiHandles.checkbox13, 'Value'), try hch18=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'motor_7_', sFactor));hold on;set(hch18,'color', [linec.col13],'LineWidth',lwVal, 'LineStyle', '--'), catch, end, end - if get(guiHandles.checkbox14, 'Value'), hch19=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'setpoint_3_', sFactor, 0.1));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), end + if get(guiHandles.checkbox14, 'Value'), try hch19=plot(tSec, PSsmoothLV(PSfig, T{fileIdx}, fileIdx, 'setpoint_3_', sFactor, 0.1));hold on;set(hch19,'color', [linec.col14],'LineWidth',lwVal), catch, end, end axis([0 xmax 0 100]) set(gca,'Color',th.axesBg); From 0e1f9497f0cae9dda6d52e94e4c13fe0727a4586 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sun, 19 Apr 2026 10:01:17 +0200 Subject: [PATCH 64/80] fix(parser): QS JSON path doubling + AP older log compat --- src/core/PSarduConvert.m | 10 ++++++++++ src/core/PSarduRead.m | 21 +++++++++++++++------ src/core/PSgetcsv.m | 3 ++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/core/PSarduConvert.m b/src/core/PSarduConvert.m index 13ee566..2bb56aa 100644 --- a/src/core/PSarduConvert.m +++ b/src/core/PSarduConvert.m @@ -8,6 +8,16 @@ error('No RATE messages in log - enable LOG_BITMASK bit 1 (ATTITUDE_FAST)'); end +% Older AP firmware (pre-2017) used TimeMS instead of TimeUS +msgNames = fieldnames(data); +for mi = 1:numel(msgNames) + m = data.(msgNames{mi}); + if isstruct(m) && ~isfield(m, 'TimeUS') && isfield(m, 'TimeMS') + m.TimeUS = double(m.TimeMS) * 1000; + data.(msgNames{mi}) = m; + end +end + rate = data.RATE; N = length(rate.TimeUS); diff --git a/src/core/PSarduRead.m b/src/core/PSarduRead.m index 10cfa9b..ccf6ba1 100644 --- a/src/core/PSarduRead.m +++ b/src/core/PSarduRead.m @@ -109,13 +109,22 @@ % nested function to parse PARM (has string fields, can't fully vectorize) function parse_parms(payloads, nMsg, fstr, ~) layout = field_layout(fstr); + nameIdx = 0; valIdx = 0; + for li = 1:length(layout) + t = layout(li).type; + if nameIdx == 0 && any(t == 'NZna'), nameIdx = li; end + if valIdx == 0 && any(t == 'fe'), valIdx = li; end + end + if nameIdx == 0 || valIdx == 0, return; end for m = 1:nMsg - row = payloads(m, :); - pname = deblank(char(row(layout(2).off : layout(2).off + layout(2).sz - 1))); - pname(pname == 0) = []; - pname = regexprep(pname, '[^a-zA-Z0-9_]', '_'); - if isempty(pname), continue; end - vbytes = row(layout(3).off : layout(3).off + 3); + rawBytes = payloads(m, layout(nameIdx).off : layout(nameIdx).off + layout(nameIdx).sz - 1); + rawBytes = rawBytes(rawBytes ~= 0 & rawBytes < 128); + pname = deblank(char(rawBytes)); + pname(~((pname>='a'&pname<='z')|(pname>='A'&pname<='Z')|(pname>='0'&pname<='9')|pname=='_')) = '_'; + if isempty(pname) || ~(isletter(pname(1)) || pname(1) == '_') + continue; + end + vbytes = payloads(m, layout(valIdx).off : layout(valIdx).off + 3); parms.(pname) = double(typecast(uint8(vbytes), 'single')); end end diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index 172a06a..cad75e8 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -28,7 +28,8 @@ elseif strcmpi(fext, '.json') [headerFile, csvFile] = PSquicJson2csv(filename, outdir); filename = headerFile; - files(1).name = csvFile; + [~, csvName, csvExt] = fileparts(csvFile); + files(1).name = [csvName csvExt]; fnums = 1; elseif any(strcmpi(fext, {'.BFL', '.BBL', '.TXT', '.BTFL'})) From 92107f16eb496798fac7faaeedbfb6f13b1290c8 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 30 May 2026 07:33:00 +0200 Subject: [PATCH 65/80] chore: bump version to 26.05.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index cdc31b8..9c9684d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.04.0-rc1 +26.05.0 From 0a0d746a3558ea99ec0cf60e1dd709e579360de6 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 5 Jun 2026 21:15:17 +0200 Subject: [PATCH 66/80] fix(step-response): default min rate 20 deg/s for Rotorflight logs --- src/ui/PStuneUIcontrol.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 8f4f43d..63b8b4d 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -28,7 +28,7 @@ newline, 'Warning: Set subsampling dropdown @ or < medium for faster processing.']; TooltipString_minRate=['Input the minimum rate of rotation for calculating the step response (lower bound must be > 0 but lower than upper bound).',... newline, 'Really low values may yield more noisy contributions to the data, whereas higher values limit the total data used.',... - newline, 'The default of 40deg/s should be sufficient in most cases, but if N is low, try setting this to lower']; + newline, 'The default of 40deg/s (20 for Rotorflight) should be sufficient in most cases, but if N is low, try setting this to lower']; TooltipString_maxRate=['Input the maximum rate of rotation for for calculating the step response (upper bound must be greater than lower bound).',... newline, 'This also marks the lower bound for step resp plots associated with the ''snap maneuvers'' selection.',... newline, 'The default of 500deg/s is sufficient in most cases']; @@ -128,7 +128,10 @@ guiHandlesTune.minRateTxt = uicontrol(PStunefig,'style','text','string','deg/s','fontsize',fontsz,... 'TooltipString', [TooltipString_minRate], 'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.minRateTxt]); -guiHandlesTune.minRateInput = uicontrol(PStunefig,'style','edit','string','40','fontsize',fontsz,... +% helis are flown with gentle cyclic rates - 40 deg/s would reject most maneuvers +minRateDef_ = '40'; +try, if any(strcmpi(fwType, 'Rotorflight')), minRateDef_ = '20'; end, catch, end +guiHandlesTune.minRateInput = uicontrol(PStunefig,'style','edit','string',minRateDef_,'fontsize',fontsz,... 'TooltipString', [TooltipString_minRate], 'units','normalized','Position',[posInfo.minRateInput],... 'callback','delete(findobj(PStunefig,''Type'',''axes'')); fcntSR = 0; updateStep = 0; PStuningParams; set(PStunefig, ''pointer'', ''arrow'');'); guiHandlesTune.maxRateInput = uicontrol(PStunefig,'style','edit','string','500','fontsize',fontsz,... From de79206eba5a262fb106dcc7a3780b9182bd8c39 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 5 Jun 2026 21:55:20 +0200 Subject: [PATCH 67/80] fix(ui): lift dark line colors to theme min brightness --- src/util/PSlinecmap.m | 6 ++++++ src/util/PStheme.m | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/util/PSlinecmap.m b/src/util/PSlinecmap.m index 5d711ef..3e8087c 100644 --- a/src/util/PSlinecmap.m +++ b/src/util/PSlinecmap.m @@ -14,6 +14,12 @@ for i = find(multiLineCols(:,1) > .5 & multiLineCols(:,2) > .7 & multiLineCols(:,3) < .3) multiLineCols(i,:) = multiLineCols(i,:) * .78; end + % jet endpoints (dark red/blue) vanish on the dark theme - lift toward + % white instead of rescaling, so neighbors keep distinct hues + th = PStheme(); + mx = max(multiLineCols, [], 2); + dim = mx < th.lineMinBright; + multiLineCols(dim,:) = min(1, multiLineCols(dim,:) + (th.lineMinBright - mx(dim))); multiLineCols = repmat(multiLineCols, round(100/nColors),1);% repeats colormap to be 100 rows long end diff --git a/src/util/PStheme.m b/src/util/PStheme.m index 0c84d42..eee4128 100644 --- a/src/util/PStheme.m +++ b/src/util/PStheme.m @@ -12,6 +12,9 @@ th.axesFg = [.75 .75 .75]; th.gridColor = [.28 .28 .30]; +% per-file line palette: minimum brightness readable on the dark background +th.lineMinBright = .85; + % text th.textPrimary = [.90 .90 .90]; th.textSecondary = [.65 .65 .65]; From 8ebd5afc3b1128d7b4ceea97c4f555d6531c8c0e Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 5 Jun 2026 21:55:20 +0200 Subject: [PATCH 68/80] fix(ui): keep title bar on screen on Windows via OuterPosition --- PIDscope.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PIDscope.m b/PIDscope.m index 03d6b29..5101b74 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -140,8 +140,14 @@ screensz = get(0,'ScreenSize'); % Octave Qt bug: setting figure units to 'normalized' permanently breaks uipanel % Calculate pixel position manually instead -set(PSfig, 'Position', round([0 0 screensz(3) screensz(4)])); -try set(PSfig, 'WindowState', 'maximized'); catch, end +if ispc + % Position sets the client area only - on Windows the title bar ends up + % above the screen top; OuterPosition includes the frame + drawnow; + set(PSfig, 'OuterPosition', round([0 0 screensz(3) screensz(4)])); +else + set(PSfig, 'Position', round([0 0 screensz(3) screensz(4)])); +end set(PSfig, 'NumberTitle', 'off'); set(PSfig, 'Name', ['PIDscope (' PsVersion ') - Log Viewer']); drawnow; pause(0.2); From 78e9ff8e80891685f3b41fffe3deaf8093fd6cb7 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 5 Jun 2026 22:05:53 +0200 Subject: [PATCH 69/80] chore: bump version to 26.06.0-rc1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9c9684d..f747570 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.05.0 +26.06.0-rc1 From 992a12c763d65b3c066a4485d3d42a053a4ba940 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Fri, 5 Jun 2026 23:47:57 +0200 Subject: [PATCH 70/80] docs: mention AUR packages in README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 628f978..64f0cb8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,17 @@ That said - if you find PIDscope useful and want to buy me a coffee (not a beer, Buy Me A Coffee --- +## Installing + +AppImage (Linux), Windows and macOS builds are on the [releases page](https://github.com/dzikus/PIDscope/releases). + +Arch Linux users can install from the AUR thanks to [FPVogel](https://github.com/FPVogel): + +- [pidscope-bin](https://aur.archlinux.org/packages/pidscope-bin) - the released AppImage +- [pidscope-git](https://aur.archlinux.org/packages/pidscope-git) - built from the latest git source + +--- + ## License PIDscope is licensed under the [GNU General Public License v3.0](LICENSE). From 0fe9573f81082f7053a3219c7baa1c0005c17f30 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 13 Jun 2026 16:10:56 +0200 Subject: [PATCH 71/80] fix(step-response): guard step tool styling when no file is loaded --- src/ui/PStuneUIcontrol.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index 63b8b4d..b3406b9 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -224,10 +224,10 @@ try idx_=find(strcmp(defaults.Parameters,'StepResp-MinRate')); if ~isempty(idx_), set(guiHandlesTune.minRateInput,'String',num2str(defaults.Values(idx_))); end, catch, end try idx_=find(strcmp(defaults.Parameters,'StepResp-MaxRate')); if ~isempty(idx_), set(guiHandlesTune.maxRateInput,'String',num2str(defaults.Values(idx_))); end, catch, end +PSstyleControls(PStunefig); else warndlg('Please select file(s)'); end -PSstyleControls(PStunefig); % functions function textinput_call3(src,eventdata) From e0b2809b8d6013dc58e90a05b7deaee813386e11 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 13 Jun 2026 16:15:11 +0200 Subject: [PATCH 72/80] chore: bump version to 26.06.0-rc2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index f747570..331fb09 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.06.0-rc1 +26.06.0-rc2 From 34e3737694509490d36207faab502cfd9292e571 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 13 Jun 2026 16:24:04 +0200 Subject: [PATCH 73/80] chore(build): bump Windows Octave to 11.3.0 --- packaging/windows/Dockerfile | 2 +- packaging/windows/build-windows.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/windows/Dockerfile b/packaging/windows/Dockerfile index bf0d06c..61658e7 100644 --- a/packaging/windows/Dockerfile +++ b/packaging/windows/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive -ARG OCTAVE_VERSION=11.1.0 +ARG OCTAVE_VERSION=11.3.0 RUN apt-get update && apt-get install -y \ p7zip-full zip wget git curl make \ diff --git a/packaging/windows/build-windows.sh b/packaging/windows/build-windows.sh index dbf0e6f..aa60f83 100644 --- a/packaging/windows/build-windows.sh +++ b/packaging/windows/build-windows.sh @@ -7,7 +7,7 @@ set -euo pipefail SRC_DIR="${1:-/src}" DIST_DIR="${2:-/dist}" -OCTAVE_VERSION="${OCTAVE_VERSION:-11.1.0}" +OCTAVE_VERSION="${OCTAVE_VERSION:-11.3.0}" # Version: env > VERSION file > git tag > fallback if [ -n "${PIDSCOPE_VERSION:-}" ]; then VERSION="${PIDSCOPE_VERSION}" From 35186dc0b2b00a8b3e030415e9e34d555e330139 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 16 Jun 2026 17:10:27 +0200 Subject: [PATCH 74/80] fix(ui): prevent crash from clicks during ginput capture --- PIDscope.m | 2 +- src/plot/PSplotLogViewer.m | 4 +-- src/ui/PSdispSetupInfoUIcontrol.m | 2 ++ src/ui/PSerrUIcontrol.m | 2 ++ src/ui/PSspec2DUIcontrol.m | 2 ++ src/ui/PSstatsUIcontrol.m | 2 ++ src/ui/PStuneUIcontrol.m | 2 ++ src/ui/PSviewerUIcontrol.m | 2 +- src/util/PSlogViewerPeriod.m | 4 +++ src/util/PSresetData.m | 2 ++ src/util/PSstepPeriod.m | 4 +++ src/util/PStrimClick.m | 10 ++++++ src/util/PStrimSelect.m | 54 +++++++++++++++++++++++++++++++ src/util/PStrimWaitClick.m | 16 +++++++++ 14 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/util/PStrimClick.m create mode 100644 src/util/PStrimSelect.m create mode 100644 src/util/PStrimWaitClick.m diff --git a/PIDscope.m b/PIDscope.m index 5101b74..2da22e4 100644 --- a/PIDscope.m +++ b/PIDscope.m @@ -270,7 +270,7 @@ set(guiHandles.clr, 'ForegroundColor', cautionCol); guiHandles.startEndButton = uicontrol(PSfig,'style','checkbox', 'string','Trim ','fontsize',fontsz,'TooltipString', [TooltipString_selectButton], 'units','normalized','Position',[posInfo.startEndButton],... - 'callback','if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end'); + 'callback','PStrimSelect;'); guiHandles.plotR =uicontrol(PSfig,'Style','checkbox','String','R','fontsize',fontsz,'TooltipString', ['Plot Roll '],... 'units','normalized','BackgroundColor',bgcolor,'ForegroundColor',th.axisRoll,'Position',[posInfo.plotR_LV], 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), PSplotLogViewer; end'); diff --git a/src/plot/PSplotLogViewer.m b/src/plot/PSplotLogViewer.m index ef269ff..4ed3ab7 100644 --- a/src/plot/PSplotLogViewer.m +++ b/src/plot/PSplotLogViewer.m @@ -414,10 +414,10 @@ % i/o keyboard trim: 'i' sets in-point, 'o' sets out-point set(PSfig, 'KeyPressFcn', [ ... 'if exist(''filenameA'',''var'') && ~isempty(filenameA), ' ... - 'kk=get(gcbo,''CurrentCharacter''); fIdx=get(guiHandles.FileNum,''Value''); ' ... + 'kk=get(gcbo,''CurrentCharacter''); fIdx=get(guiHandles.FileNum,''Value''); setappdata(0,''PSbusy'',1); ' ... 'if kk==''i'', try, [xt,~]=ginput(1); epoch1_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... 'elseif kk==''o'', try, [xt,~]=ginput(1); epoch2_A(fIdx)=round(xt*10)/10; PSplotLogViewer; catch, end; ' ... - 'end, end']); + 'end, setappdata(0,''PSbusy'',0); end']); PSdatatipSetup(PSfig); try PSresizeCP(PSfig, []); catch, end diff --git a/src/ui/PSdispSetupInfoUIcontrol.m b/src/ui/PSdispSetupInfoUIcontrol.m index 6067cc4..fa55c01 100644 --- a/src/ui/PSdispSetupInfoUIcontrol.m +++ b/src/ui/PSdispSetupInfoUIcontrol.m @@ -8,6 +8,8 @@ % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + if exist('fnameMaster','var') && ~isempty(fnameMaster) if exist('PSdisp','var') && ishandle(PSdisp) diff --git a/src/ui/PSerrUIcontrol.m b/src/ui/PSerrUIcontrol.m index 3a1db07..2401224 100644 --- a/src/ui/PSerrUIcontrol.m +++ b/src/ui/PSerrUIcontrol.m @@ -8,6 +8,8 @@ % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + if exist('fnameMaster','var') && ~isempty(fnameMaster) if exist('PSerrfig','var') && ishandle(PSerrfig) diff --git a/src/ui/PSspec2DUIcontrol.m b/src/ui/PSspec2DUIcontrol.m index c5a0bb0..224b0f7 100644 --- a/src/ui/PSspec2DUIcontrol.m +++ b/src/ui/PSspec2DUIcontrol.m @@ -7,6 +7,8 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + if exist('fnameMaster','var') && ~isempty(fnameMaster) %%% tooltips diff --git a/src/ui/PSstatsUIcontrol.m b/src/ui/PSstatsUIcontrol.m index c0262ed..132dbae 100644 --- a/src/ui/PSstatsUIcontrol.m +++ b/src/ui/PSstatsUIcontrol.m @@ -7,6 +7,8 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + if exist('fnameMaster','var') && ~isempty(fnameMaster) if exist('PSstatsfig','var') && ishandle(PSstatsfig) diff --git a/src/ui/PStuneUIcontrol.m b/src/ui/PStuneUIcontrol.m index b3406b9..155da4f 100644 --- a/src/ui/PStuneUIcontrol.m +++ b/src/ui/PStuneUIcontrol.m @@ -7,6 +7,8 @@ % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + if exist('fnameMaster','var') && ~isempty(fnameMaster) th = PStheme(); diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 8384672..2b3ae19 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -150,7 +150,7 @@ TooltipString_FileNum=['Select the file you wish to plot in the logviewer. ']; set(guiHandles.FileNum, 'string', fnameMaster, 'TooltipString', TooltipString_FileNum,... - 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; if exist(''filenameA'',''var'') && ~isempty(filenameA) && get(guiHandles.startEndButton, ''Value''), try, [x y] = ginput(1); epoch1_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; [x y] = ginput(1); epoch2_A(get(guiHandles.FileNum, ''Value'')) = round(x(1)*10)/10; PSplotLogViewer; catch, end, end, end'); + 'callback','if exist(''fnameMaster'',''var'') && ~isempty(fnameMaster), try set(zoom, ''Enable'',''off''); catch, end, expandON=0; PSplotLogViewer; PStrimSelect; end'); maxY_textToolTip = ['+/- Scaling factor for the Y axis in degs/s']; guiHandles.maxY_text = uicontrol(PSfig,'style','text','string','y scale','fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','BackgroundColor',bgcolor,'Position',[posInfo.maxYtext]); guiHandles.maxY_input = uicontrol(PSfig,'style','edit','string',int2str(maxY),'fontsize',fontsz,'TooltipString', [maxY_textToolTip],'units','normalized','Position',[posInfo.maxYinput],... diff --git a/src/util/PSlogViewerPeriod.m b/src/util/PSlogViewerPeriod.m index 423800e..7f43801 100644 --- a/src/util/PSlogViewerPeriod.m +++ b/src/util/PSlogViewerPeriod.m @@ -19,6 +19,10 @@ function PSlogViewerPeriod(fig) set(fig, 'pointer', 'crosshair'); +% PSbusy lets re-entrant callbacks bail during the ginput capture (#21) +setappdata(0, 'PSbusy', 1); +restoreBusy_ = onCleanup(@() setappdata(0, 'PSbusy', 0)); + % first click try ginput(1); catch, set(fig,'pointer','arrow'); return; end figPt = get(fig, 'CurrentPoint'); diff --git a/src/util/PSresetData.m b/src/util/PSresetData.m index 3616a71..cd9ee8d 100644 --- a/src/util/PSresetData.m +++ b/src/util/PSresetData.m @@ -1,6 +1,8 @@ %% PSresetData - clear all loaded data and reset UI state % Called from Reset button and firmware-change dialog +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call mid-capture (#21) + clear T dataA tta A_lograte epoch1_A epoch2_A SetupInfo; clear rollPIDF pitchPIDF yawPIDF filenameA fnameMaster loaded_firmware; clear debugmode debugIdx fwType fwMajor fwMinor gyro_debug_axis; diff --git a/src/util/PSstepPeriod.m b/src/util/PSstepPeriod.m index ddec44d..78bb7be 100644 --- a/src/util/PSstepPeriod.m +++ b/src/util/PSstepPeriod.m @@ -9,6 +9,10 @@ function PSstepPeriod(fig) allAx = findobj(fig, 'Type', 'axes', 'Visible', 'on'); if isempty(allAx), return; end +% PSbusy lets re-entrant callbacks bail during the ginput capture (#21) +setappdata(0, 'PSbusy', 1); +restoreBusy_ = onCleanup(@() setappdata(0, 'PSbusy', 0)); + % first click - determines target axes try ginput(1); catch, return; end figPt = get(fig, 'CurrentPoint'); diff --git a/src/util/PStrimClick.m b/src/util/PStrimClick.m new file mode 100644 index 0000000..98fe48a --- /dev/null +++ b/src/util/PStrimClick.m @@ -0,0 +1,10 @@ +function PStrimClick(ax) +% Records the x-position of a left-click on a log-viewer axes. +% Armed by PStrimWaitClick only while a trim capture is running (#21). + + if ~strcmp(get(ancestor(ax, 'figure'), 'SelectionType'), 'normal') + return + end + cp = get(ax, 'CurrentPoint'); + setappdata(0, 'PStrimX', cp(1,1)); +end diff --git a/src/util/PStrimSelect.m b/src/util/PStrimSelect.m new file mode 100644 index 0000000..7f2a6a2 --- /dev/null +++ b/src/util/PStrimSelect.m @@ -0,0 +1,54 @@ +%% PStrimSelect - set the trim window for the current file by clicking two points +% Other controls grey out during capture so a stray click can't re-enter a callback +% mid-ginput (#21); the Trim box stays live, so clicking it again cancels the phase. + +if getappdata(0, 'PStrimming'), return; end % ignore re-entry while already capturing + +if ~(exist('filenameA','var') && ~isempty(filenameA) && get(guiHandles.startEndButton,'Value')) + return; +end + +fnum_ = get(guiHandles.FileNum, 'Value'); + +ctrls_ = findobj(PSfig, 'Type', 'uicontrol'); +ctrls_ = ctrls_(ctrls_ ~= guiHandles.startEndButton); +styles_ = get(ctrls_, 'Style'); +if ~iscell(styles_), styles_ = {styles_}; end +ctrls_ = ctrls_(~strcmp(styles_, 'text')); % disabled static text greys to white in Qt +prevEnable_ = get(ctrls_, 'Enable'); +if ~iscell(prevEnable_), prevEnable_ = {prevEnable_}; end +prevKey_ = get(PSfig, 'KeyPressFcn'); + +set(ctrls_, 'Enable', 'off'); +setappdata(0, 'PStrimming', 1); + +unwind_protect + for click_ = 1:2 + lvAx_ = []; + ax_ = findobj(PSfig, 'Type', 'axes'); + for ai_ = 1:numel(ax_) + if any(strcmp(get(ax_(ai_), 'Tag'), {'PSrpy', 'PSmotor', 'PScombo'})) + lvAx_(end+1) = ax_(ai_); + end + end + if isempty(lvAx_), break; end + + set(PSfig, 'KeyPressFcn', ''); % stop 'i'/'o' from nesting another capture + x_ = PStrimWaitClick(PSfig, guiHandles.startEndButton, lvAx_); + if isempty(x_), break; end % Trim un-clicked = cancel the phase + + if click_ == 1 + epoch1_A(fnum_) = round(x_*10)/10; + else + epoch2_A(fnum_) = round(x_*10)/10; + end + PSplotLogViewer; + end +unwind_protect_cleanup + setappdata(0, 'PStrimming', 0); + for i_ = 1:numel(ctrls_) + try set(ctrls_(i_), 'Enable', prevEnable_{i_}); catch, end + end + set(PSfig, 'KeyPressFcn', prevKey_); + try PSdatatipSetup(PSfig); catch, end +end_unwind_protect diff --git a/src/util/PStrimWaitClick.m b/src/util/PStrimWaitClick.m new file mode 100644 index 0000000..081e88f --- /dev/null +++ b/src/util/PStrimWaitClick.m @@ -0,0 +1,16 @@ +function x = PStrimWaitClick(fig, trimBtn, lvAx) +% Wait for one left-click on the log-viewer axes and return its x-position. +% Returns [] if the Trim box gets un-clicked (cancel) or the window closes (#21). +% A short pause loop replaces blocking ginput so the cancel click can break it. + + set(lvAx, 'ButtonDownFcn', @(src, ~) PStrimClick(src)); + setappdata(0, 'PStrimX', []); + while isempty(getappdata(0, 'PStrimX')) + if ~ishghandle(fig) || get(trimBtn, 'Value') == 0 + x = []; + return + end + pause(0.02); + end + x = getappdata(0, 'PStrimX'); +end From 281caf9675fc74e8767e0da9121d6239ab07cd3a Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 16 Jun 2026 17:10:27 +0200 Subject: [PATCH 75/80] chore: bump version to 26.06.0-rc3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 331fb09..281b4d3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.06.0-rc2 +26.06.0-rc3 From 46442882e1d91c1f9cfde13c7744d4c08dedf609 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 11 Aug 2026 17:30:15 +0200 Subject: [PATCH 76/80] fix(parser): correct multi-session log selection and INAV decoder fallback --- src/core/PSgetcsv.m | 95 +++++++++++++++++++++--------------- tests/test_PSgetcsv.m | 111 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 tests/test_PSgetcsv.m diff --git a/src/core/PSgetcsv.m b/src/core/PSgetcsv.m index cad75e8..b00df5e 100644 --- a/src/core/PSgetcsv.m +++ b/src/core/PSgetcsv.m @@ -1,8 +1,9 @@ -function [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir) -%% [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir) +function [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir, sel) +%% [filename csvFnames] = PSgetcsv(filename, firmware_flag, outdir, sel) % Converts bbl files to csv using blackbox_decode % filename: full path to BBL/BFL/TXT/BTFL/JSON/BIN file % outdir: directory for CSV output (default: same as input file) +% sel: session numbers to keep from a multi-log file; omit to ask the user % ---------------------------------------------------------------------------------- % "THE BEER-WARE LICENSE" (Revision 42): @@ -21,6 +22,8 @@ if nargin < 3 || isempty(outdir) outdir = fdir; end +if nargin < 4, sel = []; end +askUser = isempty(sel); if strcmpi(fext, '.bin') csvFnames = {filename}; @@ -36,10 +39,10 @@ decoder_path = getappdata(0, 'PSdecoderPath'); decoder_inav = getappdata(0, 'PSdecoderPathINAV'); - if firmware_flag == 3 && ~isempty(decoder_inav) - % INAV decoder has no --output-dir; copy input to workdir first + if firmware_flag == 3 && ~isempty(decoder_inav) && exist(decoder_inav, 'file') == 2 + % INAV decoder has no --output-dir; run it on a copy inside outdir tmpSrc = fullfile(outdir, [fname fext]); - copyfile(filename, tmpSrc); + if ~strcmp(tmpSrc, filename), copyfile(filename, tmpSrc); end cmd = ['"' decoder_inav '" "' tmpSrc '" 2>&1']; else cmd = ['"' decoder_path '" --output-dir "' outdir '" "' filename '" 2>&1']; @@ -52,9 +55,15 @@ end fbase = fullfile(outdir, fname); - files = dir([fbase '*.csv']); - % filter out files with .bbl or .bfl in name + % junk the decoder drops next to the csv output + for pat = {'*.event', '*.gps.gpx', '*.gps.csv'} + junk = dir([fbase pat{1}]); + for k = 1:size(junk,1), delete(fullfile(outdir, junk(k).name)); end + end + + % side outputs keep the source extension in the name + files = dir([fbase '*.csv']); valid = true(size(files,1), 1); for k = 1:size(files,1) if contains(files(k).name, '.bbl', 'IgnoreCase', true) || contains(files(k).name, '.bfl', 'IgnoreCase', true) @@ -64,50 +73,60 @@ files = files(valid, :); if isempty(files) - set(gcf, 'pointer', 'arrow'); csvFnames = {}; return; end - % clean up junk files in outdir - fevt = dir([fbase '*.event']); - for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end - fevt = dir([fbase '*.gps.gpx']); - for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end - fevt = dir([fbase '*.gps.csv']); - for k = 1:size(fevt,1), delete(fullfile(outdir, fevt(k).name)); end - - % refresh file list after cleanup - files = dir([fbase '*.csv']); - if size(files,1) > 1 - % remove empty subfiles (<1KB) - valid = true(size(files,1), 1); + % the decoder prints one duration per log, in the order it wrote them, + % so pair them up before anything is dropped from the list + a = strfind(result, 'duration'); + labels = cell(size(files,1), 1); for k = 1:size(files,1) - if files(k).bytes < 1000 - delete(fullfile(outdir, files(k).name)); - valid(k) = false; + if k <= length(a) + labels{k} = result(a(k):min(a(k)+filename_nchars, length(result))); + else + labels{k} = files(k).name; end end - files = files(valid, :); - a = strfind(result, 'duration'); - logDurStr = ''; - for d = 1:length(a) - logDurStr{d} = [int2str(d) ') ' result(a(d):a(d)+filename_nchars)]; + % logs under 1KB hold no flight data - drop them and their labels together + small = [files.bytes] < 1000; + for k = find(small), delete(fullfile(outdir, files(k).name)); end + files = files(~small, :); + labels = labels(~small); + + if isempty(files) + if askUser + a = errordlg(['no valid data in ' mainFname]); pause(3); close(a); + else + warning('PSgetcsv: no valid data in %s', mainFname); + end + csvFnames = {}; + return; end + end - if size(files,1) > 0 - x = size(files,1); - if x > 1 - [fnums, tf] = listdlg('ListString', logDurStr, 'ListSize', [250, round(size(logDurStr,2)*20)], 'Name', 'Select file(s): '); - for k = 1:x - if ~ismember(k, fnums), delete(fullfile(outdir, files(k).name)); end - end + if size(files,1) > 1 + if askUser + for k = 1:size(files,1) + labels{k} = [int2str(k) ') ' labels{k}]; + end + % the dialog opens behind the import waitbar on Windows and looks like a hang + wb = findall(0, 'Type', 'figure', 'Tag', 'waitbar'); + set(wb, 'Visible', 'off'); + [fnums, tf] = listdlg('ListString', labels, 'ListSize', [250, round(numel(labels)*20)], 'Name', 'Select file(s): '); + set(wb, 'Visible', 'on'); + if ~tf || isempty(fnums) + for k = 1:size(files,1), delete(fullfile(outdir, files(k).name)); end + csvFnames = {}; + return; end else - validData = 0; - a = errordlg(['no valid data in ' mainFname]); pause(3); close(a); + fnums = sel(sel >= 1 & sel <= size(files,1)); + end + for k = 1:size(files,1) + if ~ismember(k, fnums), delete(fullfile(outdir, files(k).name)); end end end end diff --git a/tests/test_PSgetcsv.m b/tests/test_PSgetcsv.m new file mode 100644 index 0000000..c636648 --- /dev/null +++ b/tests/test_PSgetcsv.m @@ -0,0 +1,111 @@ +%% Tests for PSgetcsv - blackbox_decode driver and multi-session selection +%% The session number is always passed explicitly so no dialog is ever reached. + +%!function d = fake_decoder_(outdir, stdoutTxt, csvSpec) +%! % Shell stub standing in for blackbox_decode: writes the requested csv +%! % files into --output-dir and echoes canned decoder chatter. +%! d = fullfile(outdir, 'fake_decode.sh'); +%! fid = fopen(d, 'w'); +%! fprintf(fid, '#!/bin/sh\n'); +%! fprintf(fid, 'out=\n'); +%! fprintf(fid, 'while [ $# -gt 0 ]; do\n'); +%! fprintf(fid, ' case "$1" in --output-dir) out="$2"; shift 2;; *) src="$1"; shift;; esac\n'); +%! fprintf(fid, 'done\n'); +%! % without --output-dir the real decoders write next to the input file +%! fprintf(fid, '[ -n "$out" ] || out=$(dirname "$src")\n'); +%! fprintf(fid, 'base=$(basename "$src"); base=${base%%.*}\n'); +%! for k = 1:size(csvSpec, 1) +%! fprintf(fid, 'printf "%%s" "%s" > "$out/$base%s"\n', csvSpec{k,2}, csvSpec{k,1}); +%! end +%! fprintf(fid, 'printf "%%s" "%s"\n', stdoutTxt); +%! fclose(fid); +%! system(['chmod +x "' d '"']); +%!endfunction + +%!function src = fake_log_(wd, name) +%! src = fullfile(wd, name); +%! fid = fopen(src, 'w'); fprintf(fid, 'x'); fclose(fid); +%!endfunction + +%!test +%! % "duration" landing near the end of decoder output must not overrun the string +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, 'log 1 duration', {'.01.csv', ['a\n' bigrow]; '.02.csv', ['b\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! src = fake_log_(wd, 'LOG00001.BBL'); +%! err = ''; +%! try +%! [~, csvFnames] = PSgetcsv(src, 1, wd, 1); +%! catch e +%! err = e.message; +%! end +%! assert(isempty(err), 'PSgetcsv threw on truncated decoder output: %s', err); + +%!test +%! % the session list is built from decoder chatter but indexed against the file +%! % list - a short log dropped for being <1KB must not desync the two +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, ... +%! 'log 1 duration 00:10.000 log 2 duration 00:20.000 log 3 duration 00:30.000 end', ... +%! {'.01.csv', ['a\n' bigrow]; '.02.csv', 'tiny'; '.03.csv', ['c\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! src = fake_log_(wd, 'LOG00002.BBL'); +%! [~, csvFnames] = PSgetcsv(src, 1, wd, 2); +%! assert(numel(csvFnames) == 1, 'expected one csv, got %d', numel(csvFnames)); +%! % session 2 of the surviving logs is the .03 file, the tiny .02 is gone +%! assert(~isempty(strfind(csvFnames{1}, '.03.csv')), ... +%! 'selected the wrong session: %s', csvFnames{1}); + +%!test +%! % a selection past the end of the list must not index out of bounds +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, 'log 1 duration 00:10.000 log 2 duration 00:20.000 end', ... +%! {'.01.csv', ['a\n' bigrow]; '.02.csv', ['b\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! src = fake_log_(wd, 'LOG00005.BBL'); +%! [~, csvFnames] = PSgetcsv(src, 1, wd, [1 7]); +%! assert(numel(csvFnames) == 1, 'expected one csv, got %d', numel(csvFnames)); + +%!test +%! % .bbl.csv side output is filtered out, and must stay filtered after the +%! % junk-file cleanup refreshes the list +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, 'log 1 duration 00:10.000 end', ... +%! {'.01.csv', ['a\n' bigrow]; '.bbl.csv', ['junk\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! src = fake_log_(wd, 'LOG00003.BBL'); +%! [~, csvFnames] = PSgetcsv(src, 1, wd, 1); +%! assert(numel(csvFnames) == 1, 'expected one csv, got %d', numel(csvFnames)); +%! assert(isempty(strfind(csvFnames{1}, '.bbl.csv')), ... +%! 'returned the filtered .bbl.csv: %s', csvFnames{1}); + +%!test +%! % A missing INAV decoder must not silently produce nothing - fall back to +%! % the standard decoder rather than shelling out to a path that isn't there +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, 'log 1 duration 00:10.000 end', {'.01.csv', ['a\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! setappdata(0, 'PSdecoderPathINAV', fullfile(wd, 'does_not_exist_INAV')); +%! src = fake_log_(wd, 'LOG00004.TXT'); +%! [~, csvFnames] = PSgetcsv(src, 3, wd, 1); +%! setappdata(0, 'PSdecoderPathINAV', ''); +%! assert(numel(csvFnames) == 1, ... +%! 'INAV import produced %d csv files when the INAV decoder is absent', numel(csvFnames)); + +%!test +%! % INAV runs the decoder on a copy inside outdir - when the log already lives +%! % there, copying it onto itself must not abort the import +%! wd = tempname(); mkdir(wd); +%! bigrow = repmat('9', 1, 1200); +%! dec = fake_decoder_(wd, 'log 1 duration 00:10.000 end', {'.01.csv', ['a\n' bigrow]}); +%! setappdata(0, 'PSdecoderPath', dec); +%! setappdata(0, 'PSdecoderPathINAV', dec); +%! src = fake_log_(wd, 'LOG00006.TXT'); +%! [~, csvFnames] = PSgetcsv(src, 3, wd, 1); +%! setappdata(0, 'PSdecoderPathINAV', ''); +%! assert(numel(csvFnames) == 1, 'expected one csv, got %d', numel(csvFnames)); From bfc7a1598c0beaec6ee1bd5ca3d6913321978b65 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 11 Aug 2026 17:30:18 +0200 Subject: [PATCH 77/80] chore: bump version to 26.08.0-rc4 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 281b4d3..9bd4446 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.06.0-rc3 +26.08.0-rc4 From 58e2ee45e7c2a695e9af3b2e0dd44128ed37c279 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 11 Aug 2026 20:12:11 +0200 Subject: [PATCH 78/80] fix(ui): ignore dashboard clicks during spectral runs --- src/plot/PSplotSpec.m | 5 ++++- src/plot/PSplotSpec2D.m | 5 +++++ src/ui/PSfreqTimeUIcontrol.m | 9 ++++++--- src/ui/PSspecUIcontrol.m | 7 +++++-- src/ui/PSviewerUIcontrol.m | 3 +++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/plot/PSplotSpec.m b/src/plot/PSplotSpec.m index 2f81725..9de5380 100644 --- a/src/plot/PSplotSpec.m +++ b/src/plot/PSplotSpec.m @@ -115,7 +115,9 @@ if updateSpec==0 clear s dat ampmat amp2d freq a RC smat amp2d freq2d Throt p=0; - hw = waitbar(0,['please wait... ' ]); + % waitbar pumps the event queue, so hold off dashboard clicks until done + setappdata(0, 'PSbusy', 1); + hw = waitbar(0,['please wait... ' ]); tmpPSDVal = get(guiHandlesSpec.checkboxPSD, 'Value'); for k=1:length(vars) @@ -148,6 +150,7 @@ end end close(hw) + setappdata(0, 'PSbusy', 0); end else warndlg({'Dropdowns set to ''NONE''.'; 'Please select a preset or specific variables to analyze.'}); diff --git a/src/plot/PSplotSpec2D.m b/src/plot/PSplotSpec2D.m index cd7955a..4ca2787 100644 --- a/src/plot/PSplotSpec2D.m +++ b/src/plot/PSplotSpec2D.m @@ -50,6 +50,8 @@ %%% compute delay/overlay data (deferred from UI open to Run click) if ~exist('delayDataReady','var') || ~delayDataReady + % waitbar pumps the event queue, so hold off dashboard clicks until done + setappdata(0, 'PSbusy', 1); hw_delay = waitbar(0, 'computing delays...'); FilterDelayDterm={}; SPGyroDelay=[]; @@ -157,6 +159,7 @@ end delayDataReady = true; try close(hw_delay); catch, end + setappdata(0, 'PSbusy', 0); end tmpSpecVal = get(guiHandlesSpec2.SpecList, 'Value'); @@ -177,6 +180,7 @@ freq2d2 = {}; amp2d2 = {}; p=0; +setappdata(0, 'PSbusy', 1); hw_fft = waitbar(0, 'computing FFT...'); for k = 1 : length(tmpSpecVal) s = char(datSelectionString(tmpSpecVal(k))); @@ -233,6 +237,7 @@ end end try close(hw_fft); catch, end +setappdata(0, 'PSbusy', 0); prevPsdKey_ = struct('specVal', tmpSpecVal, 'fileVal', tmpFileVal, 'psdVal', tmpPSDVal, 'axes', axesOptionsSpec); end diff --git a/src/ui/PSfreqTimeUIcontrol.m b/src/ui/PSfreqTimeUIcontrol.m index 5349b38..7b68d43 100644 --- a/src/ui/PSfreqTimeUIcontrol.m +++ b/src/ui/PSfreqTimeUIcontrol.m @@ -6,9 +6,11 @@ % can do whatever you want with this stuff. If we meet some day, and you think % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- - + +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call from a running job + if exist('fnameMaster','var') && ~isempty(fnameMaster) - + %%% tooltips TooltipString_specRun=['Run current spectral configuration']; TooltipString_cmap=['Choose from a selection of colormaps']; @@ -228,10 +230,11 @@ try set(guiHandlesSpec3.subsampleFactor_select, 'Value', defaults.Values(find(st try set(guiHandlesSpec3.ColormapSelect, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqxTime-Colormap')))), catch, set(guiHandlesSpec3.ColormapSelect, 'Value', 3), end +PSstyleControls(PSspecfig3); + else warndlg('Please select file(s)'); end -PSstyleControls(PSspecfig3); % functions function selection2(src,event) diff --git a/src/ui/PSspecUIcontrol.m b/src/ui/PSspecUIcontrol.m index 25fdeb6..eb99089 100644 --- a/src/ui/PSspecUIcontrol.m +++ b/src/ui/PSspecUIcontrol.m @@ -6,7 +6,9 @@ % can do whatever you want with this stuff. If we meet some day, and you think % this stuff is worth it, you can buy me a beer in return. -Brian White % ---------------------------------------------------------------------------------- - + +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call from a running job + if exist('fnameMaster','var') && ~isempty(fnameMaster) %%% tooltips @@ -328,10 +330,11 @@ try set(guiHandlesSpec.smoothFactor_select, 'Value', defaults.Values(find(strcmp(defaults.Parameters, 'FreqXthr-Smoothing')))), catch, set(guiHandlesSpec.smoothFactor_select, 'Value', 3); end +PSstyleControls(PSspecfig); + else warndlg('Please select file(s)'); end -PSstyleControls(PSspecfig); % functions function selection2(src,event) diff --git a/src/ui/PSviewerUIcontrol.m b/src/ui/PSviewerUIcontrol.m index 2b3ae19..29c3742 100644 --- a/src/ui/PSviewerUIcontrol.m +++ b/src/ui/PSviewerUIcontrol.m @@ -9,6 +9,9 @@ % ---------------------------------------------------------------------------------- +if getappdata(0, 'PSbusy'), return; end % skip re-entrant call from a running job +if ~exist('PSfig','var') || ~ishandle(PSfig), return; end % called outside the main workspace + % Checkbox bar — pixel sizes (constant across resizes) chkW_px = 130; chkMotW_px = 100; chkEdtW_px = 45; chkTxtW_px = 65; figPos = get(PSfig, 'Position'); figW = figPos(3); figH = figPos(4); From e291895e7f78c2698e8fa9afad1063ab107a3163 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Tue, 11 Aug 2026 20:27:14 +0200 Subject: [PATCH 79/80] chore: bump version to 26.08.0-rc5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9bd4446..de39a09 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.08.0-rc4 +26.08.0-rc5 From f65a415367f793848b48dc861a59ba46b6b2ca0d Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Thu, 13 Aug 2026 08:58:11 +0200 Subject: [PATCH 80/80] chore: bump version to 26.08.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index de39a09..bf58b94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.08.0-rc5 +26.08.0