Speeding up potentially long tasks - python

I decided to make a class for easily displaying animated gifs in Tkinter, which does work, but the process of gathering all the frames dynamically pretty much always takes a noticeable chunk of time and prevents anything else from happening until it has finished. I was wondering if there would be any way to speed it up or at a more efficient way of doing the same thing.
Here is the code for the class:
from tkinter import *
class animation:
def __init__(self,*args,**kwargs):
self.root=args[0]
self.label=Label(self.root)
self.label.grid()
self.image=kwargs["image"]
try:
self.delay=kwargs["delay"]
except KeyError:
self.delay=20
self.frames=[]
x=0
while True:
try:
img=PhotoImage(file=self.image,
format="gif -index {}".format(x))
self.frames.append(img)
x+=1
except:
break
def animate(self,y):
try:
self.label.configure(image=self.frames[y])
self.root.after(self.delay,lambda:self.animate(y+1))
except IndexError:
self.label.configure(image=self.frames[0])
self.root.after(self.delay,lambda:self.animate(1))
and here is how it would be used:
from tkinter import *
from modules.animation import animation
root=Tk()
cosmog=animation(root,image="cosmog.gif").animate(0)
cosmoem=animation(root,image="cosmoem.gif").animate(0)
lunala=animation(root,image="lunala.gif").animate(0)
root.mainloop()

Try like this with threading:
import threading
import time
def __init__(self, *args, **kwargs):
self.frames=[]
self.run_loop = True
self.x=0
self.taken = []
self.order = []
while self.run_loop:
threading.Thread(target=self.add_image).start()
def add_image(self):
try:
if self.x not in self.taken: # make sure not several threads add the same image index
self.taken.append(self.x)
self.x+=1
else:
return
# save local x before creating image, for the order
# all the other threads will increment self.x while Image is created
x = self.x
img=PhotoImage(file=self.image, format="gif -index {}".format(self.x))
self.frames.append(img)
self.order.append(x) # keep track of the order
if len(self.frames) == len(self.taken): # when finish
self.frames = [x for _,x in sorted(zip(self.order,self.frames))] # sort the frames to the order
except:
self.run_loop = False
I made a simple runable example without tkinter, using time.sleep(random amount of time), to simulate PhotoImage:
import threading
import time
from random import randint
class Test:
frames=[]
run_loop = True
x=0
taken = []
order = []
time = 0
def __init__(self):
while self.run_loop:
threading.Thread(target=self.add_image).start()
def add_image(self):
if self.x < 100:
if self.x not in self.taken: # make sure not several threads add the same image index
self.taken.append(self.x)
self.x+=1
else:
return
x = self.x
t = randint(1,10)/10.0
self.time += t
time.sleep(t) # PhotoImage random time.sleep 0 to 1 second
self.order.append(x)
self.frames.append(x)
if len(self.frames) == len(self.taken):
print("Frames before sort")
print(self.frames)
self.frames = [x for _,x in sorted(zip(self.order,self.frames))]
print("\nFrames after sort")
print(self.frames)
print("\nTime used combined: {} seconds".format(self.time))
else:
self.run_loop = False
t = Test()
This test shows a combined time used, to be around 50 seconds.
With a 100 threads, it does it in 1 second. The amount of time for the longest time.sleep, wich is 0 to 1 second.
So for you, it should not take more than the one longest PhotoImage call

Related

Is there a way to run a funcion on several instances of a class at exactly the same time? Independent Multi-Metronome with MIDI ctrl

Hi :) I am programming an independent multi-metronome and need to run the funciont beat() on a while loop for every instances of my class Metronome(), starting at the same time.
import time
import mido
from numpy import interp
from IPython.display import clear_output
inport = mido.open_input() #my MIDI is a KORG nanoKontrol 2
class Metronome():
#A dict for my MIDI controller
metronomes_controls = {
'inst_number':[n + 0 for n in range(0,7)],
'vol_slide':[n + 0 for n in range(0,7)],
'tempo_knob': [n + 16 for n in range(0,7)],
'play_stop': [n + 32 for n in range(0,7)],
'sync_selected': [n + 48 for n in range(0,7)],
'tap_button': [n + 64 for n in range(0,7)]}
def __init__(self, inport, inst_number = 0, tempo=60, active=True,
on_off_list = ['ON','OFF','ON','OFF'], selector = 0):
self.inport = inport
self.inst_number = inst_number
self.tempo = tempo
self.active = active
self.on_off_list = on_off_list #The controller is not so precise
self.selector = selector
self.controls = dict(zip(list(metronomes_controls.keys()),
[val[self.inst_number] for val in metronomes_controls.values()]))
def beat(self):
if self.active == True:
print('Tick', self.tempo) #this is going to be a sound
time.sleep(int(round(60/self.tempo)))
clear_output()
self.update()
else:
self.update()
def update(self):
msg = self.inport.receive(block=False)
for msg in inport.iter_pending():
if msg.control == self.controls['tempo_knob']:
self.tempo = int(interp(msg.value,[0,127],[20,99]))
if msg.control == self.controls['play_stop']:
self.selector += 1
if self.selector >3:
self.selector = 0
if 'ON' in self.on_off_list[self.selector]:
print('ON')
self.active = True
if 'OFF' in self.on_off_list[self.selector]:
print('OFF')
self.active = False
#Creating two instances of my class
m0 = Metronome(inport = inport, inst_number = 0)
m1 = Metronome(inport = inport,inst_number = 1)
m2 = Metronome(inport = inport,inst_number = 1)
m3 = Metronome(inport = inport,inst_number = 1)
#They run on a while loop. All should start at the same time.
while True:
m0.beat()
m1.beat()
m2.beat()
m3.beat()
I read about threading but it seems to create some starting delay. Then I got into barries, but I couldn't imagine how to implement it :/ or maybe should I try something with multiprocess? I got really lost! Any advice is highly appreciated
Thanks for the help!!!
Create one thread per metronome and start them at (almost) the same time:
from threading import Thread
# Previous code...
...
def create_thread(inport, inst_number):
# The function the thread is going to execute
def inner():
m = Metronome(inport, inst_number)
m.beat()
return Thread(target=inner)
if __name__ == "__main__":
inport = mido.open_input()
# Create the threads
threads = [
create_thread(inport, i) for i in (0, 1, 1, 1)
]
# Start them at (almost) the same time
for t in threads:
t.start()
# Wait for them to finish execution
for t in threads:
t.join()

