Tutorial 5. Plot monitor#

See also

The complete source code of this tutorial can be found in

Tutorial 5. Plot monitor.ipynb

In this tutorial we dive into the capabilities of the plot monitor. We will create a fictional device and showcase how the plot monitor can be used. Enjoy!

Hide code cell content
import numpy as np
from IPython.core.interactiveshell import InteractiveShell
from qcodes import Instrument, ManualParameter

from quantify_core.data.handling import (
    get_tuids_containing,
    set_datadir,
    default_datadir,
)
from quantify_core.measurement import MeasurementControl
from quantify_core.visualization.pyqt_plotmon import PlotMonitor_pyqt

rng = np.random.default_rng(seed=555555)  # random number generator

# Display any variable or statement on its own line
InteractiveShell.ast_node_interactivity = "all"

Before instantiating any instruments or starting a measurement we change the directory in which the experiments are saved using the set_datadir() [get_datadir()] functions.


⚠️ Warning!

We recommend always setting the directory at the start of the python kernel and stick to a single common data directory for all notebooks/experiments within your measurement setup/PC.

The cell below sets a default data directory (~/quantify-data on Linux/macOS or $env:USERPROFILE\\quantify-data on Windows) for tutorial purposes. Change it to your desired data directory. The utilities to find/search/extract data only work if all the experiment containers are located within the same directory.


set_datadir(default_datadir())
Data will be saved in:
/root/quantify-data

QCoDeS drivers for our instruments#

class Device(Instrument):
    """A dummy instrument."""

    def __init__(self, name: str):
        super().__init__(name=name)

        self.add_parameter(name="amp_0", unit="A", parameter_class=ManualParameter)
        self.add_parameter(name="amp_1", unit="A", parameter_class=ManualParameter)
        self.add_parameter(name="offset", unit="A", parameter_class=ManualParameter)

        self.add_parameter(
            name="adc", label="ADC input", unit="V", get_cmd=self._get_dac_value
        )

    def _get_dac_value(self):
        s1 = np.exp(-3 * self.amp_0()) * np.sin(self.amp_0() * 2 * np.pi * 3)
        s2 = np.cos(self.amp_1() * 2 * np.pi * 2)
        return s1 + s2 + rng.uniform(0, 0.2) + self.offset()

Instantiate the instruments#

meas_ctrl = MeasurementControl("meas_ctrl")
plotmon = PlotMonitor_pyqt("PlotMonitor")
meas_ctrl.instr_plotmon(plotmon.name)
device = Device("Device")

Overview#

There are 3 parameters in the PlotMonitor_pyqt that control the datasets being displayed.

Two main parameters determine the datasets being displayed: tuids and tuids_extra.

plotmon.tuids()
plotmon.tuids_extra()
[]
[]

The interface is the same for both. The parameters accept a list of tuids or an empty list to reset.

### Example of loading datasets onto the plot
#plotmon.tuids(["20201124-184709-137-8a5112", "20201124-184716-237-918bee"])
#plotmon.tuids_extra(["20201124-184722-988-0463d4", "20201124-184729-618-85970f"])

The difference is that the MeasurementControl uses tuids and overrides them when running measurements.

Note

All the datasets must have matching data variables (settables and gettables).

The third relevant parameter is the tuids_max_num. It accepts an integer that determines the maximum number of datasets that will be stored in tuids when the MeasurementControl is running.

plotmon.tuids_max_num()
3

Note

This parameter has no effect when setting the tuids manually.

Usage examples#

# set initial values to emulate the instrument state
device.amp_0(0.0)
device.amp_1(0.0)
device.offset(0.0)

n_pnts = 50

meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/0d7e1f1cf3477e9827442364ae337d1e128baeac234ec6929a07acf8f24a9a9c.png
n_pnts = 20

meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/d42f92be734a2c26ebcfb7ea7554c27cb7ab5db08fd7121d89c4a346ab12b1fc.png
n_pnts = 30

meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/5d9385a19f7dc3b8adf741eaa747cc70cb8db2b59dbe5853b49c02ea0c935ff3.png

Now the oldest dataset will vanish from the plot:

# Now the oldest dataset will vanish from the plot

n_pnts = 40

meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/7c2c9c6de6904267c3bd9c3e68fc5fd4826e236a3b6c0ad4eb498efcc4fa752c.png

We can accumulate more datasets on the plot if we want to:

# We can accumulate more datasets on the plot if we want to
plotmon.tuids_max_num(4)
n_pnts = 40

meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/c797469de2c9cbd73786e53a0d873fb02b0d0650ed4c5856a53a0b2350c6eba7.png

Or we can disable the accumulation and plot a single dataset:

# Or we can disable the accumulation and plot a single dataset
plotmon.tuids_max_num(1)

plotmon.main_QtPlot
../_images/8fce37673ce48b84345b3d97ad1a50669bb08f6a74463da62af73047153fcc36.png

This can also be reset:

# This can also be reset with
plotmon.tuids([])

plotmon.main_QtPlot  # The plotting window will vanish, it is supposed to
../_images/fabf04168e56bbb0f5186f5950510ed6a80473057a98bf96209bf8df36593f5c.png

For now, we will allow two datasets on the plot monitor.

