读取线程中的高频数据并使用Tkinter实时绘制图形

问题描述 投票:1回答:1

[在过去的几周中,我一直在尝试制作一个可以从OpenBCI Cyton(@ 250Hz)读取EEG数据并实时绘制图形的应用程序。在这里看起来更好的是线程。我应用了我发现的技巧here 1与Tkinter进行线程通信,但是该应用程序仍然无法正常工作(给我错误RecursionError: maximum recursion depth exceeded while calling a Python object)。也许是因为我尝试使用多个.py文件而做错了什么?请参见下面的代码主要部分以及上下文中的其他注释:

###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 = [],[]

正如我发现的here 2,一个函数不应调用自身,但基本上是here 1所做的。另外,我不认为我会像在root.mainloop()中一样多次呼叫here 3

执行后,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

[这显然意味着线程已成功执行,但是plotSamples()崩溃了。

任何建议?

python multithreading matplotlib tkinter real-time
1个回答
0
投票

after()(类似于按钮的command=bind())需要不带()和不带参数的函数名-称为callback-并且after之后将其发送给mainloopmainloop使用()运行它。

()一起使用功能

 app.root.after(60, plotSamples(flag,app,kounter))

因此它立即运行(它不会将其发送到mainloop),并且此函数再次运行与同一函数同时运行的相同函数,依此类推-如此,您就创建了递归。

它的工作原理

 result = plotSamples(flag,app,kounter) # run at once
 app.root.after(60, result)

如果必须将函数与参数一起使用,则可以这样做

 app.root.after(60, plotSamples, flag, app, kounter)

最终,您可以使用lambda创建不带参数的函数

 app.root.after(60, lambda:plotSamples(flag,app,kounter) )
© www.soinside.com 2019 - 2024. All rights reserved.