Run thread and ask about data during work

I have a problem with my code. I'm running this code with thread, then I need to ask about variables SPEED, etc., but I don't know how. I'm still trying to do this, but I'm getting errors with thread.
BTW, I want to make a script that generates fake car data, and I need to fill a database, and then make some diagrams.
import time
import thread
class Test:
def __init__(self):
self.speed = 0
self.dist = 0
self.maxSpeed = 150
self.time = 6
self.fuel = 100
self.distance = 100
self.start = time.time()
self.elapsed = 0
def jazda(self):
while True:
self.speed += 1
if self.speed < self.maxSpeed:
time.sleep(1)
else:
time.sleep(60)
self.elapsed = time.time() - self.start
self.dist = (self.speed * self.elapsed) / 3600
print "Distance: ", self.dist
print "Speed: ", self.speed
print "Time: ", self.elapsed
if self.elapsed > self.time:
break
return 0
def SPEED(self):
return self.speed
and second script:
import test
import thread
import time
class Data:
def __init__(self):
self.test = test.Test()
def get_speed(self):
while True:
return self.test.SPEED()
time.sleep(2)
thread.start_new_thread( test.Test().jazda(), () )
thread.start_new_thread( obdData().get_speed, () )
The error I'm getting is:
thread.start_new_thread( Test().jazda(), () )
TypeError: first arg must be callable
I believe the problem is that the thread.start_new_thread method is expecting a method reference and not a method invocation i.e. test.Test().jazda is a method reference i.e. callable; however, test.Test().jazda() will return the result of the method (0 in this case) not a callable. Your start method should look like thread.start_new_thread(test.Test().jazda, ())

read a variable while multithreading

I get two streams of data from an API, so there are 3 threads, main one, stream1 and stream2. Stream1 and Stream2 need to process this data and once they're done they store them on main_value1 and main_value2.
From main thread I need to read the last value at any given time (so if I need this value and it is still processing then I get the last processed/stored one), what would be the optimal way? from the code example here I need help in coding functions get_main_value1() and, of course, get_main_value2()
def stream1():
while True:
main_value1 = process()
def stream2():
while True:
main_value2 = process2()
def get_main_value1(): ?
def get main_value2(): ?
def main():
threading.Thread(function=stream1,).start()
threading.Thread(function=stream2).start()
while True:
time.sleep(random.randint(0,10))
A = get_main_value1()
B = get_main_value2()
One way would be to make them global:
STREAM1_LAST_VALUE = None
def stream1():
global STREAM1_LAST_VALUE
while True:
main_value1 = process()
STREAM1_LAST_VALUE = main_value1
STREAM2_LAST_VALUE = None
def stream2():
global STREAM2_LAST_VALUE
while True:
main_value2 = process2()
STREAM2_LAST_VALUE = main_value2
def get_main_value1():
return STREAM1_LAST_VALUE
def get main_value2():
return STREAM2_LAST_VALUE
def main():
threading.Thread(function=stream1,).start()
threading.Thread(function=stream2).start()
while True:
time.sleep(random.randint(0,10))
A = get_main_value1()
B = get_main_value2()

GUI with pyqtgraph never refresh

