call_qt_aware (matplotlib helpers)

When attempting to use matplotlib UI functions directly in a plan, and running matplotlib using a Qt backend (e.g. in a standalone shell outside IBEX), you may see a hang or an error of the form:

UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
  fig, ax = plt.subplots()

This is because the RunEngine runs plans in a worker thread, not in the main thread, which then requires special handling when calling functions that will update a UI.

The ibex_bluesky_core.plan_stubs.call_qt_aware plan stub can call matplotlib functions in a Qt-aware context, which allows them to be run directly from a plan. It allows the same arguments and keyword-arguments as the underlying matplotlib function it is passed.

Note

Callbacks such as LivePlot and LiveFitPlot already route UI calls to the appropriate UI thread by default. The following plan stubs are only necessary if you need to call functions which will create or update a matplotlib plot from a plan directly - for example to create or close a set of axes before passing them to callbacks.

Usage example:

import matplotlib.pyplot as plt
from ibex_bluesky_core.plan_stubs import call_qt_aware
from ibex_bluesky_core.callbacks.plotting import LivePlot
from bluesky.callbacks import LiveFitPlot
from bluesky.preprocessors import subs_decorator


def my_plan():
    # BAD - likely to either crash or hang the plan.
    # plt.close("all")
    # fig, ax = plt.subplots()

    # GOOD
    yield from call_qt_aware(plt.close, "all")
    fig, ax = yield from call_qt_aware(plt.subplots)

    # Pass the matplotlib ax object to other callbacks
    @subs_decorator([
        LiveFitPlot(..., ax=ax),
        LivePlot(..., ax=ax),
    ])
    def inner_plan():
        ...