tkinter freezing - how to use threads/.after() - python-3.x

I'm trying to create a screen time alarm app using tkinter and the time module. I used a while loop to check if the current time equals the alarm that was set by the user. If the current time equals the alarm, a Toplevel window is supposed to pop up. But my program freezes while executing the while loop.
I think I need to use a thread or .after() to fix the code but I don't understand how to use either of them.
P.S. I have already gone through similar questions and solutions on stackoverflow but I'm still confused.
def set_alarm(): #set the alarm by collecting values from the spinboxes
hour = spin_hour.get()
mins = spin_min.get()
sec = spin_sec.get()
while True:
current = strftime("%H:%M:%S",localtime())
current.split(":")
if int(current[0]) == hour and int(current[1]) == mins and int(current[2]) == sec:
ring() #rings an alarm & calls a function to show Toplevel window
break

There's no need to continually check the time. You can use after to schedule the alarm at the given time. Just compute the number of seconds between now and the date in the future.
For example, if the alarm is set for exactly one minute from now, that's 60 seconds or 60,000 milliseconds. To ring the bell in 60,000 milliseconds you would call after like this (assuming root was the reference to the root window):
root.after(60000, ring)

Related

How to run multiple functions in Python

I need to use the turtle function to create a shape that moves and that can be paused. I figured out how to do that but now I need to make it run for 10 seconds then end. It also needs to print the seconds as the countdown runs and the timer needs to pause when the app is paused.
This is what I have so far.
import turtle
import time
wn = turtle.Screen()
wn.bgcolor('blue')
player = turtle.Turtle()
player.color('yellow')
player.shape('triangle')
player.penup()
speed=1
def PlayerPause():
global speed
speed = 0
def PlayerPlay():
global speed
speed = 1
while True:
player.forward(speed)
player.left(speed)
def Timer():
seconds = 11
for i in range(1,11):
print(str(seconds - i)+ ' Seconds remain')
time.sleep(1)
print('GAME OVER')
turtle.listen()
turtle.onkey(PlayerPause, 'p')
turtle.onkey(PlayerPlay, 'r')
turtle.onkey(Timer, 'e')
wn.mainloop()
I suggest you practice "incremental development." That means instead writing a whole bunch of code and then trying to make it work, you make just one relatively small thing work, then gradually add more functions, one small step at a time.
I'll sketch how you could do this for this question.
Get something to happen once a second. Just make some kind of visible change once a second. It could be making some object alternately appear and disappear. It can be anything that is easy, visible, and doesn't break what you already have. The Screen class has an ontimer method you can use for this.
Only after the first step is working, start counting seconds and displaying the counter. Don't worry about pausing or terminating. Just make a counter that shows increasing numbers as long as the script runs.
Change it to shut everything down when the counter gets to ten.
Change it to count down from ten to zero instead of up from zero to ten.
Change the timer function to check whether the action is paused. If so, it should not change the seconds counter.
If you get stuck on any step, you can post a more specific question.

How to dynamically update an image in a tkinter label widget?

I'm trying to make a simple slot machine python program for an assignment and I am having trouble getting the slot machine images to update. What I want to happen is for the user to click the button and have the three labels dynamically update with a different picture every 0.1 seconds for 2 seconds. But what is happening is my randint is generating random index numbers for the array, but the label only show a new image on the last randint instance. Here is the code I have:
def update_image():
global images
time.sleep(0.1)
y = images[randint(0, 3)]
slot1.delete()
slot1.configure(image = y)
print(y)
def slot_machine():
x = 0
while x != 10:
slot1.after(0,update_image)
x = x + 1
The problem is that you are calling after(0, ...) which adds a job to the "after" queue to be run as soon as possible. However, your while loop runs extremely quickly and never gives the event loop the chance to process queued events, so the entire loop ends before a single image changes.
Once the event loop is able to process the events, tkinter will try to process all pending events that are past due. Since you used a timeout of zero, they will all be past due so tkinter will run them all as fast as possible.
The better solution is to have the function that updates the image also be responsible for scheduling the next update. For example:
def update_image(delay, count):
<your existing code here>
# schedule the next iteration, until the counter drops to zero
if count > 0:
slot1.after(delay, update_image, delay, count-1)
With that, you call it once and then it calls itself repeatedly after that:
update_image(100, 10)

sync threads to read different resources at exactly the same time

I have two cameras and this is important to read the frames with OpenCV exactly at the same time, I thought something like Lock but I cannot figure out the way I can implement this.
I need some trigger to push and enable the threads to read frames, and then wait for another trigger hit, something like below :
def get_frame(queue, cap):
while running:
if(read_frame):
queue.put(cap.read());
else:
# without this sleep this function just consumes unnecessary CPU time
time.sleep(some_time);
q = Queue.Queue()
# for every camera
for u in xrange(2):
t = threading.Thread(target=get_frame, args = (q, caps[u]))
t.daemon = True
t.start()
The problems with the above implementation are :
I need the sleep time to be defined since I don't know the delay in between every frame read (i.e. it might be long or short, depending on the calculation)
This does not enable me to read once for every trigger hit.
So this approach won't work, Any suggestions?
Consider getting FPS from VideoCapture. Also, note the difference between VideoCapture.grab and VideoCapture.retrieve frame. This is used for camera synchronization.
First call VideoCapture#grab for both cameras and then retrieve the frames. See docs.

