QThreading Issue - PyQt5 GUI - multithreading

I am facing an issue.
So I have a GUI, and because i have to update constantly labels and a polargraph, i used threads. the code works just fine, the labels get updated correctly and so on. the problem comes when i try to update the graph at the same time with the labels. first, i tried to put the plotting code inside the worker, where i got the error "timers cannot be started from another thread", even tho i use no timer at all. after, i tried to put the plotting code inside my main window. there, the variables resulted from the worker function are not seen. meaning that i get the error: "worker has no attribute "ex".
since i am looped between these two errors, i am requesting help. i am attaching the main and worker classes below.
class Worker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)
def cart2pol(self,x, y):
rho = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
return(rho, phi)
def pol2cart(self,rho, phi):
x = rho * np.cos(phi)
y = rho * np.sin(phi)
return(x, y)
#functieon
def run(self):
"""Long-running task."""
for i in range(5):
self.progress.emit(i + 1)
self.finished.emit()
ser = serial.Serial('COM1', 19200, timeout=5.0)
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))
f1 = open('da.txt')
f2 = open('vn.txt')
while 1:
try:
line = sio.readline()
msg = pynmea2.parse(line)
if line.startswith('$WIMWV'):
first.valdirvant.setText(str(msg.wind_angle))
first.valvantapar.setText(str(msg.wind_speed))
da = f1.readline()
vn = f2.readline()
first.valda.setText(da)
first.valvitnav.setText(vn)
xav = []
yav = []
xbv = []
ybv = []
av = []
bv = []
xa = float(vn)
ya = float(da)
xb = -(float(msg.wind_speed))
yb = -(math.radians(float(msg.wind_angle))) #math.radians
a = xa+xb
b = ya+yb
xav.append(xa)
yav.append(ya)
xbv.append(xb)
ybv.append(yb)
av.append(a)
bv.append(b)
#first.graphWidget.plot(xav, yav)
#first.graphWidget.plot(xbv, ybv)
#first.graphWidget.plot(av, bv)
except serial.SerialException as e:
print('Device error: {}'.format(e))
break
except pynmea2.ParseError as e:
print('Parse error: {}'.format(e))
continue
class MainWindow(QDialog):
def __init__(self):
super(MainWindow, self).__init__()
loadUi("meteo.ui", self)
#butoane
self.on.clicked.connect(self.runLongTask)
#customizaregrafic
self.graphWidget.setBackground('white')
# Add polar grid lines
self.graphWidget.addLine(x=0, pen=0.2)
self.graphWidget.addLine(y=0, pen=0.2)
for r in range(2, 20, 2):
circle = pg.QtGui.QGraphicsEllipseItem(-r, -r, r * 2, r * 2)
circle.setPen(pg.mkPen(0.2))
self.graphWidget.addItem(circle)
self.graphWidget.plot(Worker.xav, Worker.yav)
self.graphWidget.plot(QObject.xbv, QObject.ybv)
self.graphWidget.plot(QObject.av, QObject.bv)
def runLongTask(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.start()
as i said earlier, i tried putting the plotting code in the run function, inside the worker class, and i got that timer error, which i understood that is because of the graphic plotting, meaning that i will have to do the plotting inside my main function. and because of that, when i put the plotting code in my main function, it gives me an error because it cannot see the variables calculated in the run function.

Related

I am trying to create a program with a class called Investment, and the object returns Interest rate and Principal

I am just a newbie in Python and trying to write this program. So far, my code looks like this.
class Investment:
def _init_(self,p,i):
self.p = p
self.i = i
def value_after(self,value,n):
value = (1+i) ** n
return p * value
def _str_(self):
return "Principal:"(p),"Investment:"(I),"%"
pr = Investment(98,3)
print(pr.value_after())
I expect to print Principal and Interest rate separately. At this point, I am getting the following Error
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38-32\principalandinterest.py", line 10, in
pr = Investment(98,3)
TypeError: Investment() takes no arguments
{class Investment(object):
def __init__(self,p,i):
self.p = p
self.i = i
def value_after(self,value,n):
value = (1+self.i) ** n
return (self.p * value)
def _str_(self):
return ("Principal:"(self.p),"Investment:"(self.i),"%")}
pr = Investment(98,3)
print(pr.value_after(2,1))
i and p are class attributes and can not be called like this in your methods, they should be called like self.i
Self.p

SOLVED: I get an error when trying to use threading with GTK & Pycairo (to draw a window and signal it from another thread)

SOLUTION
Remove the channel and associated code
Add a new update function inside the window class which takes the new shapes as a parameter
modify the initialisation of the class
call the update function
Modifications for the solution
Apologies, but the diff markdown doesn't seem to be displaying properly, hopefully you should still get an idea of how the solution works
Window class
class Window(Gtk.Window):
- __gsignals__ = {
- 'update_signal': (GObject.SIGNAL_RUN_FIRST, None,
- ())
- }
-
- def do_update_signal(self):
- print("UPDATE SIGNAL CALLED")
- self.shapes = self.shapes_channel.read()
- print("Num new shapes:", len(self.shapes))
- self.show_all()
in the class method init_ui
self.connect("delete-event", Gtk.main_quit)
+ self.show_all()
+ a = self.darea.get_allocation()
+ print (a.x, a.y, a.width, a.height)
+ self.img = cairo.ImageSurface(cairo.Format.RGB24, a.width, a.height)
a new class method update_shapes
+ def update_shapes(self, shapes):
+ self.shapes = shapes
+ cr = cairo.Context(self.img)
+ self.draw_background(cr)
+ for shape in self.shapes:
+ shape.draw(cr)
+ self.darea.queue_draw()
+ return True
Main code
- shapes_channel = Channel()
iter_num = 0
- def optimize(chan, prob, signaller):
+ def optimize(prob, signaller):
def print_iter_num(xk):
global iter_num
iter_num += 1
prob.update_positions(xk)
prob.update_grads(jacobian(xk))
new_shapes = convert_grid(prob.grid, building_size=1.0/GRID_SIZE)
- chan.write(new_shapes)
- signaller.emit("update_signal")
+ GLib.idle_add(signaller.update_shapes, new_shapes)
print("Iteration", iter_num, "complete...")
try:
sol = minimize(objective, x0, bounds = all_bounds, constraints=constraints, options={'maxiter': MAX_ITER, 'disp': True}, callback=print_iter_num, jac=jacobian)
prob.update_positions(sol.x)
except Exception as e:
print("ran into an error", e)
- window = new_window(shapes_channel=shapes_channel)
+ window = new_window()
- x = threading.Thread(target=optimize, args=(shapes_channel, optim_problem, window))
+ x = threading.Thread(target=optimize, args=(optim_problem, window))
x.start()
window.run()
QUESTION
Window class
import cairo
import gi
import math
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
class Line():
def __init__(self, start, end, thickness, colour):
self.start = start
self.end = end
self.thickness = thickness
self.colour = colour
def draw(self, cr):
cr.move_to(*self.start)
cr.line_to(*self.end)
cr.set_source_rgba(*self.colour)
cr.set_line_width(self.thickness)
cr.stroke()
class Polygon():
def __init__(self, points, line_colour, line_thickness, fill_colour=None):
self.points = points # points should be an iterable of points
self.line_colour = line_colour
self.line_thickness = line_thickness
self.fill_colour = fill_colour
def draw(self, cr):
cr.move_to(*self.points[0])
for point in self.points[1:]:
cr.line_to(*point)
cr.close_path()
cr.set_source_rgba(*self.line_colour)
cr.set_line_width(self.line_thickness)
cr.stroke()
if self.fill_colour is not None:
cr.move_to(*self.points[0])
for point in self.points[1:]:
cr.line_to(*point)
cr.close_path()
cr.set_source_rgba(*self.fill_colour)
cr.fill()
class Window(Gtk.Window):
__gsignals__ = {
'update_signal': (GObject.SIGNAL_RUN_FIRST, None,
())
}
def do_update_signal(self):
print("UPDATE SIGNAL CALLED")
self.shapes = self.shapes_channel.read()
print("Num new shapes:", len(self.shapes))
self.show_all()
def __init__(self, shapes_channel, window_size, background_colour=(1, 1, 1, 1), title="GTK window"):
super(Window, self).__init__()
self.width = window_size[0]
self.height = window_size[1]
self.background_colour = background_colour
self.title = title
self.shapes = []
self.shapes_channel = shapes_channel
self.init_ui()
def init_ui(self):
darea = Gtk.DrawingArea()
darea.connect("draw", self.on_draw)
self.add(darea)
self.set_title(self.title)
self.resize(self.width, self.height)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
def draw_background(self, cr: cairo.Context):
cr.scale(self.width, self.height)
cr.rectangle(0, 0, 1, 1) # Rectangle(x0, y0, x1, y1)
cr.set_source_rgba(*self.background_colour)
cr.fill()
def on_draw(self, wid, cr: cairo.Context):
self.draw_background(cr)
for shape in self.shapes:
shape.draw(cr)
def run(self):
Gtk.main()
def new_window(shapes_channel,
window_size=(1000, 1000),
background_colour=(1,1,1,1),
title="3yp"):
return Window(shapes_channel,
window_size=window_size,
background_colour=background_colour,
title=title)
I'm trying to run a window that can draw the shapes I've defined (Lines and Polygons).
It worked fine before when I supplied it a list of shapes and ran it at the end of my application
However, I am trying to add interactivity and have it redraw a list of shapes when the update_signal gets called and a list of new shapes get passed along the shapes_channel that is part of the constructor.
Main Code
Here is the relevant bits from my main code:
shapes_channel = Channel()
iter_num = 0
def optimize(chan, prob, signaller):
def print_iter_num(xk):
global iter_num
iter_num += 1
prob.update_positions(xk)
prob.update_grads(jacobian(xk))
new_shapes = convert_grid(prob.grid, building_size=1.0/GRID_SIZE)
chan.write(new_shapes)
signaller.emit("update_signal")
print("Iteration", iter_num, "complete...")
try:
sol = minimize(objective, x0, bounds = all_bounds, constraints=constraints, options={'maxiter': MAX_ITER, 'disp': True}, callback=print_iter_num, jac=jacobian)
prob.update_positions(sol.x)
except Exception as e:
print("ran into an error", e)
window = new_window(shapes_channel=shapes_channel)
x = threading.Thread(target=optimize, args=(shapes_channel, optim_problem, window))
x.start()
window.run()
As you can see:
A Channel() object is created, named shapes_channel
A new window is created, with the shapes_channel passed into the constructor via the intermediate function new_window.
This window is passed to the other thread so that the other thread
can emit the relevant signal ("update_signal")
The other thread is run
The window is run in the main thread I get the following console output:
UPDATE SIGNAL CALLED
Num new shapes: 31
Gdk-Message: 01:27:14.090: main.py: Fatal IO error 0 (Success) on X server :0.
From the console output, we can infer that the signal is called successfully, and the new shapes are passed to the window and stored correctly, but it fails on the line self.show_all().
This is an object that was working fine previously, and producing graphical output, and I can only think of 2 possible things that may have changed from the objects perspective:
The Channel object works as intended, but perhaps the mere presence of an object that is shared across threads throws the whole thing into disarray
Even though it's on the main thread, it doesn't like that there are other threads.
I would really appreciate some guidance on this maddening occurrence.
About your assumptions:
It is unclear if your channel object is possible to safely access from two threads.
The signal handler is executed in the thread that emits the signal.
My guess would be that it is the fact that you emit the signal from another thread that causes the issue.
You can solve this by using GLib.idle_add(your_update_func). Instead of calling your_update_func directly, a request is added to the Gtk main loop, which executes it when there are no more events to process, preventing any threading issues.
Read more here: https://wiki.gnome.org/Projects/PyGObject/Threading

Multithreading Python turtle with recursion

I made a Python turtle program that recursively generated a fractal tree, but, since it often took a few hours to fully draw, I wanted to try to use multithreading to have multiple turtles working together.
I was able to get two turtles moving at the same time, but, in this much more complex situation, everything seemed to fall apart. I've tried this many different ways, and thought this final solution would be the one, but it just throws a bunch of errors.
Here's my code:
import turtle
import threading
from queue import Queue
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def getx(self):
return self.xpos
def gety(self):
return self.ypos
def geth(self):
return self.heading
class Turtle(turtle.Turtle):
def tolocation(self, location):
self.penup()
self.setx(location.getx())
self.sety(location.gety())
self.setheading(location.geth())
self.pendown()
def get_location(self):
return Location(self.xcor(), self.ycor(), self.heading())
def draw_tree(self, startpos=Location(), size=100):
tm.q.put(self.tolocation(startpos))
for _ in range(size):
tm.q.put(self.forward(1))
for _ in range(45):
tm.q.put(self.right(1))
t2 = Turtle()
t2.speed(0)
tm.new_thread(t2.draw_tree, self.get_location(), size / 2)
for _ in range(90):
tm.q.put(self.left(1))
tm.new_thread(self.draw_tree, self.get_location(), size / 2)
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = []
def new_thread(self, func, *args):
self.threads.append(threading.Thread(target=func, args=(args,)))
self.threads[-1].daemon = True
self.threads[-1].start()
def process_queue(self, scr):
while not self.q.empty():
(self.q.get())(1)
if threading.active_count() > 1:
scr.ontimer(self.process_queue(scr), 100)
tm = ThreadManager()
scr = turtle.Screen()
t1 = Turtle()
t1.speed(0)
tm.new_thread(t1.draw_tree)
tm.process_queue(scr)
scr.exitonclick()
Can anyone give me an idea of where I went wrong here? The error messages say something along the lines of the recursion going too deep when process_queue calls itself. Am I using scr.ontimer() wrong?
There are several problems with your code:
draw_tree() doesn't have a base case to stop it's (conceptual)
recursion, it just keeps creating new threads.
Each call to draw_tree() divides size in half using floating
division so range(size) will fail as range() can only take ints.
Your calls to self.get_location() are not valid as it tells you
where the turtle is, not where it will be once the main thread
finishes processing outstanding graphics commands. You have to
compute where you will be, not look where you are.
This call, tm.q.put(self.tolocation(startpos)) isn't valid -- you
either need to do tm.q.put(self.tolocation, startpos) or call
tm.q.put() on each command inside self.tolocation().
You can't create new turtles anywhere but the main thread, as they
invoke tkinter on creation and that'll be on the wrong (not main)
thread. In my rework below, I simply preallocate them.
args=(args,) is incorrect -- should be args=args as args
is already in the correct format.
You don't need to create two new threads at each branching point, just
one. The new turtle goes one way, the old turtle continues on the
other.
Below is my rework of your code to address the above and other issues:
import math
import threading
from queue import Queue
from turtle import Turtle, Screen
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def clone(self):
return Location(self.xpos, self.ypos, self.heading)
class Terrapin(Turtle):
def tolocation(self, location):
tm.q.put((self.penup,))
tm.q.put((self.setx, location.xpos))
tm.q.put((self.sety, location.ypos))
tm.q.put((self.setheading, location.heading))
tm.q.put((self.pendown,))
def draw_tree(self, startpos, size=100):
if size < 1:
return
self.tolocation(startpos)
tm.q.put((self.forward, size))
angle = math.radians(startpos.heading)
startpos.xpos += size * math.cos(angle)
startpos.ypos += size * math.sin(angle)
tm.q.put((self.right, 45))
startpos.heading -= 45
tm.new_thread(pond.get().draw_tree, startpos.clone(), size / 2)
tm.q.put((self.left, 90))
startpos.heading += 90
self.draw_tree(startpos, size / 2)
pond.put(self) # finished with this turtle, return it to pond
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = Queue()
def new_thread(self, method, *arguments):
thread = threading.Thread(target=method, args=arguments)
thread.daemon = True
thread.start()
self.threads.put(thread)
def process_queue(self):
while not self.q.empty():
command, *arguments = self.q.get()
command(*arguments)
if threading.active_count() > 1:
screen.ontimer(self.process_queue, 100)
screen = Screen()
# Allocate all the turtles we'll need ahead as turtle creation inside
# threads calls into Tk which fails if not running in the main thread
pond = Queue()
for _ in range(100):
turtle = Terrapin(visible=False)
turtle.speed('fastest')
pond.put(turtle)
tm = ThreadManager()
tm.new_thread(pond.get().draw_tree, Location())
tm.process_queue()
screen.exitonclick()
if instead of large steps:
tm.q.put((self.right, 45))
you want to break down graphic commands into small steps:
for _ in range(45):
tm.q.put((self.right, 1))
that's OK, I just wanted to get the code to run. You need to figure out if this gains you anything.

multithread pixel plotting lib for python

I am doing a graphics project and have to use pixel plotting function only. I came across pygame but it doesnt seem to provide its calls to be used in threads. Please suggest me workaround this or any other python library that can be used with threads.
#! /usr/bin/env python
import threading
import time
class plotterthread(threading.Thread):
def __init__(self,screen,queue):
threading.Thread.__init__(self)
self.screen = screen
self.__queue__ = queue
self._colorset = False
self.kill = False
def setcolor(self,r,g,b):
self._r = r
self._g = g
self._b = b
self._colorset = True
def run(self):
try:
while not self.__queue__.empty() and not self.kill:
(x,y) = self.__queue__.get(timeout=1)
if not self._colorset:
#this is where error occures
#this calls pygame.set_at() method
self.screen.plotpixel((x,y))
else:
self.screen.plotpixel((x,y),(self._r,self._g,self._b))
except:
pass
class plotter:
def __init__(self,screen,min,max,queue,ppt=50):
self.screen = screen
self.min = min
self.max = max
self.ppt = ppt # pixels per thread
self.coords = queue
def plot(self):
noOfcoords = self.coords.qsize()
noOfthreads = int(noOfcoords/self.ppt)
if noOfthreads > self.max:
noOfthreads = self.max
elif noOfthreads < self.min:
noOfthreads = self.min
self.threadpool = []
for i in range(noOfthreads):
pthread = plotterthread(screen = self.screen,queue = self.coords)
self.threadpool.append(pthread)
pthread.start()
while True:
try:
livethreads = 0
for t in self.threadpool:
if t is not None and t.isAlive():
livethreads += 1
t.join(timeout=1)
if livethreads == 0:
break
except KeyboardInterrupt:
for threads in self.threadpool:
if threads is not None and threads.isAlive():
threads.kill = True
break
Here, screen is the instance of the window on which I am trying to draw.
queue is the Queue which contains all the calculated coordinates to be plotted.
All I wanted to do was first calculate all the pixels and push them into a queue and then share the queue with threads to plot them in parallel to speed up the process.

gtk progressbar doesn't work anymore after matplot event?

Hi I'm new at programming in python and gtk.
I'm writing a program to do some measurement.
For plotting the measurement, I use matplotlib.
The program will have a function to turn a heater on and off and to make the measurement.
I want to use separate threads for the heater and the measurement.
For now the communication with the hardware hasn't been implemented yet in this program.
The problem is when I click the "measurebutton", the "progressbar" doesn't work anymore.
I get a message:
gtk.ProgressBar object at 0x29b8460 (uninitialized at 0x0)
When I only use the heaterbutton, the progressbar keeps working
What am I doing wrong ?
This is the code
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import time
import gobject
import threading
import matplotlib
import numpy as np
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
class measure:
# callback to quit
def delete_event(self, widget, event, data = None):
gtk.main_quit()
return False
def heater_helper(self, widget, heater, progressbar):
print "starting heater thread"
threading.Thread(target=self.heater_cb, args=(widget, heater, progressbar)).start()
def heater_cb(self, widget, heater, progressbar):
heaterstring = "6.3"
heater = eval(heaterstring)
stap = 1
j = 0.1
heatervalue = widget.get_active()
print heatervalue
progressbar.set_fraction(0.1)
while (stap <= 10 ):
if widget.get_active():
print widget.get_active()
fraction = j * stap
print fraction
progressbar.set_fraction(fraction)
stap = stap + 1
time.sleep(1)
else:
stap = 11
progressbar.set_fraction(0.0)
break
def do_measurement_helper(self, widget, fig):
print " Start measurement thread"
threading.Thread(target=self.do_measurement, args=(widget, fig)).start()
def do_measurement(self, widget, fig):
fig.clear()
ax = fig.add_subplot(111)
x = np.arange(0, 5*np.pi, 0.01)
y = np.sin(x**2)*np.exp(-x)
ax.plot(x, y)
fig.canvas.draw()
def __init__(self):
# Create new Window
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", self.delete_event)
self.window.show()
mainbox = gtk.HBox(False, 0)
self.window.add(mainbox)
mainbox.show()
leftvbox = gtk.VBox(False, spacing = 10)
mainbox.pack_start(leftvbox, expand = False, fill = False, padding = 0)
leftvbox.show()
rightvbox = gtk.VBox(False, spacing = 10)
mainbox.pack_start(rightvbox, expand = False, fill = False, padding =0)
rightvbox.show()
heaterprogressbar = gtk.ProgressBar()
leftvbox.pack_start(heaterprogressbar, expand = False, fill = False, padding = 0)
heaterprogressbar.show()
heaterbutton = gtk.ToggleButton("Heater")
leftvbox.pack_start(heaterbutton, expand = True, fill = False, padding = 0)
heaterbutton.show()
heaterbutton.connect("toggled", self.heater_helper, heaterbutton, heaterprogressbar)
fig = matplotlib.figure.Figure(figsize=(5,4), dpi=64)
canvas = FigureCanvas(fig)
rightvbox.pack_start(canvas, expand = True, fill = True, padding = 0 )
canvas.show()
measurebutton = gtk.Button("Measure")
rightvbox.pack_start(measurebutton, expand = False, fill = False, padding = 0)
measurebutton.show()
measurebutton.connect("clicked", self.do_measurement_helper, fig)
def main():
gtk.main()
return(0)
if __name__ == "__main__":
gtk.gdk.threads_init()
measure()
main()
gtk.gdk.threads_leave()
Kind regards,
Joris Weijters
Combining threads, Matplotlib, and the GTK main loop is probably not supported and difficult to debug exactly what is going on. My advice is not to do any GUI calls from threads, but instead schedule them using gobject.idle_add().
Threads, Matplotlib and the GTK main loop can be combined, if you keep in mind some things:
I use gobject.threads_init() instead of gtk.gdk.threads_init(), the gtk.gdk variant did not work for me in combination with Matplotlib. I think you can also omit the gtk.gdk.threads_leave().
As ptomato mentioned, you should let the main gtk thread handle anything that has to do with gtk widgets by calling the gobject.idle_add() and gobject.timeout_add() functions.
I usually make a helper function to periodically update the statusbar from a float variable:
def do_measurement(self):
self.data = []
self.progress = 0
self.abort = threading.Event()
gobject.timeout_add(100, self.update_progressbar)
for point in some_generator_yielding_100_values():
if self.abort.is_set():
break
self.data.append(point)
self.progress += 0.01
self.progress = None
def update_progressbar(self):
if self.progress is None:
self.progressbar.set_fraction(0) # reset bar
return False # do not run again
self.progressbar.set_fraction(self.progress)
return True # run again after 100ms
def start_measurement(self):
threading.Thread(target=self.do_measurement).start()
def stop_measurement(self):
self.abort.set()
But you can of course also just call gobject.idle_add(self.progressbar.set_fraction, x) to set the new value x asynchroneously.

Resources