Keep GIF animation running while doing calculations - multithreading

I am trying to improve the user experience by showing a load mask above the active QMainWindow/QDialog when performing tasks that takes some time. I have managed to get it working as I want it, except for a moving GIF when performing the task. If I leave the load mask on after the task is complete, the GIF starts moving as it should.
My class for the load mask:
from PyQt4 import QtGui, QtCore
from dlgLoading_view import Ui_dlgLoading
class dlgLoading(QtGui.QDialog, Ui_dlgLoading):
def __init__(self,parent):
QtGui.QDialog.__init__(self,parent)
self.setupUi(self)
self.setWindowFlags(QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint))
self.setGeometry(0, 0, parent.frameGeometry().width(), parent.frameGeometry().height())
self.setStyleSheet("background-color: rgba(255, 255, 255, 100);")
movie = QtGui.QMovie("loader.gif")
self.lblLoader.setMovie(movie)
movie.start()
def showEvent(self, event):
QtGui.qApp.processEvents()
super(dlgLoading, self).showEvent(event)
def setMessage(self,message):
self.lblMessage.setText(message)
The Ui_dlgLoading contains two labels and some vertical spacers: lblLoader (will contain the gif) and lblMessage (will contain a message if needed)
I create the load mask with this code:
loadmask = dlgLoading(self)
loadmask.setMessage('Reading data... Please wait')
loadmask.show()
I figured I needed some multithreading/multiprocessing, but I can't for the life of me figure out how to do it. I read somewhere that you can't tamper with the GUIs threading, so I would need to move the heavy task there instead, but I'm still blank.
As a simple example, let's say I am trying to load a huge file into memory:
file = open(dataFilename, 'r')
self.dataRaw = file.read()
file.close()
Around that I would create and close my load mask dialog. How do I start the file read without halting the GIF animation?
The GUI is for running some heavy external exe files, so it should work with that too.

