I have a program that displays video in a tkinter window. When i want to close window i stopping video thread. But it doesn't stop. There are four functions:
def start_video(self):
if self.video is not None:
self.video_thread_stopped = False
try:
if not self.video_thread.is_alive():
self.video_thread = threading.Thread(target=self.video_stream)
self.video_thread.start()
except AttributeError:
if self.video_thread is None:
self.video_thread = threading.Thread(target=self.video_stream)
self.video_thread.start()
start_video function starts video thread
def video_stream(self):
_, frame = self.video.read()
str_time = time.time()
while frame is not None and getattr(self.video_thread, "running", True):
self.current_frame += 1
# resize and set image to label
frame = cv2.resize(frame, self.video_frame_size)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
imgtk = ImageTk.PhotoImage(image=Image.fromarray(cv2image))
self.video_label.config(image=imgtk)
self.video_label.image = imgtk
# waiting for frame rate
while time.time() - str_time < (1 / self.fps):
pass
str_time = time.time()
# reading next frame
_, frame = self.video.read()
print("exited from loop")
video_stream is the thread function
def pause_video(self):
if self.video_loaded:
self.video_thread_stopped = True
if self.video_thread is not None:
# stop video
self.video_thread.running = False
print('before join')
start_time = time.time()
while self.video_thread.is_alive():
if time.time() - start_time > 1:
break
print("after while")
self.video_thread.join()
print('after join')
pause_video has to kill thread and stop streaming video
def on_window_close(self):
self.pause_video()
print("thread stopped")
self.root.destroy()
on_window_close has to stop thread before closing tk window
(I have the following code)
root.protocol("WM_DELETE_WINDOW", w.on_window_close)
So when I start video thread and pressing a close button on the tk window - thread doesn't stop. Here is a terminal output
before join
after while
Can someone help me and tell why it doesn't stop the video_thread. Thanks!
I found the solution.
In the start_video method you have to make video_thread daemon:
def start_video(self):
if self.video is not None:
self.video_thread_stopped = False
try:
if not self.video_thread.is_alive():
self.video_thread = threading.Thread(target=self.video_stream)
self.video_thread.daemon = 1
# start audio
self.audio_class.start(self.current_frame, self.total_video_frames)
# start thread
self.video_thread.start()
except AttributeError:
if self.video_thread is None:
self.video_thread = threading.Thread(target=self.video_stream)
self.video_thread.daemon = 1
# start audio
self.audio_class.start(self.current_frame, self.total_video_frames)
# start thread
self.video_thread.start()
And in the pause_video method you need to remove line with video_thread.join()
def pause_video(self):
if self.video_loaded:
self.video_thread_stopped = True
if self.video_thread is not None:
# stop audio
# stop video
self.video_thread.running = False
print("video stopped")
self.audio_class.stop()
print("audio stopped")
# print('before join')
start_time = time.time()
while self.video_thread.is_alive():
# print("waiting for none")
if time.time() - start_time > 1:
# self.video_thread = None
break
Related
I have a program(python 3.9.10) that has a read queue and a write queue. One thread reads and once read, sends to the write queue and another thread writes.
All works fine unless there is an error. If there is, the threads do not stop.
In the following code I am simulating an error being detected in the read thread and trying to stop the threads from reading/writing so the program exits however the program/threads stay active and the program never finishes. If I remove the error simulation code, the threads stop and the program finishes.
I wish to handle the errors WITHIN the threads and if need be, stop the threads/program without throwing an error up
What am I doing wrong? Thanks
Here is a working example of my issue:
import pandas as pd
import datetime
import traceback
from queue import Queue
from threading import Thread
import time
dlQueue = Queue()
writeQueue = Queue()
dlQDone = False
errorStop = False
def log(text):
text = datetime.datetime.now().strftime("%Y/%m/%d, %H:%M:%S ") + text
print(text)
def errorBreak():
global dlQueue
global writeQueue
global errorStop
global dlQDone
dlQueue = Queue()
writeQueue = Queue()
errorStop = True
dlQDone = True
def downloadTable(t, q):
global dlQDone
global errorStop
while True:
if errorStop:
return
nextQ = q.get()
log("READING: " + nextQ)
writeQueue.put("Writing " + nextQ)
log("DONE READING: " + nextQ)
####sumulating an error and need to exit threads###
if nextQ == "Read 7":
log("Breaking Read")
errorBreak()
return
###################################################
q.task_done()
if q.qsize() == 0:
log("Download QUEUE finished")
dlQDone = True
return
def writeTable(t, q):
global errorStop
global dlQDone
while True:
if errorStop:
log("Error Stop return")
return
nextQ = q.get()
log("WRITING: " + nextQ)
log("DONE WRITING: " + nextQ)
q.task_done()
if dlQDone:
if q.qsize() == 0:
log("Writing QUEUE finished")
return
try:
log("PROCESS STARTING!!")
for i in range(10):
dlQueue.put("Read " + str(i))
startTime = time.time()
log("Starting threaded pull....")
dlWorker = Thread(
target=downloadTable,
args=(
"DL",
dlQueue,
),
)
dlWorker.start()
writeWorker = Thread(
target=writeTable,
args=(
"Write",
writeQueue,
),
)
writeWorker.start()
dlQueue.join()
writeQueue.join()
log(f"Finished thread in {str(time.time() - startTime)} seconds") # CANNOT GET HERE
log("Threads: " + str(dlWorker.is_alive()) + str(writeWorker.is_alive()))
except Exception as error:
log(error)
log(traceback.format_exc())
If I understood you correctly, you want to stop both threads in case there's some error that warrants it; you can do that with a threading.Event, and changing your queue reads to have a timeout.
import datetime
import time
import queue
import threading
dlQueue = queue.Queue()
writeQueue = queue.Queue()
stop_event = threading.Event()
def log(text):
text = datetime.datetime.now().strftime("%Y/%m/%d, %H:%M:%S ") + text
print(text)
def downloadTable(t: str, q: queue.Queue):
while not stop_event.is_set():
try:
nextQ = q.get(timeout=1)
except queue.Empty:
continue
log("READING: " + nextQ)
writeQueue.put("Writing " + nextQ)
log("DONE READING: " + nextQ)
if nextQ == "7":
log("Breaking Read")
stop_event.set()
break
q.task_done()
log("Download thread exiting")
def writeTable(t, q):
while not stop_event.is_set():
try:
nextQ = q.get(timeout=1)
except queue.Empty:
continue
log("WRITING: " + nextQ)
log("DONE WRITING: " + nextQ)
q.task_done()
log("Write thread exiting")
def main():
log("PROCESS STARTING!!")
for i in range(10):
dlQueue.put(f"{i}")
log("Starting threaded pull....")
dlWorker = threading.Thread(
target=downloadTable,
args=(
"DL",
dlQueue,
),
)
dlWorker.start()
writeWorker = threading.Thread(
target=writeTable,
args=(
"Write",
writeQueue,
),
)
writeWorker.start()
dlWorker.join()
writeWorker.join()
if __name__ == "__main__":
main()
I want to capture and store the images from live streams using two threads. I want to stop the threads with Ctrl + C keyboard interrupt. I am calling a function from
if name == "main":
but it is not working. I am using spyder with an anaconda environment. I also tried it using terminal but it is not working. Can anyone explain why and how it will work?
The code is as follow
class ImageGrabber(threading.Thread):
def __init__(self, ID):
threading.Thread.__init__(self)
self.ID=ID
self.cam=cv2.VideoCapture(ID)
self.fps = self.cam.get(cv2.CAP_PROP_FPS)
self.w=self.cam.get(cv2.CAP_PROP_FRAME_WIDTH)
self.h=self.cam.get(cv2.CAP_PROP_FRAME_HEIGHT)
(self.grabbed, self.frame) =self.cam.read()
print(f"Camera Opened with fps {self.fps}, width = {self.w}, and height = {self.h}")
plt.imshow(self.frame)
plt.show()
self.stopped = False
def run(self):
global frames
global exitProgram
# print("Entered Grabbed Run ******:")
while not self.stopped:
if not self.grabbed or exitProgram is True:
print("Exit Command reached")
self.stop()
self.cam.release()
else:
(self.grabbed, self.frame) =self.cam.read()
frames.put(self.frame)
def stop(self):
self.stopped = True
class ImageSaver(threading.Thread):
def __init__(self,grabber,codec):
threading.Thread.__init__(self)
global saveImages
fourcc = cv2.VideoWriter_fourcc(*codec)
self.fName=self.getDateStamp() + '.avi'
if saveImages is False:
self.out = cv2.VideoWriter(self.fName, fourcc,grabber.fps ,(int(grabber.w),int(grabber.h)))
def getDateStamp(self):
filedate = str(datetime.now())
filedate = filedate[0:-7]
filedate = filedate.replace(':', '_')
filename = filedate
#print(filename)
return filename
def run(self):
global frames
global saveImages
while True:
# print("Entered Loop 1:")
if(not frames.empty()):
# print("Entered frame showing:")
self.Currframe=frames.get()
if saveImages is False:
self.out.write(self.Currframe)
else:
self.imgName=self.getDateStamp() + '.tiff'
cv2.imwrite(self.imgName,self.Currframe)
print(f"Queue Size in writing = {frames.qsize()}")
# print("Show Image")
elif exitProgram is True:
print("Exit Command Main reached")
print(f"Final Queue Size at exit = {frames.qsize()}")
break
def initStart(*args):
argsList=sum(list(args),[])
argsLen = len(argsList)
if(argsLen==2):
ID = int(argsList[0])
codec = argsList[1]
elif(argsLen==1):
ID = int(argsList[0])
codec = 'XVID'
else:
ID = 0
codec = 'XVID'
grabber = ImageGrabber(ID)
saver = ImageSaver(grabber,codec)
grabber.start()
saver.start()
e = threading.Event()
# main.join()
# grabber.join()
print ('Press CTRL-C to interrupt')
while grabber.isAlive():
try:
time.sleep(1) #wait 1 second, then go back and ask if thread is still alive
print ('Sleep...')
except: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set() #set the flag that will kill the thread when it has finished
exitProgram=True
print ('Exiting...')
grabber.join()
saver.join()
if __name__ == "__main__":
full_cmd_arguments = sys.argv
# Keep all but the first. First contains the path of script or file
argument_list = full_cmd_arguments[1:]
initStart(argument_list)
If I change the program in such a way that keyboardInterrupt is called without any function then it works
grabber = ImageGrabber(0)
main = Main(grabber,'XVID')
grabber.start()
main.start()
e = threading.Event()
print ('Press CTRL-C to interrupt')
while grabber.isAlive():
try: time.sleep(1) #wait 1 second, then go back and ask if thread is
except KeyboardInterrupt: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set() #set the flag that will kill the thread when it has finished
exitProgram=True
print ('Exiting...')
grabber.join()
main.join()
The above code is called directly as python script then it works. Any reason?
Any idea? how it can work?
...
print ('Press CTRL-C to interrupt')
try:
while grabber.isAlive():
time.sleep(1) #wait 1 second, then go back and ask if thread is
except KeyboardInterrupt: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set()
...
Try this way.
The reason why KeyboardInterrupt was not working is because the while loop was scoping the try except statement, so it would repeat it continuously even though you were pressing the KeyboardInterrupt.
Hope this helps!
I want to open a function which shows the Loading VIDEO on the screen by cv2.imshow() method called.
First I want to demostrat the code and then the problem.
import cv2
import threading
def Load():
video = cv2.VideoCapture('Loading.mov')
if video.isOpened() == True:
cv2.namedWindow("The Video")
cv2.moveWindow("The Video", 500,200)
elif video.isOpened() == False:
print('No Data For Loading Video')
return 0
while video.isOpened():
_, frame = video.read()
if _ == True:
cv2.imshow("The Video",frame)
if cv2.waitKey(10) & 0xff == 27:
break
if _ == False :
break
cv2.destroyAllWindows()
video.release()
t = threading.Thread(target = Load)
t.start()
Now, Problem :
When I call the t.start() FOR THE FIRST TIME the thread gets started and shows the video properly.
After the loop breaks, if I try again to make a new t as a thread and .start() it again, it doesn't show anything at all ! Not errors, nothing !
I am using spyder to re run the codes.
And I want to re run the video whenever I needed .
Now, Where is the problem ?
I tried your code with an mp4-Video (http://techslides.com/demos/sample-videos/small.mp4) and it works. I converted said video to mov with https://video.online-convert.com/convert-to-mov and it still works...
Although I may have an educated guess:
you should try to do every call with a fresh instance of cv2.
I assume the problem could be, that the second call of the thread inherits the state of the first call (especially the internal state of cv2), since it's only a function an therefor the video is in state "already played" or something and doesn't show anything more.
so if you put everything in a class and call with a new instance of cv2 everytime Load() is called, it might work.
import cv2
import threading
class Video:
def play(self):
video = cv2.VideoCapture('small.mov')
if video.isOpened() == True:
cv2.namedWindow("The Video")
cv2.moveWindow("The Video", 500,200)
elif video.isOpened() == False:
print('No Data For Loading Video')
return 0
while video.isOpened():
_, frame = video.read()
if _ == True:
cv2.imshow("The Video",frame)
if cv2.waitKey(10) & 0xff == 27:
break
if _ == False :
break
cv2.destroyAllWindows()
video.release()
def Load():
v=Video()
v.play()
del v
t = threading.Thread(target = Load)
t.start()
A straightforward application of:
Prompt for user input.
Start countdown (or count up) timer.
Wait on user input (as timer counts down/up).
If user inputs a correct response, conditional statement 1
Else, conditional statement 2
If user exceeds a preset time, timer expires and user is directed accordingly.
I've tried some of the solutions offered on this web site. However, in all cases, the count up/down timer seems to stop once the user input prompt is generated. In other words, the timer does not seem to run as a separate (background) thread.
import threading
import time
class TimerClass(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.event = threading.Event()
self.count = 10
def run(self):
while self.count > 0 and not self.event.is_set():
print (self.count)
int_answer = (int(input('Enter your age: '), base = 10)
str_answer = str(int_answer)
while str_answer == '':
self.count -= 1
self.event.wait(10)
if (int_answer > 50) :
<do something>
else:
<do somethingelse>
def stop(self):
self.event.set()
tmr = TimerClass()
tmr.start()
time.sleep(1)
tmr.stop()
The program should go to condition 1 if a response of > 50 is provided; else, go to condition 2 if a response of <= 50 is entered. The timer should expire after a period of 10 secs. if the user has not provided a response (with user notification).
I've adapted the code from this answer to your needs:
import threading
import queue
import time
def read_kbd_input(inputQueue):
print('Ready for keyboard input:')
while (True):
input_str = input()
inputQueue.put(input_str)
def main():
inputQueue = queue.Queue()
inputThread = threading.Thread(target=read_kbd_input, args=(inputQueue,), daemon=True)
inputThread.start()
start_time = time.time()
while True:
if (inputQueue.qsize() > 0):
input_str = inputQueue.get()
# Insert your code here to do whatever you want with the input_str.
print("input_str = {}".format(input_str))
break
time.sleep(0.1) # poll each 100ms
if time.time() - start_time > 2: # timeout after 2 sec
break
print("End.")
if (__name__ == '__main__'):
main()
i manipulate a sensor : HC SR04 to capture a distance.
I'm a newbie in Python and RPI. My code work, I capture distance during a time but one moment the script stop...
My code :
GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER = 23
GPIO_ECHO = 24
GPIO.setup(GPIO_TRIGGER, GPIO.OUT)
GPIO.setup(GPIO_ECHO, GPIO.IN)
def main():
global state
print("ultrasonic")
while True:
print "1s second refresh.."
time.sleep(1)
i = 0
datas = []
average = 0
while i< 1:
GPIO.output(GPIO_TRIGGER, False)
time.sleep(C.time['count'])
GPIO.output(GPIO_TRIGGER, True)
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
while GPIO.input(GPIO_ECHO) == 0:
start = time.time()
while GPIO.input(GPIO_ECHO) == 1:
stop = time.time()
distance = (stop-start) * 17000
print "Distance : %.1f" % distance
average = F.getAverage(datas)
print "Average: %.1f" % average
GPIO.cleanup()
The code stop here
while GPIO.input(GPIO_ECHO) == 0:
start = time.time()
THE SOLUTION : with a sample timeout :
now = time()
while GPIO.input(self.gpio_echo) == 0 and time()-now<waitTime:
pass
I am also mucking about with this sensor. My code executes similar to yours and I need no timeout for it to work.
The one difference I can find is this:
while i< 1:
GPIO.output(GPIO_TRIGGER, False)
time.sleep(C.time['count'])
I don't know how long the sleep time is here, but it might be that that's causing the problem. If it would be similar to mine setting the Trigger to false would be directly after the setup of the in/out pins instead, and then there's a two second wait to eliminate noise. Your wait time might be lower, I can't tell. There should be no need to set the trigger to false again just before you send the pulse and, I don't know, but it might be causing a false start. I would change it to this to work similarly to mine and then remove the setting to false in the while loop.
GPIO.setup(GPIO_TRIGGER, GPIO.OUT)
GPIO.setup(GPIO_ECHO, GPIO.IN)
GPIO.output(GPIO_TRIGGER, False)
print("Waiting for sensor to settle\n")
time.sleep(2)
I'm not sure if this will solve the issue without the need for a timeout, but I don't seem to need one.
I've written a module for making an object of the sensor which then allows for some more readable scripting. I'm also quite new to python and not at all an experienced programmer so fun errors might be there somewhere, but it's here below if you want to use it or just compare code:
#! /usr/bin/python3
# dist.py this is a module for objectifying an ultrasonic distance sensor.
import RPi.GPIO as GPIO
import time
class Distancer(object):
#init takes an input of one GPIO for trigger and one for echo and creates the object,
#it searches for a calibration file in the working directory (name)Const.txt, if none
#is found it will initiate a calibration
def __init__(self, trig, cho, name):
self.trigger = trig
self.echo = cho
self.name = name
self.filename = self.name + 'Const.txt'
GPIO.setup(self.trigger, GPIO.OUT)
GPIO.setup(self.echo, GPIO.IN)
GPIO.output(self.trigger, False)
print("Waiting for sensor to calm down")
time.sleep(2)
try:
with open(self.filename, "r") as inConst:
self.theConst = int(inConst.read())
except (OSError, IOError) as e:
print("Not calibrated, initializing calibration")
self.calibrate()
with open(self.filename, "r") as inConst:
self.theConst = int(inConst.read())
#Returns the echo time
def measureTime(self):
GPIO.output(self.trigger, True)
time.sleep(0.00001)
GPIO.output(self.trigger, False)
while GPIO.input(self.echo) == 0:
pulse_start = time.time()
while GPIO.input(self.echo) == 1:
pulse_end = time.time()
pulse_duration = pulse_end - pulse_start
return pulse_duration
#Returns a distance in cm
def measure(self):
return self.measureTime() * self.theConst
#Makes you set up the sensor at 3 different distances in order to find the
#relation between pulse time and distance, it creates the file (name)Const.txt
#in the working directory and stores the constant there.
def calibrate(self):
ten = []
thirty = []
seventy = []
print("Make distance 10 cm, enter when ready")
input()
for i in range(30):
ten.append(10/self.measureTime())
time.sleep(0.2)
print("Make distance 30 cm, enter when ready")
input()
for i in range(30):
thirty.append(30/self.measureTime())
time.sleep(0.2)
print("Make distance 70 cm, enter when ready")
input()
for i in range(30):
seventy.append(70/self.measureTime())
time.sleep(0.2)
allTime = ten + thirty + seventy
theOne = 0.0
for i in range(90):
theOne = theOne + allTime[i]
theOne = theOne / 90
with open(self.filename, "w") as inConst:
inConst.write(str(round(theOne)))
#Will continually check distance with a given interval until something reaches the
#treshold (cm), takes an argument to set wether it should check for something being
#nearer(near) or farther(far) than the treashold. Returns True when treshold is reached.
def distWarn(self, nearfar, treashold):
if nearfar.lower() == "near":
while True:
if self.measure() < treashold:
return True
break
time.sleep(0.2)
if nearfar.lower() == "far":
while True:
if self.measure() > treashold:
return True
break
time.sleep(0.2)
#Will measure with a second interval and print the distance
def keepGoing(self):
while True:
try:
print(str(round(self.measure())) + ' cm')
time.sleep(1)
except KeyboardInterrupt:
print("Won't keep going")
break
I've run it with the code below to test it and everything seems to work. First time it's run it will prompt you to calibrate the sensor by putting it at different distances from something.
#! /usr/bin/python3
import RPi.GPIO as GPIO
import time
import dist as distancer
GPIO.setmode(GPIO.BOARD)
TRIG = 16
ECHO = 18
dist = distancer.Distancer(TRIG, ECHO, 'dist')
def main():
global dist
print(str(round(dist.measureTime(),5)) + ' s')
print(str(round(dist.measure())) + ' cm')
dist.distWarn('near', 10)
print('Warning, something nearer than 10 cm at ' + time.asctime( time.localtime(time.time()) ))
dist.distWarn('far', 10)
print('Warning, something further than 10 cm at ' + time.asctime( time.localtime(time.time()) ))
dist.keepGoing()
GPIO.cleanup()
print('Fin')
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
GPIO.cleanup()
print("Exiting")
time.sleep(1)
I am pretty sure you want
while GPIO.input(GPIO_ECHO)==GPIO.LOW:
start = time.time()
while GPIO.input(GPIO_ECHO) == GPIO.HIGH:
stop = time.time()
I don't think GPIO.input naturally returns zeros or ones, you can test that though.
Not really, I think that i lost the signal, i'll try a timeout in
while GPIO.input(GPIO_ECHO)==GPIO.LOW:
start = time.time()
I think that my program wait indefinitely a signal but he stay to 0
I know this is an old question. The cause of the problem was described in this question https://raspberrypi.stackexchange.com/questions/41159/...
The solution is to add a timeout, like the OP did, to the while loops similar to this:
# If a reschedule occurs or the object is very close
# the echo may already have been received in which case
# the following will loop continuously.
count=time.time()
while GPIO.input(GPIO_ECHO)==0 and time.time()-count<0.1:
start = time.time()
...
# if an object is not detected some devices do not
# lower the echo line in which case the following will
# loop continuously.
stop = time.time()
count=time.time()
while GPIO.input(GPIO_ECHO)==1 and time.time()-count<0.1:
stop = time.time()