Search code examples
pythonpyqt5qthread

Acess violation error in PyQt5


I am really new to programming and I recently had to do a GUI with PyQt. Basicly what it does is reading reading some data coming from an Arduino on the serial port and updates a graph every 100 values sent by the Arduino. There is also a log function to log the data into a formatted file. Everything seemed to work fine but now my program stops working and a message saying python stopped working pops up. I tried a few time and the same message pop up at random moments. Could you please help me figure this out?

Mainly from what I read on some other forums and this one, I think the problem is in my PlotThread class which is used by my main window class. I don't know what that problem is though and how to fix it. Here are the Window and PlotThread class.

Thanks,

Simon Bellemare

class Window(QDialog):
  def __init__(self, parent=None):
    super(Window, self).__init__(parent)

    self.log_x = np.array([])
    self.log_y = np.array([])
    self.test_id = ''
    self.plot_thread = PlotThread()
    self.plot_thread.main = self
    self.figure = plt.figure()

    # Canvas Widget
    self.canvas = FigureCanvas(self.figure)

    # Navigation Widget
    self.toolbar = NavigationToolbar(self.canvas, self)

    # Button to control plot function
    self.control_plot = QPushButton('Start')
    self.control_plot.clicked.connect(self.plot)

    # Button to log data
    self.log_data = QPushButton('Log')
    self.log_data.clicked.connect(self.log_file)

    # Progress bar to indicate data harvesting speed
    self.progressBar = QProgressBar(self)
    self.progressBar.setGeometry(QRect(10, 10, 200, 25))
    self.progressBar.move(500, 10)

    # Layout
    layout = QVBoxLayout()
    layout.addWidget(self.toolbar)
    layout.addWidget(self.canvas)
    layout.addWidget(self.control_plot)
    layout.addWidget(self.log_data)
    self.setLayout(layout)

    # Initialising the plot figure
    self.fig1 = self.figure.add_subplot(111)
    rect = self.fig1.patch
    rect.set_facecolor('white')
    self.fig1.set_xlabel('time [s]')
    self.fig1.set_ylabel('RSSI [dBm]')
    self.fig1.grid(True)
    self.fig1.spines["top"].set_visible(False)
    self.fig1.spines["right"].set_visible(False)
    self.fig1.spines["bottom"].set_visible(False)
    self.fig1.spines["left"].set_visible(False)

  def log_file(self):
    directory = ''.join(filter(str.isalpha, self.test_id))

    # Creating the directory if it doesn't exist
    create_directory(directory)

    # Creating the file if it doesn't exist
    if not os.path.isfile(
            'C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
                                                                                         self.test_id)):
        log = open('C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
                                                                                               self.test_id),
                   'w')
        # Creating the header
        log.write(' ' * 9 + 'Time [s]' + ' ' * 9 + '|' + ' ' * 4 + 'RSSI [dBm] \n')
    else:
        log = open('C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
                                                                                               self.test_id),
                   'a')

    # Logging the data
    for i in range(np.size(self.log_x)):
        x = str(self.log_x[i])
        space_num = 24 - len(x)
        log.write(' ' * space_num + x + "  |" + ' ' * 5 + str(self.log_y[i]) + ' \n')

    # Reinitialising logging
    self.log_x = np.array([])
    self.log_y = np.array([])

  def pause(self):
    self.plot_thread.terminate()
    self.control_plot.setText('Start')
    self.control_plot.clicked.connect(self.plot)
    self.log_data.setText('Log')
    self.log_data.clicked.connect(self.log_file)

  def plot(self):
    self.control_plot.setText('Pause')
    self.control_plot.clicked.connect(self.pause)
    self.log_data.setText('')
    self.log_data.clicked.connect(self.wait)

    self.plot_thread.start()

  def wait(self):
    pass

class PlotThread(QThread):
    def __init__(self):
        QThread.__init__(self)
        self.main = ''
        self.t_start = 0
        self.ser = ser

        # Replace by expected values at time 0s
        self.xf = 0
        self.yf = -43

    def run(self):
        while self.isRunning():
            print("Starting to plot")
            self.ser.flushInput()
            self.t_start = time.time()
            # Preallocating memory for the arrays
            x = np.empty([100])
            y = np.empty([100])

            # Initializing start values
            x[0] = self.xf
            y[0] = self.yf
            self.main.progressBar.setValue(1)

            # Reading data from the Arduino
            for i in range(99):
                reading = str(ser.readline())
                while not reading[3:5].isdigit():
                    reading = str(ser.readline())
                y[i + 1] = -int(reading[3:5])
                x[i + 1] = self.xf + (time.time() - self.t_start)
                print(x[i])
                self.main.progressBar.setValue(i + 1)

            # Preparing start values for next iteration
            self.xf = x[99]
            self.yf = y[99]

            # Preparing the log variables
            self.main.log_x = np.append(self.main.log_x, x)
            self.main.log_y = np.append(self.main.log_y, y)

            # Plotting raw data
            self.main.fig1.plot(x, y, 'r--')

            # Filtering raw data
            poly8 = np.polyfit(x, y, 8)
            xGraph = np.linspace(x[0], x[99], num=200)
            yGraph = np.polyval(poly8, xGraph)

            # Graphing filtered data
            self.main.fig1.plot(xGraph, yGraph, 'b')
            # Saving the graph into a file
            timer = self.xf + time.time() - self.t_start
            # Scaling the axes
            if timer > 80:
                print("Scaling the axes")
                self.main.fig1.set_xlim([int(timer) - 80, timer])

            # refresh canvas
            self.main.canvas.draw()
            self.main.canvas.flush_events()

Solution

  • You may not interact with the GUI in a non-main thread:

    As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.