1

this question has been asked many times but to be honest I don't quite understand why do I get this error "RuntimeError: main thread is not in main loop". I have the following code to read data from Serial and draw some graph with it. My problem is that my program is working if I do not try to draw in real time the data (so if I only keep take_measure() inside the plotter function). But if I add the part for the graph drawing as shown bellow my code is returning this mainloop error. What is happening ? I know I should use the after command but for me after command was a total disaster (a lots of bugs). So do you know what is the cause of my error and how to solve it/how to implement the after method ?

Sorry for my nasty code....

from tkinter import * 
from random import randint 
import numpy as np
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure 
import time 
import threading
import serial
from tkinter.filedialog import asksaveasfile
import csv
from skimage.restoration import (denoise_tv_chambolle, denoise_bilateral, denoise_wavelet, estimate_sigma)

continuePlotting = False 
 
def change_state(): 
    global continuePlotting
    global serial_port
    global i
    global b_start
    global mid
    global label
    if continuePlotting == True: 
        continuePlotting = False
        serial_port.close()
        i = 2
        label.pack_forget()
    else: 
        continuePlotting = True
        label.pack(in_=mid)

t, p1 = [], []
i = 0

def take_measure():
    global t
    global p1
    global t_temp
    global p1_temp
    global i
    global serial_port
    if i == 0:
        serial_port =serial.Serial('COM5', 2000000)
        serial_port.setDTR(False)
        time.sleep(0.1)
        serial_port.setDTR(True)
        serial_port.flushInput()
        p1_temp = []
        t_temp = []
        i = 1
    elif i == 2:
        serial_port =serial.Serial('COM5', 2000000)
        serial_port.setDTR(False)
        time.sleep(0.1)
        serial_port.setDTR(True)
        serial_port.flushInput()
        p1_temp = []
        t_temp = []
        i = 1
    try:
        temp=serial_port.readline()[:-2].decode()
        a = temp.index(";")
        t_val = float(temp[:a])
        p1_val = (float(temp[a+1:])-2640)*20/3520
        t.append(t_val)
        p1.append(p1_val)
        t_temp.append(t_val)
        p1_temp.append(p1_val)
    except:
        pass

def app():
    global t_temp
    global p1_temp
    global i
    global serial_port
    global t
    global p1
    global b_start
    global mid
    global label

root = Tk() 
root.config(background='white') 
root.geometry("1000x700")

top = Frame(root)
bottom = Frame(root)
mid = Frame(root)
top.pack(side="top")
mid.pack(side="top")
bottom.pack(side="bottom", fill="both", expand=True) 
 
fig = Figure() 
 
ax = fig.add_subplot(111) 
ax.set_xlabel("X axis") 
ax.set_ylabel("Y axis") 
ax.grid()

graph = FigureCanvasTkAgg(fig, master=root) 
graph.get_tk_widget().pack(in_=bottom, side="top",fill='both',expand=True)
graph.draw()

toolbar = NavigationToolbar2Tk(graph, root, pack_toolbar=False)
toolbar.update()

def plotter():
    while continuePlotting:
        take_measure()
        ax.clear()
        ax.plot(t_temp, p1_temp)
        graph.draw()

        


def gui_handler(): 
    change_state() 
    t=threading.Thread(target=plotter, daemon=True)
    t.start()

def Save():
    files = [('CSV', '*.csv')]
    file_name = asksaveasfile(filetypes = files, defaultextension = files)
    with open(str(file_name.name),"w", newline='') as file:
        Writer=csv.writer(file)
        Writer.writerow(["temps en ms", "pression en V"]) 
        for elt in range(len(t)):
            Writer.writerow([t[elt], p1[elt]])
    file.close()

def Clear():
    global t
    global p1
    serial_port.close()
    i = 0
    ax.clear()
    graph.draw()
    t = []
    p1 = []

def Draw():
    l_t = [t[0]]
    l_p1 = [p1[0]]
    ax.cla() 
    ax.grid()
    for elt in range(1,len(t)):
        if t[elt] == 0:
            l_p = denoise_wavelet(np.array(l_p1), method="VisuShrink", mode="hard", wavelet_levels=3, wavelet='haar', rescale_sigma='True')
            print("max", max(l_p1))
            print("min", min(l_p1))
            ax.plot(l_t, l_p)
            l_t, l_p1 = [], []
        l_t.append(t[elt])
        l_p1.append(p1[elt])
    l_p = denoise_wavelet(np.array(l_p1), method="VisuShrink", mode="hard", wavelet_levels=3, wavelet='haar', rescale_sigma='True')
    ax.plot(l_t, l_p)
    graph.draw()
    print("max", max(l_p1))
    print("min", min(l_p1))

b_start = Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white") 
b_start.pack(in_=top, side = LEFT) 

button_quit = Button(master=root, text="Quit", command=root.destroy)
button_save = Button(root,text="Save", command=Save)
button_clear = Button(root,text="Clear graph", command=Clear)
button_draw = Button(root,text="Show graphs", command=Draw)

button_draw.pack(in_=top, side = LEFT)
button_clear.pack(in_=top, side = LEFT)
button_save.pack(in_=top, side = LEFT)
button_quit.pack(in_=top, side=LEFT)

label = Label(root, text = "WAIT")

toolbar.pack(in_=bottom, fill=X)

root.mainloop() 


if __name__ == '__main__': 
    app() 

The error is the following:

Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
  File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
Exception ignored in: <function Image.__del__ at 0x000001CCB1098D60>
Traceback (most recent call last):
  File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 4083, in __del__
    self.tk.call('image', 'delete', self.name)
RuntimeError: main thread is not in main loop
Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
  File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
Exception ignored in: <function Variable.__del__ at 0x000001CCB0F3E3E0>
Traceback (most recent call last):
  File "c:\Users\cbroggi\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 410, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: main thread is not in main loop
cbroggi
  • 23
  • 3

1 Answers1

0

Since you cannot update the UI from a thread your graph.draw() method will cause this type of error.

As a solution try using the .after() method:

def update():
    global graph
    graph.draw()
    root.after(1000, update) ##after 1000ms run update()
    ##WARNING: this will require some additoinal code to stop when you exit the code.
def plotter():
    while continuePlotting:
        take_measure()
        ax.clear()
        ax.plot(t_temp, p1_temp)
        #graph.draw() #removed as its in a thread
update() ##triggers the update loop that re-draws the graph every 1000ms
root.mainloop()
Scott Paterson
  • 392
  • 2
  • 17
  • Thanks, unfortunatly I'm still having the same issue with this method. If I understand your explaination, I have to remove everything that is trying to modifiy the main thread (root) while not being part of it. In that case it means that if I call the update function inside the main thread even if it is declared ouside of it it will still execute itself only whre I call it ? – cbroggi Feb 20 '23 at 07:27