Skip to content

feat(autotune): draw the plots with pyqtgraph - #59

Merged
bresch merged 8 commits into
masterfrom
feat/autotune-pyqtgraph
Oct 28, 2026
Merged

bresch merged 8 commits into
masterfrom
feat/autotune-pyqtgraph

Conversation

@bresch

@bresch bresch commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

The GUI windows now draw their plots with pyqtgraph instead of matplotlib: the main window (General and Robustness tabs), the data selection and the PID analysis window. The command-line scripts (closed_loop_sim.py, simulated_autotune.py) keep matplotlib.

Display speed

Each tick of a gain slider recomputes the closed loop and redraws the plots. Median time per tick, measured on screen with the same model and window size:

Tab shown master this PR
General 741 ms 196 ms
Robustness 804 ms 198 ms

Drawing now takes about 30 ms per tick, down from about 575 ms. The matplotlib version cleared and redrew every axes on each change. The pyqtgraph plots are built once, and a tick only replaces their data. The remaining ~166 ms is the control computation (stability, delay and disk margins, root locus), which this PR does not change.

Screenshots

Taken on quadrotor_sitl_jmavsim.ulg between 54 s and 62 s, with the default preset (Rollrate).

Main window

Robustness tab

Data selection

Other changes

  • Zoom: the mouse wheel zooms and dragging pans. The zoom is kept across gain changes, and a double-click restores the default view. The right-click menu exports a plot, replacing the toolbar.
  • Hover readout: a crosshair with the value under the mouse on the time and frequency plots, in Hz on the frequency axes.
  • Linked axes: the Bode magnitude and phase share the frequency axis, and the sensitivity plot follows the Bode frequency range.
  • Bode resolution: the Bode plots use 500 frequencies instead of 40.
  • Data selection: input and output are stacked with a shared time axis, and a left drag selects the range. The selected range can then be moved or resized, and the wheel zooms the time axis only.
  • Theme: a light theme and a colorblind-checked palette.
  • Maximize: the main window was a dialog, which GNOME will not maximize. It is now a normal window.
  • README: new screenshots, and pyqtgraph in the install command.
  • CI: the lint job pinned Python 3.10.12, which the Ubuntu 26.04 runners don't provide, so it failed before linting anything. It now takes the latest 3.10, as the test jobs do.

Testing

  • New plot tests: test_plots.py, test_data_selection.py and test_pid_analysis.py check the data the plots show: curves, margins, legends, kept zoom, linked axes and range selection.
  • Missing tests added: test_fit_metrics.py and test_pid_gains.py already existed but were never committed. They are now part of the suite.
  • conftest.py: closes the windows a test leaves open. An open window repainted while pyqtgraph's plots were garbage collected segfaulted.
  • Full suite: all 110 tests pass. One suite run out of about 70 crashed without a traceback during development. It did not happen again in 40 runs that tried to reproduce it, or in later runs.
  • Manual check: tested on a GNOME desktop. Fixed a flicker in the plots with a locked aspect ratio (Nyquist, root locus, pole-zero): their tick labels and the axis width kept resizing each other.

A gain change took about 1.4 s: matplotlib redrew the general figure
twice per update and recomputed its constrained layout each time. The
general tab now creates its pyqtgraph items once and only updates their
data, which brings an update down to about 0.8 s; the rest is the
robustness tab, still on matplotlib, and the control math.

The plots use the light dataviz palette and keep the user's zoom across
updates; a double-click restores the default range. The Bode magnitude
and phase share their frequency axis and are drawn with 500 points
instead of 40. Time and frequency plots show a crosshair readout.

The fit metrics and PID gain tests were never added to the repository:
add them along with tests of the plotted data. Windows left open
segfault when garbage collected during a later paint, so the tests now
close them.
The robustness figure was cleared, replotted and laid out again on
every gain change. Its pyqtgraph items are now created once, which
brings a gain change from about 0.8 s down to about 0.3 s; what is left
is the computation of the margins and of the root locus.

The sensitivity plot shares its frequency range with the Bode plots of
the general tab. The views are kept equal by range rather than with
setXLink, which lines views up by screen position and would offset
plots that sit in different tabs over the same area.
Input and output were drawn on twin y-axes of one matplotlib plot. They
are now two stacked plots sharing the time axis, each with its own
scale. A left drag draws the selected range as SpanSelector did, and
the selection band on either plot can then be moved or resized; the two
bands stay in sync. The wheel zooms time only, within the log span, as
the custom matplotlib zoom did.
The step responses of all the windows are drawn as a single curve
broken between windows, instead of one matplotlib line per window.
plot_step_responses, whose only caller passed its own axes, is folded
into the window.
Document the plot helpers, the new plot tests and the reason for the
window-closing test fixture. The isort command now passes the Black
profile, as the pre-commit hook does: without it, isort rewraps imports
in a style the hook rejects.
The main window was a dialog box, and GNOME does not let a dialog
maximize: snapping it to the top of the screen, double-clicking the
title bar and Super+Up did nothing, and it had no maximize button.
It is now shown as a normal top-level window.
Show the pyqtgraph windows: the main window, the robustness tab and the
data selection, taken on quadrotor_sitl_jmavsim.ulg between 54 s and
62 s with the default preset. List pyqtgraph in the install commands.
ubuntu-latest moved to Ubuntu 26.04, which has no build of Python
3.10.12, so the job failed before checking anything. Take the latest
3.10 release instead, as the test workflows already do.
@bresch
bresch force-pushed the feat/autotune-pyqtgraph branch from 6830c37 to df0a97b Compare October 28, 2026 08:29
@bresch
bresch merged commit e3f92bd into master Oct 28, 2026
3 checks passed
@bresch
bresch deleted the feat/autotune-pyqtgraph branch October 28, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant