使用 Tkinter 实时读取线程中的高频数据并绘制图形 [英] Reading higher frequency data in thread and plotting graph in real-time with Tkinter

查看:68
本文介绍了使用 Tkinter 实时读取线程中的高频数据并绘制图形的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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'. 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). Maybe I'm doing something wrong because I'm trying to use multiple .py files? 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 = [],[]

我发现 此处 2,函数不应调用自身,但基本上是 这里是 1 .另外,我认为我不会像在 此处 3.

As I found here 2, a function should not call itself, but it's basically what here 1 does. Also, I don't think I'm calling root.mainloop() multiple times like done in here 3.

执行后,python 给了我以下错误/输出:

After executing, python gives me the following error/output:

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() 正在崩溃.

This means the thread is being successfully executed, apparently, but the plotSamples() is crashing.

有什么建议吗??

推荐答案

after()(类似于按钮的 command=bind()>) 需要不带 () 且不带参数的函数名称 - 它被称为 callback - 并且 after 将其发送到 mainloop> 和 mainloop 稍后使用 () 来运行它.

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.

你在()中使用函数

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

所以它一次运行它(它不会将它发送到 mainloop)并且这个函数再次运行相同的函数,它一次运行相同的函数等等 - 所以你创建递归.

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.

它就像

 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

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

最终你可以使用 lambda 来创建没有参数的函数

Eventually you can use lambda to create function without argument

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

这篇关于使用 Tkinter 实时读取线程中的高频数据并绘制图形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