我正在嘗試在 tkinter GUI 中不斷更新 matlibplots,同時能夠單擊按鈕來暫停/繼續/停止更新繪圖。我嘗試過使用執行緒,但它們似乎沒有并行執行(例如,正在執行資料執行緒但繪圖沒有得到更新 單擊按鈕被忽略)。為什么它不起作用?
# 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:主執行緒不在主回圈中”***
uj5u.com熱心網友回復:
公平地說,您代碼中的所有函式都極有可能導致分段錯誤,而其他不會導致分段錯誤的函式根本不起作用,很難解釋出了什么問題。
- 定義全域變數,就
global好像你要修改它們一樣 window.after通過重復使用該方法更新主執行緒中的 GUI 。- 只有從您的微控制器讀取應該在單獨的執行緒中完成。
- Tkinter 物件的創建應該在主執行緒中完成,只允許在其他執行緒中進行更新,但它不是執行緒安全的,所以雖然它可能作業但有時會導致一些奇怪的行為或錯誤。
- 呼叫 matplotlib 之類的函式
ion會flush_events導致錯誤,因為這些是針對 matplotlib 互動式畫布的,而不是針對 tkinter 畫布的。 - 執行緒有一個非常艱難的學習曲線,所以在你嘗試使用執行緒之前問問自己“我真的需要執行緒嗎”和“有什么方法可以不使用執行緒”,因為一旦你開始使用執行緒,你就不再使用 python 的“安全代碼”,盡管付出了所有努力,但執行緒不能安全地用于任何任務,由您來確保它們的安全,老實說,這里不需要執行緒,除非您從微控制器讀取 1 GB/s。
- 不要在狀態中使用數字,它不是 Pythonic,它會使讀者感到困惑,并且與使用 Enums 相比,它沒有性能優勢。
- 程式是增量構建的,而不是從多個作業片段中復制粘貼,因為當代碼的多個部分沒有被驗證為作業時,更難跟蹤錯誤來自哪里。
# 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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/523273.html
上一篇:如何將implFnTrait物件傳遞給多個執行緒,例如使用執行緒池
下一篇:同一程式中的馬虎計數器和多個鎖