I ended up doing this:
class runthread(threading.Thread):
def __init__(self, commandline, cwd):
self.stdout = None
self.stderr = None
self.commandline = commandline
self.cwd = cwd
self.finished = False
threading.Thread.__init__(self)
def run(self):
subprocess.call(self.commandline, cwd=self.cwd)
self.finished = True
class command()
def __init__(self):
...
def run():
...
thread = runthread("\"%s\" \"%s\"" % (os.path.join(self.__caller.exefolder, "%s.exe" % self.__cmdtype), self.__name), self.__caller.exeWorkdir)
thread.start()
count = 0
sleeptime = 0.5
maxcount = 60.0/sleeptime
while True:
time.sleep(sleeptime)
QtWidgets.qApp.processEvents()
count += 1
if thread.finished:
break
if count >= maxcount:
results = QtWidgets.QMessageBox.question(self.__caller, "Continue?", "The process is taking longer than expected. Do you want to continue?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if results == QtWidgets.QMessageBox.Yes:
count == 0
else:
QtWidgets.QMessageBox.warning(self.__caller, "Process stopped", "The process was stopped")
return False
It actually doesn't directly answer my question, but it worked for me, so I'm posting the answer if others want to do something similar.
I call a process (in this case Pythons subprocess.call) through a thread and track when the process is actually finished. A continuous loop checks periodically if the process is done and updates the GUI (processEvents - this is what triggers the GIF to update). To avoid an infinite loop I offer the user an option to exit after some time.

Related

Queue and thread from file customize working threads

I am planing to write a python script that reads urls from a file and checks the status code from these urls using requests. To speed up the process my intention is to use multiple threads at the same time.
import threading
import queue
q = queue.Queue()
def CheckUrl():
while True:
project = q.get()
#Do the URL checking here
q.task_done()
threading.Thread(target=CheckUrl, daemon=True).start()
file = open("TextFile.txt", "r")
while True:
next_line = file.readline()
q.put(next_line)
if not next_line:
break;
file.close()
print('project requests sent\n', end='')
q.join()
print('projects completed')
My problem. Now the code is reading all the text at once making as many threads as there are lines in the text file if I understand correctly. I i would like to do something like read 20 lines at the same time, check status code from the 20 urls, if one or more checks are done go to the next.
is there something like
threading.Thread(target=CheckUrl, daemon=True, THREADSATSAMETIME=20).start()
Seems i have to stick with this one
def threads_run():
for i in range(20): #create 20 threads
(i) = threading.Thread(target=CheckUrl, daemon=True).start()
threads_run()

pyqt threading, appropriate pattern?

I working on a gui application which plots real time data as it comes in. There is a main class which is a plot window. The window shows the plot of the data itself as well as a number of buttons, checkboxes, textboxes, etc. that a user can interact with to modify the plot output. All of these widgets configure the "settings" for the plot. The plotting of new data itself is resource intensive. The loading and plotting of the data may take around a second. I want the user to be able to modify the settings in the GUI without worrying about the lag of loading and plotting so I want to execute the loading and plotting in a separate thread.
I've come up with, what seems to me, to be a weird way to accomplish this but which works. It is something like this:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
class PlotWorker(QtCore.QObject):
def __init__(self, plotwindow):
self.plotwindow = plotwindow
self.plot_thread = QThread()
self.plot_thread.start()
self.moveToThread(self.plot_thread)
def worker_update:
self.plotwindow.updating = True
self.plotwindow.plot()
self.plotwindow.updating = False
class PlotWindow(Ui_PlotWindow, QtWidgets.QMainWindow):
update_signal = pyqtSignal()
def __init__(self):
self.setupUi(self)
self.settings = self.settings_lineEdit.text()
self.worker = PlotWorker(self)
self.update_signal.connect(self.worker.worker_update)
self.update_settings_pushButton.clicked.connect(self.update_settings)
self.updating = False
self.refresh_timer = QtCore.QTimer(self)
self.refresh_timer.timeout.connect(self.update)
self.update_settings()
self.plot()
self.refresh_time.start(1)
def update_setting(self):
self.settings = self.settings_lineEdit.text()
def update(self)
if self.updating:
print('Already updating, cannot update until previous update complete')
elif not self.updating:
self.update_signal.emit()
def plot(self):
# Plot the data, slow. Uses self.settings
self.load()
...
def load(self):
# Load the data from the appropriate directory/file, slow
...
This gets the rough gist of what I am going for. Ui_PlotWindow implements setupUi. In practices there self.settings stands for a long list of settings variables which can be manipulated by the user. The check on self.updating ensures that requests to update the plot which arrive while the plot is currently updating are terminated rather than added to a queue in the worker thread event loop.
Though my code works, I feel like the pattern I am using with the worker thread is a bit strange. Basically ALL of the information needed is in the PlotWindow class, but since I want to call one of the methods of the PlotWindow class in a separate thread I feel I need a different QObject which can live in a different QThread to house the slot which will run the expensive method.
It just feels a bit roundabout to have a whole seperate class and object just to call a function which already exists in the first class.. However, I am new to threading applications and perhaps this is a normal pattern and not to be worried about?
I would appreciate any advice for how I might be able to make the flow of this function more clear.
The answer is to use a different pattern for threading. Instead of the self.thread = and self.moveToThread for the worker the worker should simply BE a QThread. The thread can be "started" in which case its run method is initiated in a new thread. If a request is made in the main thread to "start" the worker thread again while it is already running nothing will happen. Below is a revision of the above code which implements this pattern.
WARNING: This code is still a bad idea because the plotting involving redrawing GUI objects (the plots). This should not happen in a thread different than the main thread. For posterity I'll state that code in the format in the original question did in fact run but I think that was by luck that the plotting worked in the worker thread. The best pattern would be to have any data loading and processing happen in the worker thread but to then finally emit the data to a plotting method of the main thread to update the GUI with the new data.
class PlotWorker(QtCore.QThread):
def __init__(self, plotwindow):
self.plotwindow = plotwindow
def run(self):
self.plotwindow.plot()
class PlotWindow(Ui_PlotWindow, QtWidgets.QMainWindow):
update_signal = pyqtSignal()
def __init__(self):
self.setupUi(self)
self.settings = self.settings_lineEdit.text()
self.worker = PlotWorker(self)
self.update_settings_pushButton.clicked.connect(self.update_settings)
self.refresh_timer = QtCore.QTimer(self)
self.refresh_timer.timeout.connect(self.update)
self.update_settings()
self.plot()
self.refresh_time.start(1)
def update_setting(self):
self.settings = self.settings_lineEdit.text()
def update(self)
if self.worker.started():
print('Already updating, cannot update until previous update complete')
elif not self.worker.started():
self.worker.start()
def plot(self):
# Plot the data, slow. Uses self.settings
self.load()
...
def load(self):
# Load the data from the appropriate directory/file, slow
...

Transfer of data between Python files

Need some help please to explain why the following does not work.
Environment: Python 3.4, Gtk3.0, limited experience of Python
File selectcontact.py contains code to select one of a number of records and pass its key back to its parent process for use in one of at least three other actions.
Code snippet from the parent class:
….
self.cindex = 0
….
def editcontact_clicked (self, menuitem):
import selectcontact
selectcontact.SelectContactGUI(self)
print ('From Manage ', self.cindex)
if self.cindex > 0:
import editcontact
editcontact.EditContactGUI(self.db, self.cindex)
….
Code snippet from selectcontact:
class SelectContactGUI:
def init(self, parent_class):
self.builder = Gtk.Builder()
self.builder.add_from_file(UI_FILE)
self.builder.connect_signals(self)
self.parent_class = parent_class
self.db = parent_class.db
self.cursor = self.db.cursor(cursor_factory = psycopg2.extras.NamedTupleCursor)
self.contact_store = self.builder.get_object('contact_store')
self.window = self.builder.get_object('window1')
self.window.show_all()
def select_contact_path(self, path):
self.builder.get_object('treeview_selection1').select_path(path)
def contact_treerow_changed (self, treeview):
selection = self.builder.get_object('treeview_selection1')
model, path = selection.get_selected()
if path != None:
self.parent_class.cindex = model[path][0]
print ('From select ', self.parent_class.cindex)
self.window.destroy()
….
window1 is declared as “modal”, so I was expecting the call to selectcontact to act as a subroutine, so that editcontact wouldn’t be called until control was passed back to the parent. The parent_class bit works because the contact_store is correctly populated. However the transfer back to the parent appears not to work, and the two print statements occur in the wrong order:
From Manage 0
From select 2
Comments gratefully received.
Graeme
"Modal" refers to windows only. That is, a modal window prevents accessing the parent window.
It has little to do with what code is running. I am not familiar with this particular windowing framework, but any I have worked with has had a separate thread for GUI and at least one for processing, to keep the GUI responsive, and message loops running in all active windows, not just the one currently with the focus. The modal dialog has no control over what code in other threads are executed when.
You should be able to break into the debugger and see what threads are running and what is running in each thread at any given time.

Stopping, restarting and changing variables in a thread from the main program (Python 3.5)

I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.

PyQtGraph: GUI unresponsive during only one process

I'm writing a GUI for a video camera that can basically run in two modes that I call liveview and recordview. The only difference being that I'm recording in the latter and only viewing in the former.
In liveview mode the image gets updated properly. I've set a button that triggers recordview but during this acquisition the GUI gets unresponsive and the image doesn't get updated. Let me show you the relevant parts of the code:
import numpy as np
from PyQt4 import QtGui, QtCore
import pyqtgraph as pg
from lantz.drivers.andor.ccd import CCD
app = QtGui.QApplication([])
def updateview(): # <-- this works OK
global img, andor
img.setImage(andor.most_recent_image16(andor.detector_shape),
autoLevels=False)
def liveview():
""" Image live view when not recording
"""
global andor, img, viewtimer
andor.acquisition_mode = 'Run till abort'
andor.start_acquisition()
viewtimer.start(0)
def UpdateWhileRec():
global stack, andor, img, n, ishape
j = 0
while j < n:
if andor.n_images_acquired > j:
# Data saving <-- this part (and the whole while-loop) works OK
i, j = andor.new_images_index
stack[i - 1:j] = andor.images16(i, j, ishape, 1, n)
# Image updating <-- this doesn't work
img.setImage(stack[j - 1], autoLevels=False)
liveview() # After recording, it goes back to liveview mode
def record(n):
""" Record an n-frames acquisition
"""
global andor, ishape, viewtimer, img, stack, rectimer
andor.acquisition_mode = 'Kinetics'
andor.set_n_kinetics(n)
andor.start_acquisition()
# Stop the QTimer that updates the image with incoming data from the
# 'Run till abort' acquisition mode.
viewtimer.stop()
QtCore.QTimer.singleShot(1, UpdateWhileRec)
if __name__ == '__main__':
with CCD() as andor:
win = QtGui.QWidget()
rec = QtGui.QPushButton('REC')
imagewidget = pg.GraphicsLayoutWidget()
p1 = imagewidget.addPlot()
img = pg.ImageItem()
p1.addItem(img)
layout = QtGui.QGridLayout()
win.setLayout(layout)
layout.addWidget(rec, 2, 0)
layout.addWidget(imagewidget, 1, 2, 3, 1)
win.show()
viewtimer = QtCore.QTimer()
viewtimer.timeout.connect(updateview)
# Record routine
n = 100
newimage = np.zeros(ishape)
stack = np.zeros((n, ishape[0], ishape[1]))
rec.pressed.connect(lambda: record(n))
liveview()
app.exec_()
viewtimer.stop()
As you see UpdateWhileRec runs only once per acquisition while updateview runs until viewtimer.stop() is called.
I'm new to PyQt and PyQtGraph so regardless of the particular way of solving my present issue, there's probably a better way to do everything else. If that's the case please tell me!
thanks in advanced
Your problem stems from the fact that you need to return control to the Qt event loop for it to redraw the picture. Since you remain in the UpdateWhileRec callback while waiting for the next image to be acquired, Qt never gets a chance to draw the image. It only gets the chance once you exit the function UpdateWhileRec.
I would suggest the following changes.
Then instead of your while loop in UpdateWhileRec, have a QTimer that periodically calls the contents of your current while loop (i would probably suggest a singleshot timer). This ensures control will be returned to Qt so it can draw the image before checking for a new one.
So something like:
def UpdateWhileRec():
# Note, j should be initialised to 0 in the record function now
global stack, andor, img, n, j, ishape
if andor.n_images_acquired > j:
# Data saving <-- this part (and the whole while-loop) works OK
i, j = andor.new_images_index
stack[i - 1:j] = andor.images16(i, j, ishape, 1, n)
# Image updating <-- this should now work
img.setImage(stack[j - 1], autoLevels=False)
if j < n:
QTimer.singleShot(0, UpdateWhileRec)
else:
liveview() # After recording, it goes back to liveview mode
Note, you should probably put functions and variables in a class, and create an instance of the class (an object). That way you don't have to call global everywhere and things are more encapsulated.
Ultimately, you may want to look into whether your andor library supports registering a function to be called when a new image is available (a callback) which would save you doing this constant polling and/or acquiring the images in a thread and posting them back to the GUI thread to be drawn. But one step at a time!

Resources