Search code examples
pythonmatplotlibcolormapreal-time-datareal-time-updates

How do you real-time plot a colormap?


I am currently working on creating a Qt window with 3 matplotlib colormaps.

The process is something like this:

  1. The user will input the x and y values of the plots into a GUI (each plot will have the same x and y values).
  2. These values will be converted and then sent to a microprocessor which will communicate with both a DAC and ADC (Instrument "stuff" happens on the "backend" between the DAC and ADC, i.e. a spectrum analyzer takes noise measurements).
  3. For each x and y point, we will receive one z value for each colormap. To emphasize: This means that each subplot will need to update simultaneously.

I am currently working on the script that will real-time graph these colormaps in a Qt window as the data is being taken. I can find readily accessible resources on how to real-time plot line graphs; however, I am unable to find anything for colormaps. Here is the code I have so far:

import sys
import time
import random

import numpy as np

from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import (
    FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable


# Quick note, this will need to be a widget class in the final version so it can be run from the kalamari main window
# but I think that will just change how the class is initalized

class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        # set up the window
        super().__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        # It seems as though this layout is what is going to allow me to
        # orient all 3 subplots onto the same y axis
        layout = QtWidgets.QVBoxLayout(self._main)

        # create seperate canvas objects
        first_canvas = FigureCanvas(Figure(figsize=(9, 6)))
        layout.addWidget(NavigationToolbar(first_canvas, self))
        layout.addWidget(first_canvas)

        second_canvas = FigureCanvas(Figure(figsize=(9, 3)))
        layout.addWidget(second_canvas)
        layout.addWidget(NavigationToolbar(second_canvas, self))

        # add subplots to the first canvas
        self._first_axs = first_canvas.figure.subplots(1, 3)

        # create data and colormap

        # Here I replace X and Y with coordinate vectors which are just np.linspace
        x = np.linspace(0, 10, 10)
        y = np.linspace(0, 10, 10)
        # For the final version you can use x for flux and y for biases and then have
        # each row of z be the voltages, such that:
        # z[i,j] is V(bias[j], flux[i])
        # Then here is the data I will use to determine the color,
        # this needs to have the same dimension as the coordinates
        z = np.random.rand(10, 10)

        custom_cmap = mpl.colors.LinearSegmentedColormap.from_list(
            "custom", ["#00008B", "blue", "cyan", "green", "yellow", "orange", "red", "#8B0000"])

        # access each subplot using regular indexing
        self._first_axs[0].set_title(
            'I' + u'\u209B' + u'\u2092' + u'\u209C', size=40)
        self._first_axs[1].set_title(
            'dI' + u'\u209B' + u'\u2092' + u'\u209C' + '/dt', size=40)
        self._first_axs[2].set_title('Noise', size=40)

        # plot data and create colorbars
        self.plot1 = self._first_axs[0].contourf(
            x, y, z, levels=20, cmap=custom_cmap)
        self.plot2 = self._first_axs[1].contourf(
            x, y, z, levels=20, cmap=custom_cmap)
        self.plot3 = self._first_axs[2].contourf(
            x, y, z, levels=20, cmap=custom_cmap)
        self.cbar1 = first_canvas.figure.colorbar(
            self.plot1, ax=self._first_axs[0], orientation='horizontal')
        self.cbar2 = first_canvas.figure.colorbar(
            self.plot2, ax=self._first_axs[1], orientation='horizontal')
        self.cbar3 = first_canvas.figure.colorbar(
            self.plot3, ax=self._first_axs[2], orientation='horizontal')

        # make the second canvas a dynamic plot
        self._second_ax = second_canvas.figure.subplots()
        t = list(range(50))
        self.yData = [random.randint(0, 10) for i in range(50)]

        # Set up a Line2D.
        self._line, = self._second_ax.plot(t, self.yData)
        self._timer = second_canvas.new_timer(50)
        self._timer.add_callback(self._update_canvas)
        self._timer.start()

    def _update_canvas(self):
        t = list(range(50))
        self.yData = self.yData[1:] + [random.randint(0, 10)]
        # set line data
        self._line.set_data(t, self.yData)
        self._line.figure.canvas.draw()


if __name__ == "__main__":
    # Check for open QApplication.instance()
    qapp = QtWidgets.QApplication.instance()
    if not qapp:
        qapp = QtWidgets.QApplication(sys.argv)

    # run it!
    app = ApplicationWindow()
    app.show()
    app.activateWindow()
    app.raise_()
    qapp.exec()

This code currently produces 1 Qt window with 2 separate "canvases". The first canvas is the 3 colormaps, the second is a line plot that real-time graphs.

Top Plots

  • These are static, but I would like them to be dynamic.
  • Would anybody be able to help me determine how to real-time plot these colormaps as I am collecting data?

enter image description here

Bottom Plot

  • This runs in real-time
  • I do not need the line graph for my purposes. It is just there as a reference to real-time plotting methods.

enter image description here


Solution

  • You can try replotting the contour with every update call. I use ax.cla() to clear the current plot, before plotting new information. This script is an example of what that might look like.

    import numpy as np
    import matplotlib.pyplot as plt
    
    rng = np.random.default_rng()
    
    N = 25
    x = np.linspace(-1, 1, N)
    y = np.linspace(-1, 1, N)
    
    Niter = 25
    fig, ax = plt.subplots()
    ax.set_aspect(1)
    for _ in range(Niter):
        ax.cla()
        z = rng.uniform(size=(N,N))
        ax.contourf(x, y, z)
        fig.canvas.draw()
        renderer = fig.canvas.renderer
        ax.draw(renderer)
        plt.pause(0.01)
    

    enter image description here