Plotting

LivePlot

Bluesky has good integration with matplotlib for data visualization, and data from scans may be easily plotted using the LivePlot callback.

ibex_bluesky_core provides a thin wrapper over bluesky’s default LivePlot callback, which ensures that plots are promptly displayed in IBEX.

In order to use the wrapper, import LivePlot from ibex_bluesky_core.callbacks rather than importing bluesky.callbacks.mpl_plotting.LivePlot directly:

from ibex_bluesky_core.callbacks.plotting import LivePlot

Configuration

A range of configuration options for LivePlot are available - see the bluesky LivePlot documentation for more details about available options.

The LivePlot object allows an arbitrary set of matplotlib Axes to be passed in, onto which it will plot. This can be used to configure properties which are not directly exposed on the LivePlot object, for example log-scaled axes.

See the matplotlib Axes documentation for a full range of options on how to configure an Axes object.

Below is a full example showing how to use standard matplotlib & bluesky functionality to plot a scan with a logarithmically-scaled y-axis:

import matplotlib.pyplot as plt
from ibex_bluesky_core.callbacks import LivePlot
from ibex_bluesky_core.plan_stubs import call_qt_aware

def plan():
    # Create a new figure to plot onto.
    yield from call_qt_aware(plt.figure)
    # Make a new set of axes on that figure
    ax = yield from call_qt_aware(plt.gca)
    # Set the y-scale to logarithmic
    yield from call_qt_aware(ax.set_yscale, "log")
    # Use the above axes in a LivePlot callback
    plot_callback = LivePlot(y="y_variable", x="x_variable", ax=ax, yerr="yerr_variable")
    # yerr is the uncertanties of each y value, producing error bars

Note

See docs for call_qt_aware for a description of why we need to use yield from call_qt_aware rather than calling matplotlib functions directly.

By providing a signal name to the yerr argument you can pass uncertainties to LivePlot, by not providing anything for this argument means that no errorbars will be drawn. Errorbars are drawn after each point collected, displaying their standard deviation- uncertainty data is collected from Bluesky event documents and errorbars are updated after every new point added.

The plot_callback object can then be subscribed to the run engine, using either:

  • An explicit callback when calling the run engine: RE(some_plan(), plot_callback)

  • Be subscribed in a plan using subs_decorator from bluesky (recommended)

  • Globally attached to the run engine using RE.subscribe

    • Not recommended, not all scans will use the same variables and a plot setup that works for one scan is unlikely to be optimal for a different type of scan.

By subsequently re-using the same ax object in later scans, rather than creating a new ax object for each scan, two scans can be “overplotted” with each other for comparison.

LivePColorMesh

LivePColorMesh is a specialized heatmap plotting callback which reacts to rows of data at a time. This is suitable for use by DAE reducers which emit rows of data at a time, such as ibex_bluesky_core.devices.simpledae.PeriodSpecIntegralsReducer or ibex_bluesky_core.devices.simpledae.DSpacingMappingReducer.

This callback updates live as the scan progresses. It is otherwise very similar to the existing bluesky plotting callbacks.

Note

Due to an implementation detail of matplotlib.pyplot.pcolormesh, the plot will only appear once at least two rows of data have been collected.

Saving plots to PNG files

ibex_bluesky_core provides a PlotPNGSaver callback to save plots on a run stop to PNG files, which by saves them to the default output file location unless a filepath is explicitly given.

This is enabled by default in the ISISCallbacks callbacks collection.

Using the above example (i.e. without the ISISCallbacks helper) it can be used like so:

from pathlib import Path
import matplotlib.pyplot as plt
from ibex_bluesky_core.callbacks import LivePlot, PlotPNGSaver
from ibex_bluesky_core.plan_stubs import call_qt_aware

def plan():
    # Create a new figure to plot onto.
    yield from call_qt_aware(plt.figure)
    # Make a new set of axes on that figure
    ax = yield from call_qt_aware(plt.gca)
    # Set the y-scale to logarithmic
    yield from call_qt_aware(ax.set_yscale, "log")
    # Use the above axes in a LivePlot callback
    plot_callback = LivePlot(y="y_variable", x="x_variable", ax=ax, yerr="yerr_variable")
    # Add a PNG saving callback
    png_callback = PlotPNGSaver(y="y_variable", x="x_variable", ax=ax, output_dir=Path("C://", "Some", "Custom", "Directory"), postfix="test123")