title: PyQt5 Crashes While Plotting with PyQtGraph in Real Time
body: <p>I'm currently using a LabView DAQ program, but I need some functionality that LabView doesn't offer. So I created a program using Python that reads inputs from the NIDAQ, converts them, plots to the screen, and stores the values in a local database. To optimize the system, I use a thread to read and process the pressure card, a thread to read the thermocouple card, and a thread to pull the channels I need and combine them into a Numpy array to pass to the main GUI thread. Since the different channels plot to different axes, I loop through the array and plot them to their individual axis.</p>
  <p>I wrote a DAQ simulator that generates semi-random values so I don't have to carry the hardware around. I think I have the bare minimum code to simulate the problem, still using two of the worker threads.</p>
  <p>I've tried multiple ways to plot the data, and this is the best I've been able to get; about 5 minutes and the GUI becomes unresponsive, and occasionally a full crash. I've profiled the program without any useful results. Or, I just don't know how to interpret the results.</p>
  <p><a href="https://i.stack.imgur.com/VUqOd.png" rel="nofollow noreferrer">Python Memory Profiler Plot</a></p>
  <pre><code>import sys
  import time
  import numpy as np
  import pyqtgraph as pg
  from PyQt5 import QtCore
  from PyQt5.QtCore import pyqtSignal, pyqtSlot
  from PyQt5.QtWidgets import QWidget, QMainWindow, QTreeWidget, QTreeWidgetItem, QFrame,  QTabWidget, QApplication
  
  pg.mkQApp()
  pg.setConfigOption('useOpenGL', True)
  
  webster = {0: {'name': 'Tubing'}, 
             1: {'name': 'Annulus'}}
  output_array = np.full((20, 16), -1.0)
  
  output_array[0, 0] = 0
  output_array[0, 1] = 0
  output_array[0, 6] = 0
  output_array[0, 7] = 0
  output_array[0, 8] = 0
  output_array[0, 13] = 0
  
  output_array[1, 0] = 1
  output_array[1, 1] = 0
  output_array[1, 6] = 0
  output_array[1, 7] = 0
  output_array[1, 8] = 1
  output_array[1, 13] = 0
  
  p_buffer = np.zeros((1, 1), dtype=np.float64)
  p_cal = np.array([[-1.000001, -1.000001, -1.000001, -1.000001, -1.000001], [5, 5, 5, 5, 5], [50, 50, 50, 50, 50]], dtype=float)
  
  p_fluctuation = 0.004
  
  run_thread = False
  recording = True
  daq_time = time.time()
  
  d_symbol = '\u00b0'
  settings = ('', 0, 2, 0, 20000, 0, 0, 2, 0, 400, 0, 0, 2, 0, 100000, -100000)
  color_list = [(0, 0, 0), (255, 0, 0), (0, 0, 255), (0, 255, 0), (0, 255, 255), (255, 0, 255), (255, 100, 0), (255, 0, 150), (138, 29, 29), (17, 12, 168)]
  
  # Pressure Inputs DAQ Thread
  class PressureThread(QtCore.QThread):
      p_array = pyqtSignal(np.ndarray)
  
      def __init__(self, parent=None):
          QtCore.QThread.__init__(self, parent)
  
      def run(self):
          global run_thread, p_buffer, p_cal, p_fluctuation, recording
  
          while run_thread:
              if recording:
                  p_buffer = np.random.uniform(low=0.995, high=1.01, size=(5, 10))
                  # Average buffer
                  p_mean_array = np.mean(p_buffer, axis=1)
                  # Add x to negative x1
                  add_array = np.add(p_mean_array, p_cal[0:1])
                  # Multiply by y2 divided by x2 minus x1
                  mult_array = np.multiply(add_array, 4)
                  inter_array = np.add(p_cal[0:1], p_cal[1:2])
                  comp_array = np.divide(mult_array, inter_array)
                  # Create and combine arrays of metric and imperial 0-10k and 0-5k transducers
                  twenty_k_psi_array = comp_array * 20000
                  twenty_k_mpa_array = comp_array * 20000 * 6.89476
                  five_k_psi_array = comp_array * 5000
                  five_k_mpa_array = comp_array * 5000 * 6.89476
                  p_full_array = np.stack((twenty_k_psi_array, twenty_k_mpa_array, five_k_psi_array, five_k_mpa_array))
                  self.p_array.emit(p_full_array)
                  time.sleep(0.1)
              else:
                  pass
  
      def stop(self):
          self.exit()
  
  
  # Input Calculation Thread
  class SlideRuleThread(QtCore.QThread):
  
      tree_out = pyqtSignal(str)
      arrays = pyqtSlot(np.ndarray, name='arrays')
  
      def __init__(self, parent=None):
          QtCore.QThread.__init__(self, parent)
  
          global run_thread, recording, daq_time, webster, output_array
          run_thread = True
  
          try:
              self.thread1 = PressureThread()
              self.thread1.start()
              self.thread1.p_array.connect(self.worker)
  
          except Exception as e:
              print(e)
  
      def run(self):
  
          print('Running')
  
      def worker(self, arrays):
          global recording, daq_time, webster, output_array
  
          shape = arrays.shape
          current_time = time.time()
          elapsed_time = round(float(current_time - daq_time), 4)
      
          for row in output_array:
              ref_row = row[0]
              if ref_row != -1:
                  # Values shift left
                  row[2] = row[4]
                  row[3] = row[5]
                  row[4] = elapsed_time  # New value to x2
  
              if shape == (4, 1, 5):
                  if row[1] == 0:  # Pressure array
                      if row[6] == 0:  # Pressure
                          value = arrays[int(row[7]), 0, int(row[8])]  # New y value
                          row[5] = round(value, 2)  # New value to y2
                          
                      elif row[6] == 2:  # Pressure load
                          value = (arrays[int(row[7]), 0, int(row[8])] * row[9]) + (arrays[int(row[10]), 0, int(row[11])] * row[12])  # New calculation
                          row[5] = round(value, 2)  # New value to y2
                          
          sign = ''
          self.tree_out.emit(sign)
  
      def stop(self):
          self.thread1.exit()
          self.exit()
  
  
  # Plot Element Builder
  class MultiLine(pg.QtWidgets.QGraphicsPathItem):
      def __init__(self, x, y, p):
          global color_list
          &quot;&quot;&quot;x and y are 2D arrays of shape (Nplots, Nsamples)&quot;&quot;&quot;
      
          x_array = np.array(x)
          y_array = np.array(y)
      
          self.path = pg.arrayToQPath(x_array.flatten(), y_array.flatten())  # self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten())
          pg.QtWidgets.QGraphicsPathItem.__init__(self, self.path)
          self.setPen(pg.mkPen(color_list[p], width=1))
      def shape(self): # override because QGraphicsPathItem.shape is too expensive.
          return pg.QtWidgets.QGraphicsItem.shape(self)
      def boundingRect(self):
          return self.path.boundingRect()
  
  
  # Main GUI Window
  class MyWindow(QMainWindow):
  
      # Initial Inputs and Variables
      def __init__(self):
          super(MyWindow, self).__init__()
          self.setGeometry(0, 0, 1920, 1080)
          self.initUI()
          self.showMaximized()
          self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
  
          global run_thread, recording, daq_online
  
          self.graph = False
  
          self.right_left_axis = pg.AxisItem('right')
          self.right_right_axis = pg.AxisItem('right')
          self.right_left_vb = pg.ViewBox()
          self.right_right_vb = pg.ViewBox()
          self.main_plot_view = pg.GraphicsView()
          self.main_plot_view_layout = pg.GraphicsLayout()
  
          # GUI Items
      def initUI(self):
  
          # TOP LEFT FRAME
          fra_data = QFrame(self)
          fra_data.setGeometry(QtCore.QRect(20, 30, 350, 210))
          fra_data.setFrameShape(QFrame.Panel)
          fra_data.setFrameShadow(QFrame.Raised)
          fra_data.lineWidth()
          fra_data.midLineWidth()
          fra_data.setLineWidth(5)
  
          self.list_cha = QTreeWidget(fra_data)
          self.list_cha.setColumnCount(3)
          self.list_cha.setHeaderLabels(['Channel', 'Data', ''])
          self.list_cha.setGeometry(10, 10, 330, 190)
          self.list_cha.setColumnWidth(0, 180)
          self.list_cha.setColumnWidth(1, 100)
          self.list_cha.setColumnWidth(2, 40)
          self.list_cha.addTopLevelItem(QTreeWidgetItem(0))
          self.list_cha.topLevelItem(0).setText(0, 'Pressure1')
          self.list_cha.topLevelItem(0).setText(1, 'Stopped')
          self.list_cha.topLevelItem(0).setText(2, 'psi')
          self.list_cha.addTopLevelItem(QTreeWidgetItem(1))
          self.list_cha.topLevelItem(1).setText(0, 'Pressure2')
          self.list_cha.topLevelItem(1).setText(1, 'Stopped')
          self.list_cha.topLevelItem(1).setText(2, 'psi')
  
          # Tabs
          fra_tabs = QFrame(self)
          fra_tabs.setGeometry(QtCore.QRect(400, 30, 1500, 950))
          fra_tabs.lineWidth()
          fra_tabs.midLineWidth()
          fra_tabs.setLineWidth(5)
  
          tabs = QTabWidget(fra_tabs)
  
          daq_tab = QWidget()
  
          tabs.resize(1500, 950)
          tabs.addTab(daq_tab, 'DAQ')
  
          # DAQ Tab
          self.fra_plot = QFrame(daq_tab)
          self.fra_plot.setFrameShape(QFrame.StyledPanel)
          self.fra_plot.setGeometry(10, 40, 1480, 860)
  
          self.mpW = pg.PlotWidget(self.fra_plot, axisItems={'bottom': pg.DateAxisItem(utcOffset=0)})
      
          self.mpW.move(5, 5)
          self.mpW.setBackground('w')
          self.mpW.resize(1460, 850)
  
          self.main_axis = self.mpW.plotItem
  
          self.graph = True
  
          # Plot Settings
          self.main_axis.showGrid(x=True, y=True, alpha=1)
          self.main_axis.setLabel('bottom', '(hh:mm:ss)')
          self.main_axis.setTitle('Just DAQ Profiler', color='b', size='20pt')
  
          # Axis Labels and Units
          self.main_axis.getAxis('left').setLabel('Pressure (psi)', color='#0000ff')
  
          # Axis Ranges
          self.main_axis.vb.setYRange(0, 100, padding=0)
  
          # Adjustable Horizontal Reference Lines
          self.main_axis.vb.addItem(pg.InfiniteLine(pos=100 - ((100 - 0) * 0.1), angle=0, pen=(0, 255, 0),
                                                  movable=True))
          self.main_axis.vb.addItem(pg.InfiniteLine(pos=0 + ((100 - 0) * 0.1), angle=0, pen=(0, 255, 0),
                                                  movable=True))
  
          self.mpW.show()
  
          self.thread3 = SlideRuleThread()
          self.thread3.start()
          self.thread3.tree_out.connect(self.treeUpdater)
  
      # Updates Values in Channel List Tree
      def treeUpdater(self, sign):
          
          global output_array
  
          for row in output_array:
              tree_row = int(row[0])
              if tree_row != -1:
                  value = str(row[5])
                  self.list_cha.topLevelItem(tree_row).setText(1, value)
              
                  time_one = float(row[4])
                  time_two = float(row[2])
                  list_time = [time_two, time_one]
                  value_one = float(row[5])
                  value_two = float(row[3])
                  list_values = [value_two, value_one]
              
                  lines = MultiLine(list_time, list_values, tree_row)
                  self.main_axis.addItem(lines)
  
  
  def main():
  
      app = QApplication(sys.argv)
      win = MyWindow()
      sys.exit(app.exec_())
  
  if __name__ == '__main__':
      main()
  </code></pre>
  <p>The last time I commented out &quot;self.main_axis.addItem(lines)&quot;, the code ran for 5 hours and was still perfectly responsive. But, if I uncomment it, it runs for about 4 to 5 minutes before it slowly dies and then crashes. Which is weird, because the PyQtGraph examples I've tried plotted 100,000 data points in 0.01 seconds, and in 5 minutes with 2 channels updating at 10 Hz, this is only 6,000 data points.</p>
  <p>I'm new to Python, this is my first program, so I'm sure there's lots of things I'm doing incorrectly, or could be improved. But, through all my searching and program tweaking over the last couple months, I can't find any other examples that are similar or explain what is going on. I'm hoping someone here can point me in the right direction. Thanks.</p>
  <p>System: Windows 10 Pro 64bit, 16 GB RAM, Intel Core i7 @ 2.30GHz
  Visual Studio Code: 1.71.0
  Python: 3.10.6
  PyQtGraph: 0.12.4
  Numpy: 1.23.2</p>