Я пытаюсь постоянно обновлять matlibplots в графическом интерфейсе tkinter, имея возможность нажимать кнопки, чтобы приостановить/продолжить/остановить обновление графиков. Я пытался использовать потоки, но они, похоже, не выполняются параллельно (например, поток данных выполняется, но графики не обновляются + нажатие кнопок игнорируется). Почему это не работает?
# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
# global variables
state = 1 # 0 starting state; 1 streaming; 2 pause; -1 end and save
x = [0]*12
y = [0]*12
# Thread buttons and plots separately
def threading():
state = 1
t_buttons = Thread(target = buttons)
t_plots = Thread(target = plots)
t_data = Thread(target = data)
t_buttons.start()
t_plots.start()
t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
while(state!=-1):
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
# create buttons
def stream_clicked():
state = 1
print("clicked")
def pause_clicked():
state = 2
print("state")
def finish_clicked():
state = -1
def buttons():
continue_button = tk.Button(window, width = 30, text = "Stream data" ,
fg = "black", bg = '#98FB98', command = stream_clicked)
continue_button.place(x = window.winfo_screenwidth()*0.2, y = 0)
pause_button = tk.Button(window, width = 30, text = "Pause streaming data" ,
fg = "black", bg = '#FFA000', command = pause_clicked)
pause_button.place(x = window.winfo_screenwidth()*0.4, y = 0)
finish_button = tk.Button(window, width = 30, text = "End session and save",
fg = 'black', bg = '#FF4500', command = finish_clicked())
finish_button.place(x = window.winfo_screenwidth()*0.6, y = 0)
def plots():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state==1:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT of all 12 channels", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
def main_plot():
plt.ion()
fig1, axs1 = plt.subplots(12, figsize = (10, 9), sharex = True)
fig1.subplots_adjust(hspace = 0)
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig1, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = 0, y = 35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state==1:
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
axs1[0].set_title("Plot recordings", x = 0.5, y = 1)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
def FFT_plot():
# Plot FFT figure
plt.ion()
fig2, axs2 = plt.subplots(1, figsize = (7, 9))
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig2, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = window.winfo_screenwidth()*0.55, y = 35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background = 'white')
threading()
window.mainloop()
*** Иногда это просто не работает без каких-либо сообщений, а иногда я также получаю «RuntimeError: основной поток не находится в основном цикле» ***
Проверьте расписание tkinter






Честно говоря, все функции в вашем коде, скорее всего, вызовут ошибку сегментации, а другие функции, которые не приводят к ошибке сегментации, просто не работают, трудно объяснить, что не так.
global, если вы собираетесь их изменятьwindow.after.ion и flush_events, вызывает ошибки, потому что они предназначены для интерактивного холста matplotlib, а не для холста tkinter.# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
from enum import Enum,auto
UPDATE_INTERVAL_MS = 300
class States(Enum):
STREAM = auto()
PAUSE = auto()
SAVE = auto()
START = auto()
# global variables
state = States.START # check States Enum
x = [[0]]*12
y = [[0]]*12
# Thread buttons and plots separately
def threading():
global state
global window
state = States.STREAM
buttons()
plots()
data()
t_grab_data = Thread(target=grab_data_loop,daemon=True)
t_grab_data.start()
# t_buttons = Thread(target=buttons)
# t_plots = Thread(target=plots)
# t_data = Thread(target=data)
#
# t_buttons.start()
# t_plots.start()
# t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
global fig1,axs1,fig2,axs2
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
def grab_data_loop():
while state != States.SAVE:
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
time.sleep(0.1) # because we are not reading from a microcontroller
def draw_data_loop():
if state == States.STREAM:
update_main_plot(fig1, axs1)
update_FFT_plot(fig2, axs2)
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
# create buttons
def stream_clicked():
global state
state = States.STREAM
print("clicked")
def pause_clicked():
global state
state = States.PAUSE
print("state")
def finish_clicked():
global state
state = States.SAVE
window.destroy()
def buttons():
continue_button = tk.Button(window, width=30, text = "Stream data",
fg = "black", bg='#98FB98', command=stream_clicked)
continue_button.place(x=window.winfo_screenwidth() * 0.2, y=0)
pause_button = tk.Button(window, width=30, text = "Pause streaming data",
fg = "black", bg='#FFA000', command=pause_clicked)
pause_button.place(x=window.winfo_screenwidth() * 0.4, y=0)
finish_button = tk.Button(window, width=30, text = "End session and save",
fg='black', bg='#FF4500', command=finish_clicked)
finish_button.place(x=window.winfo_screenwidth() * 0.6, y=0)
def plots():
global state
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state == States.STREAM:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
# fig1.canvas.draw()
# fig1.canvas.flush_events()
# for i in range(0, 12):
# axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT of all 12 channels", x=0.5, y=1)
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
def main_plot():
# plt.ion()
global canvas1
fig1, axs1 = plt.subplots(12, figsize=(10, 9), sharex=True)
fig1.subplots_adjust(hspace=0)
# Add fixed values for axis
canvas1 = FigureCanvasTkAgg(fig1, master=window)
# canvas.draw()
canvas1.get_tk_widget().pack()
canvas1.get_tk_widget().place(x=0, y=35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state == States.STREAM:
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
axs1[0].set_title("Plot recordings", x=0.5, y=1)
canvas1.draw()
# fig1.canvas.draw()
# fig1.canvas.flush_events()
def FFT_plot():
# Plot FFT figure
# plt.ion()
global canvas2
fig2, axs2 = plt.subplots(1, figsize=(7, 9))
# Add fixed values for axis
canvas2 = FigureCanvasTkAgg(fig2, master=window)
# canvas.draw()
canvas2.get_tk_widget().pack()
canvas2.get_tk_widget().place(x=window.winfo_screenwidth() * 0.55, y=35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
if state == States.STREAM:
axs2.clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT", x=0.5, y=1)
canvas2.draw()
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background='white')
threading()
window.mainloop()
# put saving logic here
Такое подробное объяснение и решение много значат, большое спасибо, Ахмед!
Привет, вопрос как нуб Python: почему вы использовали многопоточность, но не использовали IPC (межпроцессное взаимодействие) по сравнению с глобальными переменными?
@CustosMortem, использующий многопроцессорность и IPC, требует, чтобы ядра выполняли дополнительную работу по сериализации и десериализации данных между процессами, в этих накладных расходах нет необходимости, особенно когда чтение с микроконтроллера сбрасывает GIL, поэтому вы можете иметь 2 потока, работающих одновременно, это быстрее, потому что им не нужно тратить циклы на IPC. более чистым подходом было бы использование потокобезопасной очереди, для этого не потребуется сериализация, поскольку она находится в одном процессе, и вы не будете читать из глобальной переменной, поэтому она более масштабируема для нескольких устройств одновременно. время.
@CustosMortem прочитайте это, чтобы узнать, когда использовать каждый многопроцессорность против многопоточности против асинхронности в Python 3, единственное, что я бы добавил к этому, это то, что чтение из последовательных портов / USB / dll сбрасывает GIL, поэтому лучше использовать потоки при работе с драйверами.
Пожалуйста, отредактируйте вопрос, чтобы ограничить его конкретной проблемой с достаточной детализацией, чтобы найти адекватный ответ.