Frequent Updating of GUI WxPYTHON

I have a piece of code which has to get executed every 100ms and update the GUI. When I am updating the GUI - I am pressing a button, which calls a thread and in turn it calls a target function. The target function gives back the message to the GUI thread using pub sub as follows.
wx.CallAfter(pub.sendMessage, "READ EVENT", arg1=data, arg2=status_read) # This command line is in my target function
pub.subscribe(self.ReadEvent, "READ EVENT") # This is in my GUI file - whihc calls the following function
def ReadEvent(self, arg1, arg2):
if arg2 == 0:
self.MessageBox('The program did not properly read data from MCU \n Contact the Program Developer')
return
else:
self.data = arg1
self.firmware_version_text_control.Clear()
#fwversion = '0x' + ''.join('{:02X}'.format(j) for j in reversed(fwversion))
self.firmware_version_text_control.AppendText(str(SortAndDecode(self.data, 'FwVersion')))
# Pump Model
self.pump_model_text_control.Clear()
self.pump_model_text_control.AppendText(str(SortAndDecode(self.data, 'ModelName')))
# Pump Serial Number
self.pump_serial_number_text_control.Clear()
self.pump_serial_number_text_control.AppendText(str(SortAndDecode(self.data, 'SerialNum'))[:10]) # Personal Hack to not to display the AA , AB and A0
# Pressure GAIN
self.gain_text_control.Clear()
self.gain_text_control.AppendText(str(SortAndDecode(self.data, 'PresGain')))
# Pressure OFFSET Offset
self.offset_text_control.Clear()
self.offset_text_control.AppendText(str(SortAndDecode(self.data, 'PresOffset')))
#Wagner Message:
#self.status_text.SetLabel(str(SortAndDecode(self.data, 'WelcomeMsg')))
# PUMP RUNNING OR STOPPED
if PumpState(SortAndDecode(self.data, 'PumpState')) == 1:
self.led6.SetBackgroundColour('GREEN')
elif PumpState(SortAndDecode(self.data, 'PumpState')) == 0:
self.led6.SetBackgroundColour('RED')
else:
self.status_text.SetLabel(PumpState(SortAndDecode(self.data, 'PumpState')))
# PUMP RPM
self.pump_rpm_text_control.Clear()
if not self.new_model_value.GetValue():
self.pump_rpm_text_control.AppendText("000")
else:
self.pump_rpm_text_control.AppendText(str(self.sheet_num.cell_value(self.sel+1,10)*(SortAndDecode(self.data, 'FrqQ5'))/65536))
# PUMP PRESSURE
self.pressure_text_control.Clear()
self.pressure_text_control.AppendText(str(SortAndDecode(self.data, 'PresPsi')))
# ON TIME -- HOURS AND MINUTES --- EDITING IF YOU WANT
self.on_time_text_control.Clear()
self.on_time_text_control.AppendText(str(SortAndDecode(self.data, 'OnTime')))
# JOB ON TIME - HOURS AND MINUTES - EDITING IF YOU WANT
self.job_on_time_text_control.Clear()
self.job_on_time_text_control.AppendText(str(SortAndDecode(self.data, 'JobOnTime')))
# LAST ERROR ----- RECHECK THIS AGAIN
self.last_error_text_control.Clear()
self.last_error_text_control.AppendText(str(SortAndDecode(self.data, 'LastErr')))
# LAST ERROR COUNT --- RECHECK THIS AGAIN
self.error_count_text_control.Clear()
self.error_count_text_control.AppendText("CHECK THIS")
As you can see my READEVENT is very big and it takes a while for the GUI to take enough time to successfully do all these things. My problem here is, when my GUI is updating the values of TEXTCTRL it is going unresponsive - I cannot do anything else. I cant press a button or enter data or anything else. My question is if there is a better way for me to do this, so my GUI wont go unresponsive. I dont know how I can put this in a different thread as all widgets are in the main GUI. But that also requires keep creating threads every 100ms - which is horrible. Any suggestions would be greatly helpful.
Some suggestions:
How long does SortAndDecode take? What about the str() of the result? Those may be good candidates for keeping that processing in the worker thread instead of the UI thread, and passing the values to the UI thread pre-sorted-and-decoded.
You can save a little time in each iteration by calling ChangeValue instead of Clear and AppendText. Why do two function calls for each text widget instead of just one? Function calls are relatively expensive in Python compared to other Python code.
If it's possible that the same value will be sent that was sent on the last iteration then adding checks for the new value matching the old value and skipping the update of the widget could potentially save lots of time. Updating widget values is very expensive compared to leaving them alone.
Unless there is a hard requirement for 100ms updates you may want to try 150 or 200. Fewer updates per second may be fast enough for most people, especially since it's mostly textual. How much text can you read in 100ms?
If you are still having troubles with having more updates than the UI thread can keep up with, then you may want to use a different approach than pubsub and wx.CallAfter. For example you could have the worker thread receive and process the data and then add an object to a Queue.Queue and then call wx.WakeUpIdle(). In the UI thread you can have an EVT_IDLE event handler that checks the queue and pulls the first item out of the queue, if there are any, and then updates the widgets with that data. This will give the benefit of not flooding the pending events list with events from too many wx.CallAfter calls, and you can also do things like remove items from your data queue if there are too many items in it.

