From 83ce6dcd44f5eb314e650c1e92505034f8edfc19 Mon Sep 17 00:00:00 2001 From: Grzegorz Sterniczuk Date: Sat, 7 Mar 2026 10:05:36 +0100 Subject: [PATCH 01/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] =?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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] =?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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] =?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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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