[英]Reading higher frequency data in thread and plotting graph in real-time with Tkinter
In the last couple of weeks, I've been trying to make an application that can read EEG data from OpenBCI Cyton (@250Hz) and plot a graph in 'real-time'.在过去的几周里,我一直在尝试制作一个可以“实时”读取来自 OpenBCI Cyton (@250Hz) 和 plot 的 EEG 数据的应用程序。 What seems to work better here are threads.
在这里似乎工作得更好的是线程。 I applied the tips I found here 1 to communicate the thread with Tkinter, but the application still doesn't work (gives me the error
RecursionError: maximum recursion depth exceeded while calling a Python object
).我应用了我在此处找到的提示 1与 Tkinter 通信线程,但应用程序仍然无法工作(给我错误
RecursionError: maximum recursion depth exceeded while calling a Python object
)。 Maybe I'm doing something wrong because I'm trying to use multiple .py
files?也许我做错了什么,因为我试图使用多个
.py
文件? See below the main parts of my code and a few more comments in context:请参阅下面我的代码的主要部分以及上下文中的更多注释:
###FILE main.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from AppWindow import *
window = AppWindow()
window.start()
###FILE AppWindow.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import scroller as scrl
import logging
import requests
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import random
from pandas import DataFrame
import stream_lsl_eeg as leeg
#Definitions
H = 720
W = 1280
#Color palette>> https://www.color-hex.com/color-palette/92077
bg_color = "#c4ac93"
sc_color = "#bba58e"
tx_color = "#313843"
dt_color = "#987f62"
wn_color = "#6b553b"
class AppWindow:
#Other Functions
def plotGraph(self, x, y):
self.ax.clear()
self.ax.plot(x,y, color = tx_color)
plt.subplots_adjust(bottom=0.31, left=0.136, top=0.9, right=0.99)
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.root.wm_title("Hybrid BCI - SSVEP and Eye Tracker")
#Other Graphical Elements
#Button that calls function
self.btn_ReceiveEEG = tk.Button(self.EEG_frame, text = "Receive EEG signal", bg = bg_color, fg = tx_color, state = tk.DISABLED, command = lambda: leeg.getEEGstream(self))
self.btn_ReceiveEEG.place(anchor = 'nw', relx = 0.52, rely = 0.5, width = 196, height = 40)
#Other Graphical Elements
def start(self):
self.root.mainloop() #end of application
### FILE stream_lsl_eeg.py
from pylsl import StreamInlet, resolve_stream
import tkinter as tk
import AppWindow as app
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import threading
import queue
import time
class myThread(threading.Thread):
def __init__(self, name, q, f):
threading.Thread.__init__(self)
self.name = name
self.q = q
self.f = f
def run(self):
print("Starting ", self.name)
pullSamples(self.q, self.f) #place where function is called
def getInlet(app): #this is triggered by another button and it's working fine
global inlet
app.logger.warn('Looking for an EEG strean...')
streams = resolve_stream('type', 'EEG')
inlet = StreamInlet(streams[0])
app.logger.warn('Connected')
app.btn_ReceiveEEG.config(state = tk.NORMAL)
def pullSamples(q):
i = 0
while i<1000:
sample, timestamp = inlet.pull_sample()
threadLock.acquire() #thread locks to put info in the queue
q.put([sample,timestamp]) #data is put in the queue for other threads to access
threadLock.release() #thread unlocks after info is in
i += 1
stopcollecting = 1
print("Exit flag on")
def plotSamples(app, kounter): #Outside Thread
if not stopcollecting: #testing if stream reception stopped
while dataqueue.qsize( ):
try:
kounter += 1
sample, timestamp = dataqueue.get(0)
samples.append(sample[0]) #getting just channel 1 (0)
timestamps.append(timestamp)
show_samples = samples[-250:]
show_timestamps = timestamps[-250:]
app.plotGraph(show_timestamps,show_samples)
print(counter) #this was just a control to count if the right amount of samples was coming out of the queue
except dataqueue.Empty:
pass #still not implemented, but will return to the main application
app.root.after(60, plotSamples(flag,app,kounter)) #60 chosen because plot should update every 15 samples (15/250 = 0,06s)
def getEEGstream(app): #function called by button
app.logger.warn('Starting thread...')
#
kounter = 0
start = time.perf_counter()
thread1.start()
##
plotSamples(flag, app, kounter)
##
thread1.join() #I don't know if I need this...
finish = time.perf_counter()
#
print(f'Sizes: Samples [{len(samples)}, {len(samples[0])}], {len(timestamps)} timestamps')
print(f'Sucessfully streamed in {round(finish-start,3)}s!')
###
threadLock = threading.Lock()
dataqueue = queue.Queue()
stopcollecting = 0
kounter = []
flag = queue.Queue() #secondary queue for flags not used at the moment
flag.put(0)
thread1 = myThread("Thread-1", dataqueue,flag)
samples,timestamps = [],[]
show_samples, show_timestamps = [],[]
As I found here 2 , a function should not call itself, but it's basically what here 1 does.正如我在这里发现的 2 , function 不应该调用自己,但它基本上是这里 1所做的。 Also, I don't think I'm calling
root.mainloop()
multiple times like done in here 3 .另外,我认为我不会像这里 3那样多次调用
root.mainloop()
。
After executing, python gives me the following error/output:执行后,python 给我以下错误/输出:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\robotics\AppData\Local\Continuum\anaconda3\envs\psychopy\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\AppWindow.py", line 109, in <lambda>
self.btn_ReceiveEEG = tk.Button(self.EEG_frame, text = "Receive EEG signal", bg = bg_color, fg = tx_color, state = tk.DISABLED, command = lambda: leeg.getEEGstream(self))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 118, in getEEGstream
plotSamples(flag, app, kounter)
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
[Previous line repeated 986 more times]
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 92, in plotSamples
while dataqueue.qsize( ): # if not dataqueue.empty():
File "C:\Users\robotics\AppData\Local\Continuum\anaconda3\envs\psychopy\lib\queue.py", line 87, in qsize
with self.mutex:
RecursionError: maximum recursion depth exceeded while calling a Python object
Exit flag on
This means the thread is being successfully executed, apparently, but the plotSamples()
is crashing.显然,这意味着线程正在成功执行,但
plotSamples()
正在崩溃。
Any advice??有什么建议吗??
after()
(similar to button's command=
and bind()
) needs function's name without ()
and without argument - it is called callback
- and after
sends it to mainloop
and mainloop
later uses ()
to run it. after()
(类似于按钮的command=
和bind()
)需要不带()
和不带参数的函数名称 - 它被称为callback
- after
将其发送到mainloop
, mainloop
稍后使用()
运行它。
You use function with ()
您将 function 与
()
一起使用
app.root.after(60, plotSamples(flag,app,kounter))
so it runs it at once (it doesn't send it to mainloop
) and this function runs at once again the same function which runs at once the same function, etc. - so you create recursion.所以它立即运行它(它不会将它发送到
mainloop
)并且这个 function 再次运行相同的 function ,它同时运行相同的 ZC1C425268E68385D1AB5074C17A 等创建。
It works like它像
result = plotSamples(flag,app,kounter) # run at once
app.root.after(60, result)
If you have to use function with arguments then you can do如果您必须将 function 与 arguments 一起使用,那么您可以这样做
app.root.after(60, plotSamples, flag, app, kounter)
Eventually you can use lambda
to create function without argument最终你可以使用
lambda
创建 function 没有参数
app.root.after(60, lambda:plotSamples(flag,app,kounter) )
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.