First, I am new to Python. I am a long-time MatLab user (engineer, not computer scientist) and I am beginning the process of attempting to work Python, NumPy, SciPy, etc. into my workflow. So, please excuse my obvious ignorance of what is a wonderful programming language!
As my first endeavor, I decided to build an application to interact with a sensor that I am developing. The sensor has microsecond resolution (data from 512 high and 512 low energy "pixels" every 500 microseconds), but the I/O will be blocking. Since I will continually poll the device, I know threading will be important to keep the GUI responsive (the GUI will ultimately also integrate serial communication with another device, and have an image processing subroutine that operates on the sensor data). I created a threaded instance of MatPlotLib to plot these "real-time" data from the sensor. Though I've built the module that communicates with the sensor independently and verified I know how to do that in Python, I am starting here simply with a "simulation" of the data by generating 512 random numbers between 8 and 12 for the low energy "pixels", and 512 random numbers between 90 and 110 for the high energy "pixels". That is what is threaded. Working from many examples here, I also learned to use blitting to get a fast enough screen update with MatPlotLib -- BUT, the problem is that unless I use put the threaded process to sleep for 20ms using time.sleep(0.02), the GUI is unresponsive. This can be verified because the interactive X,Y data point feedback from MatPlotLib doesn't work and the 'STOP' button cannot be used to interrupt the process. Anything longer than time.sleep(0.02) makes the GUI operated even smoother, but at the expense of "data rate". Anything slower than time.sleep(0.02) makes the GUI unresponsive. I'm not sure I understand why. I was going to go off and try to use GUIqwt instead, but thought I would ask here before abandoning MatPlotLib since I'm not sure that is even the problem. I am concerned that putting the thread to sleep for 20ms will mean that I miss at least 40 potential lines of data from the sensor array (40 lines * 500us/line = 20ms).
Here is the current code:
import time, random, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QMainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.thread = Worker()
self.create_main_frame()
self.create_status_bar()
self.connect(self.thread, SIGNAL("finished()"), self.update_UI)
self.connect(self.thread, SIGNAL("terminated()"), self.update_UI)
self.connect(self.startButton, SIGNAL("clicked()"), self.start_acquisition)
self.connect(self.stopButton, SIGNAL("clicked()"), self.stop_acquisition)
self.thread.pixel_list.connect(self.update_figure)
def create_main_frame(self):
self.main_frame = QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QPushButton(self.tr("&Start"))
self.stopButton = QPushButton(self.tr("&Stop"))
layout = QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.thread.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.thread.render()
def stop_acquisition(self):
self.thread.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QThread):
pixel_list = pyqtSignal(list, list)
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def render(self):
self.start()
def run(self):
# simulate I/O
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.02)
n -= 1
def main():
app = QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Perhaps my problem isn't even with MatPlotLib or PyQT4, but the way I implemented threading. As I noted, I am new to this and learning. And, I'm not even sure GUIqwt will address any of these issues -- but I do know I've seen many recommendations here to use something faster than MatPlotLib for "real time" plotting in a GUI. Thanks for the help on this!
[edited because QThread is confusing/confused]
There seems to be two ways to use it, either sub-classing it (as your example and the documentation says) or creating a worker object and then moving it to a thread (See this blog post). I then get more confused when you mix signal/slots in. As Avaris says, this change may not be your problem.
I re-worked your Worker class as a a sub-class of QObject (because this is the style I understand).
A problem is that if you do not put a sleep in your fake data system, then you generate all the call backs to the main window in < 1s. The main thread is then essentially blocked as it clears out the signal queue. If you set the delay to your specified delay, (0.0005s), then it cranks through generating the data far faster than it can be displayed, which seems to suggest that this may not be suitable for your problem (being pragmatic, you also can't see that fast, and it seems to do ok at 30 - 40 fps).
import time, random, sys
#from PySide.QtCore import *
#from PySide.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.create_status_bar()
self.startButton.clicked.connect(self.start_acquisition)
self.stopButton.clicked.connect(self.stop_acquisition)
self.worker.pixel_list.connect(self.update_figure)
self.worker.done.connect(self.update_UI)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QtGui.QPushButton(self.tr("&Start"))
self.stopButton = QtGui.QPushButton(self.tr("&Stop"))
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QtGui.QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.worker.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
def stop_acquisition(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QtCore.QObject):
pixel_list = QtCore.pyqtSignal(list, list)
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
#QtCore.pyqtSlot()
def get_data(self):
# simulate I/O
print 'data_start'
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.05)
n -= 1
print 'n: ', n
self.done.emit()
Related
This question already has answers here:
Equivalent to time.sleep for a PyQt application
(5 answers)
Closed 1 year ago.
I trying create GUI Api. First i build python script with only print information in console.
So I wanted to rebuild applications into applications with an interface. I decided to use PyQt5
Like this:
To(first look):
I ran into a problem with the loop While. Aplication just freeze when while is runing
I prepared a short script simulating the problem. The main program looks different
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
from termcolor import colored
import time
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'API NORD'
self.left = 0
self.top = 0
self.width = 300
self.height = 200
self.setWindowTitle(self.title)
self.resize(800, 600)
self.center()
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
def center(self):
# geometry of the main window
qr = self.frameGeometry()
# center point of screen
cp = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qr.moveCenter(cp)
# top left of rectangle becomes top left of window centering it
self.move(qr.topLeft())
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.pushButton1 = QPushButton("Run")
self.layout.addWidget(self.pushButton1)
self.pushButton1.clicked.connect(self.button2_clicked)
self.textedit = QtWidgets.QTextEdit(readOnly=True)
self.layout.addWidget(self.textedit)
self.textedit.setText("STATUS")
def onClicked(self):
radioButton = self.sender()
if radioButton.isChecked():
x=0
# print("Shop is %s" % (radioButton.shop))
self.Sklep=radioButton.shop
self.l1.setText(self.Sklep)
return
def checkBulkStatus(self):
Status = "Start"
x=0
self.textedit.setText("Start")
while x < 5:
print("Aktualny Status:", colored(Status,"yellow"))
Status="Running"
self.textedit.append(Status)
if Status=="FAILED":
print("Error")
break
time.sleep(2.5)
x+=1
print("Aktualny Status: ", colored("COMPLETED", "green"))
self.textedit.setText("COMPLETED")
def button2_clicked(self):
self.checkBulkStatus()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
In main program I ussing while to check status of BULK request in GraphQL:
def checkBulkStatus(self):
self.url = self.auth(self.Sklep)["url_auth"]
print(self.url)
Status = "Start"
self.textedit.setText("Start")
while Status != "COMPLETED":
print("Aktualny Status:", colored(Status,"yellow"))
checking = self.Core.callShopifyGraphQL(self.Core.CheckQuery,self.url)
result = checking.json()
Status=result["data"]["currentBulkOperation"]["status"]
self.textedit.append(Status)
if Status=="FAILED":
print(result["data"]["currentBulkOperation"])
break
time.sleep(2.5)
print("Aktualny Status: ", colored("COMPLETED", "green"))
URL_bulk=result["data"]["currentBulkOperation"]["url"]
The problem is that the gui runs in the same thread as the script, so when you run the script it freezes the interface. To prevent this from happening, you need to run the script in a thread, as this way you can share variables with the main thread.
I hope it helps you, greetings.
I'm new to GUI-programming and need help with a QThread application.
I designed a GUI-Programm which records a Signal from the microphone and plots it in a Figure at the same time.
Now I want to evaluate the signal in another thread, so it still records and plots in the GUI.
The streaming and plotting works fine but everytime I start the thread the GUI freezes and then exits.
Does somebody know what I did wrong in my code, I don't have that much Programming-experience?
# Imports ----------------------------
import sys
import time
import numpy as np
import pyaudio
from PyQt5 import QtGui, QtWidgets, QtCore
import matplotlib
from matplotlib.mlab import find
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
matplotlib.use('Qt5Agg')
class Window(QtWidgets.QMainWindow):
def __init__(self): # template for rest of GUI,
super(Window, self).__init__()
self.setGeometry(50, 50, 1500, 900)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.channels = 2 # StereoSignal
self.fs = 44100 # Samplingrate
self.Chunks = 4096 # Buffersize
self.streamstart = False
self.audiodata = [] # to buffer streaming-values in
self.tapeLength = 4 # seconds
self.tape = np.empty(self.fs * self.tapeLength) * np.nan # tape to store signal-chunks
self.home()
def home(self):
btn = QtWidgets.QPushButton("Stream and Plot", self) # Button to start streaming
btn.clicked.connect(self.plot)
btn.move(100, 100)
btn = QtWidgets.QPushButton("Stop", self) # Button to stop streaming
btn.clicked.connect(self.stop_signal)
btn.move(200, 100)
btn = QtWidgets.QPushButton("Evaluate", self) # Button for the Evaluation
btn.clicked.connect(self.evaluation)
btn.move(100, 140)
self.textEdit = QtWidgets.QTextEdit(self) # Show text of evaluation
self.textEdit.move(250, 170)
self.textEdit.resize(200, 200)
self.scrollArea = QtWidgets.QScrollArea(self) # Scroll-Area to plot signal (Figure) in
self.scrollArea.move(75, 400)
self.scrollArea.resize(600, 300)
self.scrollArea.setWidgetResizable(False)
self.figure = Figure((15, 2.8), dpi=100) # figure instance (to plot on) F(width, height, ...)
self.canvas = FigureCanvas(self.figure)
self.scrollArea.setWidget(self.canvas)
self.gs = gridspec.GridSpec(1, 1)
self.ax = self.figure.add_subplot(self.gs[0])
self.figure.subplots_adjust(left=0.05)
def start_stream(self, start=True):
"""start a Signal-Stream with pyAudio, with callback (to also play immediately)"""
if start is True:
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input=True,
output=True, frames_per_buffer=self.Chunks, stream_callback=self.callback)
self.streamstart = True
self.stream.start_stream()
print("Recording...")
def callback(self, in_data, frame_count, time_info, flag):
"""Callback-Function which stores the streaming data in a list"""
data = np.fromstring(np.array(in_data).flatten(), dtype=np.float32)
self.audiodata = data
print("appending...")
return data, pyaudio.paContinue
def tape_add(self):
"""add chunks from (callback)-list to tapes for left and right Signalparts"""
if self.streamstart:
self.tape[:-self.Chunks] = self.tape[self.Chunks:]
self.taper = self.tape # tape for right signal
self.tapel = self.tape # tape for left signal
self.tapel[-self.Chunks:] = self.audiodata[::2]
self.taper[-self.Chunks:] = self.audiodata[1::2]
print("taping...")
else:
print("No streaming values found")
def plot(self):
"""Start the streaming an plot the signal"""
print("(Stereo-)Signal streaming & plotting...")
if self.streamstart:
pass
else:
self.start_stream(start=True)
self.t1 = time.time()
time.sleep(0.5)
while self.streamstart:
QtWidgets.QApplication.processEvents() # does this still work with threads?
print("Plotting...")
self.tape_add()
self.timeArray = np.arange(self.taper.size)
self.timeArray = (self.timeArray / self.fs) * 1000 # scale to milliseconds
self.ax.clear()
self.ax.plot(self.timeArray, (self.taper / np.max(np.abs(self.taper))), '-b')
self.ax.grid()
self.ax.set_ylabel("Amplitude")
self.ax.set_xlabel("Samples")
self.canvas.draw()
def stop_signal(self):
print("Stopping Signal.")
if self.streamstart:
print("Stop Recording")
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
self.streamstart = False
else:
pass
def evaluation(self):
""" Start the evaluation in another Thread"""
threader = WorkerThread(self.taper, self.tapel)
thread = QtCore.QThread()
# threader.threadDone.connect(self.thread_done) # doesn't work yet
thread.started.connect(threader.run)
thread.start() # start thread
class WorkerThread(QtCore.QObject):
def __init__(self, taper, tapel): # take the tape-parts from the original thread
# super().__init__() # do I need this or next?
QtCore.QThread.__init__(self)
self.__taper = taper
self.__tapel = tapel
def run(self):
"""Do evaluation, later mor, for now just some calculations"""
print("Evaluating Signal")
self.tpr = self.__taper.astype(np.float32, order='C') / 32768 # here the GUI freezes and then exits
self.tpl = self.__tapel.astype(np.float32, order='C') / 32768
# cut nan-values if there are some
self.r = self.tpr[~np.isnan(self.tpr)]
self.l = self.tpl[~np.isnan(self.tpl)]
# normalize signals
self.left2 = (self.l / np.max(np.abs(self.l)))
self.right2 = (self.r / np.max(np.abs(self.r)))
self.norm_audio2 = np.array((self.left2, self.right2)) # like channels (in de_interlace)
# do some calculations
self.databew = """ Mute, Loudness and PSNR/MOS...
Dominant fundamental frequencies etc.
"""
print(self.databew)
# self.textEdit.append(self.databew) # would this work?
# self.threadDone.emit('Thread-Bewertung Done.') # later implemented
def main():
app = QtWidgets.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
main()
So the streaming parts work, maybe someone can tell me what's wrong with the Threading-Part, where I want to do some simple calculations with the recorded signal?
The thread doesn't work with the Signal still recording, but also not, when I stop the recording and plotting and have the Signal in a buffer.
I'm sorry I couldn't get a simpler programm working with similar values, where the same problem occurs.
Hope someone can still help me?
thx, Julia
After a little trying out different things I found a sulition. So the Problem was indeed the QApplication.ProcessEvents-part. This is for completing loops in PyQt, but mine is a endless loop, only stopped after a button-klick. This is why the GUI froze everytime I used it.
The solution now, was to put the plotting part also in a new Thread, which can access the GUI-window.
Here is the new code, which works fine and reasonable fast:
# Imports ----------------------------
import sys
import time
import numpy as np
import pyaudio
from PyQt5 import QtGui, QtWidgets, QtCore
import matplotlib
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
matplotlib.use('Qt5Agg')
class Window(QtWidgets.QMainWindow):
def __init__(self): # template for rest of GUI,
super(Window, self).__init__()
self.setGeometry(50, 50, 1500, 900)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.channels = 2 # StereoSignal
self.fs = 44100 # Samplingrate
self.Chunks = 4096 # Buffersize
self.streamstart = False
self.audiodata = [] # to buffer streaming-values in
self.tapeLength = 4 # seconds
self.tape = np.empty(self.fs * self.tapeLength) * np.nan # tape to store signal-chunks
self.home()
def home(self):
btn = QtWidgets.QPushButton("Stream and Plot", self) # Button to start streaming
btn.clicked.connect(self.plot)
btn.move(100, 100)
btn = QtWidgets.QPushButton("Stop", self) # Button to stop streaming
btn.clicked.connect(self.stop_signal)
btn.move(200, 100)
btn = QtWidgets.QPushButton("Evaluate", self) # Button for the Evaluation
btn.clicked.connect(self.evaluation)
btn.move(100, 140)
self.textEdit = QtWidgets.QTextEdit(self) # Show text of evaluation
self.textEdit.move(250, 170)
self.textEdit.resize(200, 200)
self.scrollArea = QtWidgets.QScrollArea(self) # Scroll-Area to plot signal (Figure) in
self.scrollArea.move(75, 400)
self.scrollArea.resize(600, 300)
self.scrollArea.setWidgetResizable(False)
self.figure = Figure((15, 2.8), dpi=100) # figure instance (to plot on) F(width, height, ...)
self.canvas = FigureCanvas(self.figure)
self.scrollArea.setWidget(self.canvas)
self.gs = gridspec.GridSpec(1, 1)
self.ax = self.figure.add_subplot(self.gs[0])
self.figure.subplots_adjust(left=0.05)
def start_stream(self, start=True):
"""start a Signal-Stream with pyAudio, with callback (to also play immediately)"""
if start is True:
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input=True,
output=True, frames_per_buffer=self.Chunks, stream_callback=self.callback)
self.streamstart = True
self.stream.start_stream()
print("Recording...")
def callback(self, in_data, frame_count, time_info, flag):
"""Callback-Function which stores the streaming data in a list"""
data = np.fromstring(np.array(in_data).flatten(), dtype=np.float32)
self.audiodata = data
print("appending...")
return data, pyaudio.paContinue
def tape_add(self):
"""add chunks from (callback)-list to tapes for left and right Signalparts"""
if self.streamstart:
self.tape[:-self.Chunks] = self.tape[self.Chunks:]
self.taper = self.tape # tape for right signal
self.tapel = self.tape # tape for left signal
self.tapel[-self.Chunks:] = self.audiodata[::2]
self.taper[-self.Chunks:] = self.audiodata[1::2]
print("taping...")
else:
print("No streaming values found")
def plot(self):
"""Start the streaming an plot the signal"""
print("(Stereo-)Signal streaming & plotting...")
self.plot_thread = PlotThead(self)
self.plot_thread.start()
def stop_signal(self):
print("Stopping Signal.")
if self.streamstart:
print("Stop Recording")
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
self.streamstart = False
self.plot_thread.stop()
else:
pass
def evaluation(self):
""" Start the evaluation in another Thread"""
self.thread = WorkerThread(self, self.taper, self.tapel)
self.thread.start() # start thread
class PlotThead(QtCore.QThread):
def __init__(self, window):
QtCore.QThread.__init__(self)
self.deamon = True
self.__is_running = True
self.window = window
def stop(self):
self.__is_running = False
def run(self):
if self.window.streamstart:
pass
else:
self.window.start_stream(start=True)
self.window.t1 = time.time()
time.sleep(0.5)
while self.window.streamstart and self.__is_running:
print("Plotting...")
self.window.tape_add()
self.window.timeArray = np.arange(self.window.taper.size)
self.window.timeArray = (self.window.timeArray / self.window.fs) * 1000 # scale to milliseconds
self.window.ax.clear()
self.window.ax.plot(self.window.timeArray, (self.window.taper / np.max(np.abs(self.window.taper))), '-b')
self.window.ax.grid()
self.window.ax.set_ylabel("Amplitude")
self.window.ax.set_xlabel("Samples")
self.window.canvas.draw()
class WorkerThread(QtCore.QThread):
def __init__(self, window, taper, tapel): # take the tape-parts from the original thread
QtCore.QThread.__init__(self)
self.__taper = taper
self.__tapel = tapel
self.deamon = True
self.window = window
def run(self):
"""Do evaluation, later mor, for now just some calculations"""
print("Evaluating Signal")
self.tpr = self.__taper.astype(np.float32, order='C') / 32768 # here the GUI freezes and then exits
self.tpl = self.__tapel.astype(np.float32, order='C') / 32768
# cut nan-values if there are some
self.r = self.tpr[~np.isnan(self.tpr)]
self.l = self.tpl[~np.isnan(self.tpl)]
# normalize signals
self.left2 = (self.l / np.max(np.abs(self.l)))
self.right2 = (self.r / np.max(np.abs(self.r)))
self.norm_audio2 = np.array((self.left2, self.right2)) # like channels (in de_interlace)
# do some calculations
self.databew = """ Mute, Loudness and PSNR/MOS...
Dominant fundamental frequencies etc.
"""
print(self.databew)
self.window.textEdit.append(self.databew) # would this work?
def main():
app = QtWidgets.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
main()
I used the "scrolling graph" example from pyqtgraph.examples as template to create a plot, which shows a continuous sinus wave. Now, I would like to use buttons to change the amplitude of the sinus wave. This is why I used another structure (see the code below). This doesn't work, it only plots a static sinus wave.
How can I use my update function to plot the continuous sinus wave? How can I chage the amplitude by using the buttons? I have already checked this and this without success.
import sys
from PyQt4 import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
class sinus_wave(QtGui.QWidget):
def __init__(self):
super(sinus_wave, self).__init__()
self.initUI()
def initPlot(self, plots):
a = 10
ptr1 = 30
data1 = a*np.sin(np.linspace(0,30,121))
plots.plot(data1)
timer = pg.QtCore.QTimer()
timer.timeout.connect(lambda: self.update(self,p1 = plots,data1= data1, ptr1 = ptr1))
timer.start(50)
def initUI(self):
IncreaseButton = QtGui.QPushButton("Increase Amplitude")
DecreaseButton = QtGui.QPushButton("Decrease Amplitude")
p1 = pg.PlotWidget()
hbox = QtGui.QVBoxLayout()
hbox.addWidget(p1)
hbox.addWidget(IncreaseButton)
hbox.addWidget(DecreaseButton)
self.initPlot(p1)
self.setLayout(hbox)
self.setGeometry(10, 10, 1000, 600)
self.setWindowTitle('Sinuswave')
self.show()
def update(self, p1, data1, ptr1):
data1[:-1] = data1[1:]
data1[-1] = np.sin(ptr1/4)
p1.plot(data1)
ptr1 += 1
p1.show()
def IncreaseButtonClick(self):
print ("Amplitude increased")
def DecreaseButtonClick(self):
print ("Amplitude decreased")
def main():
app = QtGui.QApplication(sys.argv)
app.setApplicationName('Sinuswave')
ex = sinus_wave()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
So this is not really a pyqtgraph question. Mostly you need to read up on classes in python and on pyqt basics. But I will try to give you some quick fixes for your problems, and hopefully you will learn something on the way.
For making your buttons work you need to connect them to the methods. Try looking at the answer here: https://stackoverflow.com/a/8763339/4328381
You would need something like this
def qt_connections(self):
self.increasebutton.clicked.connect(self.on_increasebutton_clicked)
self.decreasebutton.clicked.connect(self.on_decreasebutton_clicked)
Then to make the buttons actually do something they need to change your amplitude. First store the amplitude as an attribute for the instance. Also store a t attribute to later make it move.
Your a and ptr1 are just variables inside the method that will be cleared up once the method finishes. By using self. they become instance attributes that can be used from the other methods in the class.
def __init__(self):
...
self.amplitude = 10
self.t = 0
...
Then you can change the amplitude in the method connected to the button.
def on_increasebutton_clicked(self):
print ("Amplitude increased")
self.amplitude += 1
self.updateplot()
Then to make it continous you first need to make sure that the timer works. Try adding a print("test") in it and you will see that it doesn't.You need to keep a reference of it, otherwise it will be cleaned up.
self.timer = pg.QtCore.QTimer()
self.timer.timeout.connect(self.moveplot)
self.timer.start(50)
To make the sine move continuously you need to move it in the method connected to the timer, you can simple create a moveplot method.
def moveplot(self):
self.t+=1
self.updateplot()
And then to put it together create and updateplot method which uses the attributes created earlier.
def updateplot(self):
print "Update"
data1 = self.amplitude*np.sin(np.linspace(0,30,121)+self.t)
self.plotcurve.setData(data1)
In the end you will get something like this
import sys
from PyQt4 import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
class sinus_wave(QtGui.QWidget):
def __init__(self):
super(sinus_wave, self).__init__()
self.init_ui()
self.qt_connections()
self.plotcurve = pg.PlotCurveItem()
self.plotwidget.addItem(self.plotcurve)
self.amplitude = 10
self.t = 0
self.updateplot()
self.timer = pg.QtCore.QTimer()
self.timer.timeout.connect(self.moveplot)
self.timer.start(500)
def init_ui(self):
self.setWindowTitle('Sinuswave')
hbox = QtGui.QVBoxLayout()
self.setLayout(hbox)
self.plotwidget = pg.PlotWidget()
hbox.addWidget(self.plotwidget)
self.increasebutton = QtGui.QPushButton("Increase Amplitude")
self.decreasebutton = QtGui.QPushButton("Decrease Amplitude")
hbox.addWidget(self.increasebutton)
hbox.addWidget(self.decreasebutton)
self.setGeometry(10, 10, 1000, 600)
self.show()
def qt_connections(self):
self.increasebutton.clicked.connect(self.on_increasebutton_clicked)
self.decreasebutton.clicked.connect(self.on_decreasebutton_clicked)
def moveplot(self):
self.t+=1
self.updateplot()
def updateplot(self):
print "Update"
data1 = self.amplitude*np.sin(np.linspace(0,30,121)+self.t)
self.plotcurve.setData(data1)
def on_increasebutton_clicked(self):
print ("Amplitude increased")
self.amplitude += 1
self.updateplot()
def on_decreasebutton_clicked(self):
print ("Amplitude decreased")
self.amplitude -= 1
self.updateplot()
def main():
app = QtGui.QApplication(sys.argv)
app.setApplicationName('Sinuswave')
ex = sinus_wave()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Me and my colleagues are writing a data processing application in python.
We are currently working on the frontend part of the application.
We have a big problem though, that's that the application gets the following error after a random amount of time:
QWidget::repaint: Recursive repaint detected
This one also pops up from time to time:
QPainter::begin: Paint device returned engine == 0, type: 1
This is the file where all gui related stuff happens, I cut out the irrelevant methods for the sake of not being to lengthy:
gfx.py:
import sys, random, math
from PyQt4 import QtGui, QtCore
from random import randrange
from eventbased import listener
app = QtGui.QApplication(sys.argv)
def exec():
return app.exec_()
class MapView(QtGui.QMainWindow, listener.Listener):
def __init__(self, mapimagepath = 0, nodes = 0):
QtGui.QMainWindow.__init__(self)
listener.Listener.__init__(self)
self.setWindowTitle('Population mapping')
self.map = Map(self, mapimagepath)
self.setCentralWidget(self.map)
self.map.start()
self.center()
def center(self):
screen = QtGui.QDesktopWidget().screenGeometry()
size = self.geometry()
self.move(50, 0)
def handle(self, event):
if(event.type == 0):
self.map.addNode(event.object.scanner)
if(event.type == 1):
self.map.delNode(event.object.scanner)
if(event.type == 2):
self.map.addBranch(event.object.node1.scanner, event.object.node2.scanner)
if(event.type == 3):
self.map.delBranch(event.object.node1.scanner, event.object.node2.scanner)
if(event.type == 4):
self.map.changeNode(event.object.scanner.sensorid, event.result)
if(event.type == 5):
self.map.changeBranch(event.object.node1.scanner.sensorid, event.object.node2.scanner.sensorid, event.result)
self.repaint(self.map.contentsRect())
self.update(self.map.contentsRect())
######################################################################
class Map(QtGui.QFrame):
def __init__(self, parent, mapimagepath):
QtGui.QFrame.__init__(self, parent)
#self.timer = QtCore.QBasicTimer()
#coordinaten hoeken NE en SW voor kaart in map graphics van SKO
self.realmap = RealMap(
mapimagepath,
(51.0442, 3.7268),
(51.0405, 3.7242),
550,
800)
parent.setGeometry(0,0,self.realmap.width, self.realmap.height)
self.refreshspeed = 5000
self.mapNodes = {}
def addNode(self, scanner):
coord = self.realmap.convertLatLon2Pix((scanner.latitude, scanner.longitude))
self.mapNodes[scanner.sensorid] = MapNode(scanner, coord[0], coord[1])
# type: 4 --> changenode ,
#((change, gem_ref, procentuele verandering ref), scanner object)
def changeNode(self, sensorid, branchdata):
self.mapNodes[sensorid].calcDanger(branchdata[2])
def paintEvent(self, event):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken nodes
for sensorid, mapNode in self.mapNodes.items():
mapNode.drawMapNode(painter, self.realmap)
######################################################################
class RealMap:
def __init__(self, path, coordRightTop,
coordLeftBot, width, height, pixpermet = 2.6):
self.path = path
self.coordLeftBot = coordLeftBot
self.coordRightTop = coordRightTop
self.width = width
self.height = height
self.realdim = self.calcRealDim()
self.pixpermet = pixpermet
def drawRealMap(self, painter):
image = QtGui.QImage(self.path)
painter.drawImage(0,0,image)
######################################################################
class MapNode:
dangertocolor = {"normal":"graphics//gradients//green.png",
"elevated":"graphics//gradients//orange.png",
"danger":"graphics//gradients//red.png"}
def __init__(self, scanner, x, y, danger = 0):
self.scanner = scanner
self.x = x
self.y = y
self.danger = 'normal'
self.calcDanger(danger)
def drawMapNode(self, painter, realmap):
radiusm = self.scanner.range
radiusp = radiusm*realmap.pixpermet
factor = radiusp/200 # basis grootte gradiƫnten is 200 pixels.
icon = QtGui.QImage("graphics//BT-icon.png")
grad = QtGui.QImage(MapNode.dangertocolor[self.danger])
grad = grad.scaled(grad.size().width()*factor, grad.size().height()*factor)
painter.drawImage(self.x-100*factor,self.y-100*factor, grad)
painter.drawImage(self.x-10, self.y-10,icon)
painter.drawText(self.x-15, self.y+20, str(self.scanner.sensorid) + '-' + str(self.scanner.name))
An object is made through our application class:
mapview = gfx.MapView(g_image)
mapview.show()
So the first question is. What are we doing wrong in the paintEvent method?
Secondly question
Is there a way to make the paintevent not be called at EVERY RANDOM THING that happens ? (like mouseovers, etc)?
I tried something like:
def paintEvent(self, event):
if(isinstance(event, QtGui.QPaintEvent)):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken nodes
for sensorid, mapNode in self.mapNodes.items():
mapNode.drawMapNode(painter, self.realmap)
else:
pass
This 'works' but is to general I guess.. It actually makes the error appear a lot faster then without the conditional.
When in your gfx.py you have:
self.repaint(self.map.contentsRect())
self.update(self.map.contentsRect())
Calling repaint and calling update one right after another is redundant. And if a paint event comes through that handler and you call repaint() there, you are asking for infinite recursion.
Take note of any Warnings or Notes in the documentation.
http://doc.qt.io/qt-4.8/qwidget.html#update
http://doc.qt.io/qt-4.8/qwidget.html#repaint
http://doc.qt.io/qt-4.8/qwidget.html#paintEvent
I don't see the cause for your other error right off, but it probably has to do with QPainter getting used when it shouldn't...
http://doc.qt.io/qt-4.8/qpainter.html#begin
http://doc.qt.io/qt-4.8/qpainter.html#details
Hope that helps.
EDIT #3: tcaswell has solved some problems in my original problem, but now I appear to be referencing the same instance of an object when I should have several in parallel.
(see comment section of tcaswell's answer)
ORIGINAL PROBLEM:
I was wondering if anyone had any insight to a problem I am having with getting a user created object passed to a GUI so that the GUI will refresh itself and not move to 'Not Responding'. I know this is fairly common issue, and I have read through several forums trying to understand QThreads, signals, slots, mutiprocessing, etc. and am still having trouble. I am now avoiding getting a grayed out window, but now my program simply does nothing when I want it to start several large processes in the background.
My project needs to have several tabs operating in their own process, but with each tab having it's own data to show in a matplotlib plot. I have a couple of buttons that should initiate the processing of the data and show the changes in the matplotlib plot. A lot of the ideas of how to organize the thresds came from this thread.
Here is the function which is initiated after the button is pressed:
# This appears to be where the problem lies because this should initialize all of the processes
def process_tabs(self):
for special_object in self.special_objects_list:
thread = QtCore.QThread(parent=self)
worker = Worker(special_object)
worker.moveToThread(thread)
worker.signal.connect(self.update_GUI)
thread.start()
return
The worker should be creating a whole bunch of signals in a loop that send objects to update the GUI. Here is the worker class I have made:
# This class performs the iterative computation that needs to update the GUI
# the signals it send would *ideally* be special_obect objects so any of the parameters can be shown
class Worker(QtCore.QObject):
signal = QtCore.pyqtSignal(QtCore.QObject)
done = QtCore.pyqtSignal()
def __init__(self, special_object):
QtCore.QObject.__init__(self)
self.special_object = special_object
#QtCore.pyqtSlot()
def process_on_special_object(self):
# do a long fitting process involving the properties of the special_object
for i in range(0,99999999999999999):
self.special_object.Y += .1
self.signal.emit(self.special_object)
self.done.emit()
return
Thank you for any help with this, it is much appreciated.
EDIT:
I have re-written the code to follow tcaswell's schema and changed the python slot decorators to pass the special_objects to the update_GUI slot.
EDIT AGAIN: I added a time.sleep(0.03) so the GUI will remain responsive.
Here is the new code in full form:
import multiprocessing as mp
from PyQt4 import QtGui, QtCore
import numpy as np
import matplotlib
matplotlib.use('QtAgg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import figure
import sys
import lmfit
import time
# This object will just hold certain objects which will help create data objects ato be shown in matplotlib plots
# this could be a type of species with properties that could be quantized to a location on an axis (like number of teeth)
#, which special_object would hold another quantization of that property (like length of teeth)
class object_within_special_object:
def __init__(self, n, m):
self.n = n
self.m = m
def location(self, i):
location = i*self.m/self.n
return location
def NM(self):
return str(self.n) + str(self.m)
# This is what will hold a number of species and all of their properties,
# as well as some data to try and fit using the species and their properties
# I made this inherit QObject becuase I figured it *may* be more acceptable to send as a signal if the class inherited a PyQt4 class
class special_object(QtCore.QObject):
def __init__(self, name, X, Y):
QtCore.QObject.__init__(self)
self.name = name
self.X = X
self.Y = Y
self.params = lmfit.Parameters()
self.things = self.make_a_whole_bunch_of_things()
for thing in self.things:
self.params.add('something' + str(thing.NM()) + 's', value = 3)
def make_a_whole_bunch_of_things(self):
things = []
for n in range(0,20):
m=1
things.append(object_within_special_object(n,m))
return things
# a special type of tab which holds a (or a couple of) matplotlib plots and a special_object ( which holds the data to display in those plots)
class Special_Tab(QtGui.QTabWidget):
start_comp = QtCore.pyqtSignal()
def __init__(self, parent, tmp_so):
QtGui.QTabWidget.__init__(self, parent)
self.special_object = tmp_so
self.grid = QtGui.QGridLayout(self)
# matplotlib figure put into tab
self.fig = figure.Figure()
self.plot = self.fig.add_subplot(111)
self.line, = self.plot.plot(0, 0, 'r-')
self.canvas = FigureCanvas(self.fig)
self.grid.addWidget(self.canvas)
self.canvas.show()
self.canvas.draw()
self.canvas_BBox = self.plot.figure.canvas.copy_from_bbox(self.plot.bbox)
self.ax1 = self.plot.figure.axes[0]
thread = QtCore.QThread(parent=self)
self.worker = Worker(self.special_object)
self.worker.moveToThread(thread)
self.worker.update_signal.connect(self.update_GUI)
# self.worker.done_signal.connect(?)
self.start_comp.connect(self.worker.process_on_special_object)
thread.start()
#QtCore.pyqtSlot(special_object)
def update_GUI(self, tmp_so):
"""
have the tab update it's self
"""
# change the GUI to reflect changes made to special_object
self.line.set_data(tmp_so.X, tmp_so.Y)
self.ax1.set_xlim(tmp_so.X.min(), tmp_so.X.max())
self.ax1.set_ylim(0, tmp_so.Y.max() + 0.05*tmp_so.Y.max())
self.plot.draw_artist(self.line)
self.plot.figure.canvas.blit(self.plot.bbox)
def start_computation(self):
self.start_comp.emit()
# This class performs the iterative computation that needs to update the GUI
# the signals it send would *ideally* be special_obect objects so any of the parameters can be shown
class Worker(QtCore.QObject):
update_signal = QtCore.pyqtSignal(QtCore.QObject)
done_signal = QtCore.pyqtSignal()
def __init__(self, tmp_so):
QtCore.QObject.__init__(self)
self.tmp_so = tmp_so
#QtCore.pyqtSlot()
def process_on_special_object(self):
# do a long fitting process involving the properties of the special_object
for i in range(0,999):
self.tmp_so.Y += .1
time.sleep(0.03)
self.update_signal.emit(self.tmp_so)
self.done_signal.emit()
return
# This window just has a button to make all of the tabs in separate processes
class MainWindow(QtGui.QMainWindow):
process_signal = QtCore.pyqtSignal()
def __init__(self, parent = None):
# This GUI stuff shouldn't be too important
QtGui.QMainWindow.__init__(self)
self.resize(int(app.desktop().screenGeometry().width()*.6), int(app.desktop().screenGeometry().height()*.6))
self.tabs_list = []
self.special_objects_list = []
central_widget = QtGui.QWidget(self)
self.main_tab_widget = QtGui.QTabWidget()
self.layout = QtGui.QHBoxLayout(central_widget)
self.layout.addWidget(self.main_tab_widget)
button = QtGui.QPushButton('Open Tabs')
self.layout.addWidget(button)
QtCore.QObject.connect(button, QtCore.SIGNAL("clicked()"), self.open_tabs)
button2 = QtGui.QPushButton('process Tabs')
self.layout.addWidget(button2)
QtCore.QObject.connect(button2, QtCore.SIGNAL("clicked()"), self.process_tabs)
self.setCentralWidget(central_widget)
central_widget.setLayout(self.layout)
# Here we open several tabs and put them in different processes
def open_tabs(self):
for i in range(0, 10):
# this is just some random data for the objects
X = np.arange(1240.0/1350.0, 1240./200., 0.01)
Y = np.array(np.e**.2*X + np.sin(10*X)+np.cos(4*X))
# Here the special tab is created
temp_special_object = special_object(str(i), X, Y)
new_tab = Special_Tab(self.main_tab_widget, temp_special_object)
self.main_tab_widget.addTab(new_tab, str(i))
# this part works fine without the .start() function
self.tabs_list.append(new_tab)
return
# This appears to be where the problem lies because this should initialize all of the processes
def process_tabs(self):
for tab in self.tabs_list:
tab.start_computation()
return
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
First, you never actually call process_on_special_object, so the computation never gets run.
Second, I think you are not understanding signals and slots properly. Think of them as pipes with check valves. A Signal is the open end of a pipe that stuff can only flow out of and a Slot is on open end that stuff can only flow into. When you connect Signal to Slot you are patching the output to the input. When you call emit in a Signal you are pushing stuff down the pipe. You can connect a single Signal to multiple Slots and multiple Signals to a single Slot.
Third, be careful not to shadow your class names with variable names (you seem to use special_object as both)
Forth, you create a zillion threads and workers, but don't do anything with them. I would suggest a major design change. You should think of the Special_Tab class as fully self contained, encapsulating the graph, the worker, and it's thread:
class Special_Tab(QtGui.QTabWidget):
start_comp = QtCore.pyqtSignal()
kill_thread = QtCore.pyqtSignal()
def __init__(self, parent, tmp_so):
QtGui.QTabWidget.__init__(self, parent)
self.special_object = tmp_so
self.grid = QtGui.QGridLayout(self)
# matplotlib figure put into tab
self.fig = figure.Figure()
self.plot = self.fig.add_subplot(111)
self.line, = self.plot.plot(0, 0, 'r-')
self.canvas = FigureCanvas(self.fig)
self.grid.addWidget(self.canvas)
self.canvas.show()
self.canvas.draw()
self.canvas_BBox = self.plot.figure.canvas.copy_from_bbox(self.plot.bbox)
ax1 = self.plot.figure.axes[0]
thread = QtCore.QThread(parent=self)
self.worker = Worker(self.special_object)
self.worker.moveToThread(thread)
self.worker.update_signal.connect(self.update_GUI)
# self.worker.done_signal.connect(?)
self.start_comp.connect(self.worker.process_on_special_object)
self.kill_thread.connect(thread.quit)
thread.start()
#QtCore.pyqtSlot(special_object)
def update_GUI(self, tmp_so):
"""
have the tab update it's self
"""
# change the GUI to reflect changes made to special_object
self.line.set_data(tmp_so.X, tmp_so.Y)
self.plot.draw_artist(self.line)
self.plot.figure.canvas.blit(self.plot.bbox)
def start_computation(self):
self.start_comp.emit()
def closeEvent(self, ce):
self.kill_thread.emit()
QtGui.QTabWidget.closeEvent(self, ce)
With the related changes to Worker
# This class performs the iterative computation that needs to update the GUI
# the signals it send would *ideally* be special_obect objects so any of the parameters can be shown
class Worker(QtCore.QObject):
update_signal = QtCore.pyqtSignal(QtCore.QObject)
done_signal = QtCore.pyqtSignal()
def __init__(self, special_object):
QtCore.QObject.__init__(self)
self.special_object = special_object
#QtCore.pyqtSlot()
def process_on_special_object(self):
# do a long fitting process involving the properties of the special_object
for i in range(0,99999999999999999):
self.special_object.Y += .1
self.signal.emit(self.special_object)
self.done.emit()
return
which makes your main window simpler
# This window just has a button to make all of the tabs in separate processes
class MainWindow(QtGui.QMainWindow):
process_signal = QtCore.pyqtSignal()
def __init__(self, parent = None):
## snipped
pass
# Here we open several tabs and put them in different processes
def open_tabs(self):
for i in range(0, 10):
# this is just some random data for the objects
X = np.arange(1240.0/1350.0, 1240./200., 0.01)
Y = np.array(np.e**.2*X + np.sin(10*X)+np.cos(4*X))
# Here the special tab is created
temp_special_object = special_object(str(i), X, Y)
new_tab = Special_Tab(self.main_tab_widget, temp_special_object)
self.main_tab_widget.addTab(new_tab, str(i))
# this part works fine without the .start() function
self.tabs_list.append(new_tab)
return
# This appears to be where the problem lies because this should initialize all of the processes
def process_tabs(self):
for tab in self.tabs_list:
tab.start_computation()
return
# This should be the 'slot' which the signal is communicating to
def update_GUI(self, special_object):
pass