Rendering videos and polling for Saccade Data in Real Time in PsychToolbox

I am using MATLAB's PsychToolbox to run an experiment where I have to gather saccade information in real time, while also rendering a video frame by frame.
The problem that I have is that given the frame rate of the video and the display (~24fps), it means that I have about 40ms time window to render query and render every frame that I have previously stored in memory. This is fine, but since this process takes aditional time, it usually implies that I have about ~20ms to consistently poll for a saccade from beginning to end.
This is a problem, because when I poll for saccades, what I am usually doing (in say still images, that only have to be displayed once), is I wait for a start and end of a fixation, given consistent polling from the eye tracking machine, that detects that the observers gaze has shifted abruptly from one point to another with a
speed exceeding: 35 deg/s
and an
acceleration exceeding: 9500 deg/s^2
but if the beginning of a saccade or end of it takes places when a frame is being rendered (Which is most of the time), then it makes it impossible to get the data in real time without splitting the rendering and polling process into two separate MATLAB threads.
My code (relevant part) looks like this:
while GetSecs-t.stimstart(sess,tc)<fixation_time(stimshownorder(tc))
x =evt.gx(1);
y =evt.gy(1);
pa = evt.pa(1);
x_vec = [x_vec; x];
y_vec = [y_vec; y];
pa_vec = [pa_vec; pa];
evta=Eyelink('NewestFloatSample');
evtype=Eyelink('GetNextDataType');
#%% Ideally this block should detect saccades
#%% It works perfect in still images but it can't do anything here
#%% since it conflicts the main for loop ahead.
if evtype==el.ENDSACC
sacdata=Eyelink('GetFloatData',evtype);
sac.startx(sess,tc,sacc)=sacdata.gstx;
sac.starty(sess,tc,sacc)=sacdata.gsty;
sac.endx(sess,tc,sacc)=sacdata.genx;
sac.endy(sess,tc,sacc)=sacdata.geny;
sac.start(sess,tc,sacc)=sacdata.sttime;
sac.end(sess,tc,sacc)=sacdata.entime;
sacc=sacc+1;
end
#%Main loop where we render each frame:
if (GetSecs-t.space(sess,tc)>lag(tc))
z = floor((GetSecs-t.space(sess,tc)-lag(tc))/(1/24))+1;
if z > frame_number
z = frame_number;
end
Screen('DrawTexture',win,stimTex{z});
Screen('Flip',win);
#DEBUG:
#disp(z);
#%disp(frame_number);
end
end
Ideally, I'd want a MATLAB function that can render the video independently in one separate thread in the back end, while still polling for saccades in the main thread. Ideally like this:
#% Define New thread to render video
#% Some new function that renders video in parallel in another thread
StartParallelThread(1);
#%Play video:
Playmovie(stimTex);
#%Now start this main loop to poll for eye movements.
while GetSecs-t.stimstart(sess,tc)<fixation_time(stimshownorder(tc))
x =evt.gx(1);
y =evt.gy(1);
pa = evt.pa(1);
x_vec = [x_vec; x];
y_vec = [y_vec; y];
pa_vec = [pa_vec; pa];
evta=Eyelink('NewestFloatSample');
evtype=Eyelink('GetNextDataType');
if evtype==el.ENDSACC
sacdata=Eyelink('GetFloatData',evtype);
sac.startx(sess,tc,sacc)=sacdata.gstx;
sac.starty(sess,tc,sacc)=sacdata.gsty;
sac.endx(sess,tc,sacc)=sacdata.genx;
sac.endy(sess,tc,sacc)=sacdata.geny;
sac.start(sess,tc,sacc)=sacdata.sttime;
sac.end(sess,tc,sacc)=sacdata.entime;
sacc=sacc+1;
end
end
It also seems that the time it takes to run the Screen('Flip',win) command is about 16ms. This means that if any saccades happen in this interval, I would not be able to detect or poll them. Note that in the end I am having 42ms (for frame refresh rate) minus 16ms (for time it takes to query and display the frame), so a total of ~26ms of probing time per frame for getting eye movements and computing any real-time processing.
A possible solution might be to continually poll for gaze, instead of checking if an eye movement is a saccade or not. But I'd still have the problem of not capturing what goes on in about a third of each frame, just because it takes time to load it.
You need to reorganize your code. The only way to make this work is knowing how long the flip takes and knowing how long submission of the next video frame takes. Then you poll the eye tracker in a loop until you have just enough time left for the drawing commands to be executed before the next screen vertical blank.
You can't do any form of reliable multi-threading in matlab

Resources