# For now we will allow two datasets on the plot monitor
plotmon.tuids_max_num(2)

Now let’s imagine that something strange is happening with our setup…

# Now let's imagine that something strange is happening with our setup
device.offset(1.5)

n_pnts = 40
meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan problem")
Starting iterative measurement...
plotmon.main_QtPlot
../_images/993fde5ba5ee1e98dc79c8ecc9673e31e123a105857740e603d50f7edc1054af.png

We would like to compare if the current behavior matches for example what we got a few minutes ago:

# We would like to compare if the current behavior matches for example
# what we got a few minutes ago

reference_tuids = sorted(get_tuids_containing("ADC"))[-3:-1]

plotmon.tuids_extra(reference_tuids)
plotmon.main_QtPlot
../_images/79627a3334d91d12a73f255b929859f5f0e52143a82e2cd03fbc469b431dfea4.png

OK… that cable was not connected in the right place…

device.offset(0.0)  # OK... that cable was not connected in the right place...

# Now let's run again our experiments while we compare it to the previous one in realtime

n_pnts = 30
meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")

n_pnts = 40
meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")
Starting iterative measurement...
Starting iterative measurement...
plotmon.main_QtPlot
../_images/aa59d09b929ccdd5824e180a27503f6364545c0584a6bd65468e0247ada08f3f.png

We do not need the reference datasets anymore

# We do not need the reference datasets anymore
plotmon.tuids_extra([])
plotmon.main_QtPlot
../_images/8d61f992443f5ff31f70144ec844a037e5c7f273f04d154991ed894008138a88.png
# Note: both plotmon.tuids_extra and plotmon.tuids can be used
# but keep in mind that meas_ctrl also uses the plotmon.tuids

tuids = get_tuids_containing("problem")[0:1]
tuids
plotmon.tuids(tuids)

n_pnts = 40
meas_ctrl.settables(device.amp_0)
meas_ctrl.setpoints(np.linspace(0, 1, n_pnts))
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan")

plotmon.main_QtPlot
['20241106-153112-177-d25840']
Starting iterative measurement...
../_images/fcf8f680076f9281ce6e11c19cd741d016f2a5574d8f65024246488b64c75331.png

When we have 2D plots only the first dataset from plotmon.tuids or plotmon.tuids_extra will be plotted in the secondary window, in that order of priority.

# When we have 2D plots only the first dataset from plotmon.tuids or
# plotmon.tuids_extra, in that order of priority, will be plotted in the
# secondary window

meas_ctrl.settables([device.amp_0, device.amp_1])
meas_ctrl.setpoints_grid([np.linspace(0, 1, 20), np.linspace(0, 0.5, 15)])
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan 2D")
reference_tuid_2D = dset.attrs["tuid"]
Starting iterative measurement...
plotmon.main_QtPlot
plotmon.secondary_QtPlot
../_images/dcd781057f91dde64f098ecdcf8f09c2506d098d66ed67aa739d2cfcbb7d6cab.png ../_images/06aed89c3d92ba2b5d4388275aef051c03edd3a9145f2ad6cfc69b1440be6ace.png

Note

The secondary window displays the last dataset with a 2D structure, and it remains persistent until replaced by a new dataset with a 2D structure.

Mind that the data on the secondary window does not always display data corresponding to the same dataset as the main window.

We still have the persistence of the previous dataset on the main window:

meas_ctrl.settables([device.amp_0, device.amp_1])
meas_ctrl.setpoints_grid([np.linspace(0, 1, 20), np.linspace(0, 0.5, 15)])
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan 2D")
Starting iterative measurement...
plotmon.main_QtPlot
plotmon.secondary_QtPlot
../_images/c07eeb32a7b7354c1aac3f98fc319b487d7fa8dd3eed71fe66dab7be2b3f6fa1.png ../_images/df362a70c2cf6f131be1f5525de5da8e5e65e9db396944ba3c29005d515c3f8e.png

We can still have a permanent dataset as a reference in the main window:

device.offset(2.03)
plotmon.tuids_extra([reference_tuid_2D])

meas_ctrl.settables([device.amp_0, device.amp_1])
meas_ctrl.setpoints_grid([np.linspace(0, 1, 20), np.linspace(0, 0.5, 15)])
meas_ctrl.gettables(device.adc)
dset = meas_ctrl.run("ADC scan 2D")
Starting iterative measurement...
plotmon.main_QtPlot
plotmon.secondary_QtPlot
../_images/7879dd579be16b8918f6709dbd37c0c27f561656d872b98ca1fd1e8a86b417bc.png ../_images/24d10c8f9e068e57285083c11cfef65d2bf30a835883b944db0106f6f49651fb.png

But if we want to see the 2D plot we need to reset plotmon.tuids.

plotmon.tuids([])
plotmon.tuids_extra([reference_tuid_2D])
plotmon.main_QtPlot
plotmon.secondary_QtPlot
../_images/e287a4b2837eea946d63a31c82f1731abeba688deba146753a0b644b1b3efdcf.png ../_images/06aed89c3d92ba2b5d4388275aef051c03edd3a9145f2ad6cfc69b1440be6ace.png
plotmon.tuids()
[]

Now your life will never be the same again ;)