I have been working on a GUI for the beagle bone black that launch a thread when a button is clicked and starts to get data through the SPI.
This function is inside a class called Scanner(QObject) and runs in a different thread when the Start button is clicked.
def scan (self):
thread_name = QThread.currentThread().objectName()
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
QThread.currentThread().msleep(50)
app.processEvents()
if self.__abort:
self.sig_msg.emit('scan stopped by user')
break
self.sig_done.emit(thread_name)
sig_msg is a pyqtsignal connected to the following function inside the GUI thread.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
And finally the MainGraph.update_fig() is called. Inside that function i have used setData(self.datat,self.datay) and app.processEvents() for update the graph, but nothing changes. If i run plot(self.datat,self.datay) instead it redraws the graph but causes a huge performance hit.
class DynamicPlotter(PlotWidget):
def __init__(self,parent=None):
PlotWidget.__init__(self)
self.setParent(parent)
# Use getPlotItem() to get the PlotItem inside PlotWidget.
self.pitem = self.getPlotItem()
#now pitem is our PlotItem
self.pitem.curve=self.pitem.plot()
#curve is a new PlotDataItem added by PlotItem.plot()
self.datat = [1,2]
self.datay = [1,2]
self.pitem.curve.setData(self.datat,self.datay)
#this graph works fine
self.datat = []
self.datay = []
def update_fig(self,t:int,y:int):
self.datat.append(t)
self.datay.append(y)
#it works
self.pitem.curve=self.pitem.plot(self.datat,self.datay)
#it doesn't
self.pitem.curve.setData(self.datat,self.datay)
app.processEvents()
print (self.datat)
log.debug(str(t)+str(y))
def reset_figure(self):
log.debug('clean graph')
self.clear()
I have been following this example from the pyqtplot and my idea was do something similar inside my GUI.
import initExample
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
app = QtGui.QApplication([])
p = pg.plot()
p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
curve = p.plot()
data = np.random.normal(size=(50,5000))
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr%10])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
app.processEvents() ## force complete redraw
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
I have been reading the documentation and right know I'm not sure where is/are the problems. I bet for the threads or the event loop handler but i don't know.
Which are the critical points that i have to review?
Any clue?
Thank you.
After a while i have found the problem by myself.
I fixed the problem changing the way that i was using to reset the graph and stopping the scan thread until elements are plot.
Changes on reset function. self.clear() remove the traces on the PlotWidget and that wasn't what i needed.
def reset_figure(self):
log.debug('clean graph')
self.datat =[]
self.datay=[]
self.pitem.curve.setData(self.datat,self.datay)
Scan was modified to stop while the data is plotted in the other thread. sync_to_plot stop the thread execution until self._wait=False. This value is changed by wait_state.
def scan (self):
thread_name = QThread.currentThread().objectName()
#thread_id = str(QThread.currentThreadId())#review
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
#pause while plot values
self.sync_to_plot()
if step % refrate == 0:
log.debug("%5d : %d" % (step, data) )
if self.__abort:
self.sig_msg.emit('scan stoped by user')
break
self.sig_done.emit(thread_name)
def sync_to_plot(self):
self._wait=True
while self._wait:
log.debug("waiting")
QThread.currentThread().msleep(1)
app.processEvents()
def wait_state(self, stat):
self._wait=stat
With that done the last change was on the on_scaner_data that unblocks the thread that is waiting on sync_to_plot.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
for thread, scaner in self.__threads:
scaner.wait_state(False)
log.debug("scanner false")

How to end program running after given time in Python

I'd like my Python program to run an algorithm for a given number of seconds and then to print the best result so far and to end.
What is the best way to do so?
I tried the following but it did not work(the program kept running after the printing):
def printBestResult(self):
print(self.bestResult)
sys.exit()
def findBestResult(self,time):
self.t = threading.Timer(time, self.printBestResult)
self.t.start()
while(1):
# find best result
Untested code, but something like this?
import time
threshold = 60
start = time.time()
best_run = threshold
while time.time()-start < threshold:
run_start = time.time()
doSomething()
run_time = time.time() - start
if run_time < best_run:
best_run = run_time
On unix, you can use signals -- This code times out after 1 second and counts how many times it iterates through the while loop in that time:
import signal
import sys
def handle_alarm(args):
print args.best_val
sys.exit()
class Foo(object):
pass
self=Foo() #some mutable object to mess with in the loop
self.best_val=0
signal.signal(signal.SIGALRM,lambda *args: handle_alarm(self))
signal.alarm(1) #timeout after 1 second
while True:
self.best_val+=1 # do something to mutate "self" here.
Or, you could easily have your alarm_handler raise an exception which you then catch outside the while loop, printing your best result.
If you want to do this with threads, a good way is to use an Event. Note that signal.alarm won't work in Windows, so I think threading is your best bet unless in that case.
import threading
import time
import random
class StochasticSearch(object):
def __init__(self):
self.halt_event = threading.Event()
def find_best_result(self, duration):
halt_thread = threading.Timer(duration, self.halt_event.set)
halt_thread.start()
best_result = 0
while not self.halt_event.is_set():
result = self.search()
best_result = result if result > best_result else best_result
time.sleep(0.5)
return best_result
def search(self):
val = random.randrange(0, 10000)
print 'searching for something; found {}'.format(val)
return val
print StochasticSearch().find_best_result(3)
You need an exit condition, or the program will run forever (or until it runs out of memory). Add one yourself.

Categories