Pausing a loop with spacebar, resume again with spacebard - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: Pausing a loop with spacebar, resume again with spacebard (/thread-3467.html) Pages:
1
2
|
Pausing a loop with spacebar, resume again with spacebard - bigmit37 - May-25-2017 I have a loop that opens and closes images. from PIL import Image import time import subprocess for i in bio: p = subprocess.Popen(["C:\Program Files\IrfanView\i_view64.exe",'C:\\Users\Moondra\\Bioteck_charts\{}.png'.format(i)]) time.sleep(5) p.kill()As you can see the loop iterates every 5 seconds. I would like to pause this loop with "spacebar" if I see an image I would like to study more in detail, and then resume the loop once I press space bar again. I would also like to escape out of the entire loop if I press ESC I usually run my programs through IDLE or Jupyternotebook, but I could only find KeyPress readers (barring Tinker and GUIS which I haven't really studied yet) that work on the OS level. My OS is Windows 7. Here is a sample of what I tried to do. I tried variations of this code using multiple while loops etc: from PIL import Image import time import subprocess import pickle with open('Bioteck.pickle', 'rb') as file: bio = pickle.load(file) from msvcrt import getch key = ord(getch()) for i in bio: while key != 27: #escape key p = subprocess.Popen(["C:\Program Files\IrfanView\i_view64.exe",'C:\\Users\Moondra\\Bioteck_charts\{}.png'.format(i)]) time.sleep(5) p.kill() break if key == 32: #Space bar) raw_input() if key == 32: #kill p.kill() continue if key == 27: os._exit()However, pressing spacebar, just doesn't pause the loop. It just jumps to the next image in the loop. Looking for suggestions of a better way to do this. Thank you. RE: Pausing a loop with spacebar, resume again with spacebard - Larz60+ - May-25-2017 when you run your Popen, it should be done like: from the docs: https://docs.python.org/3.6/library/subprocess.html Quote:Popen.communicate(input=None, timeout=None) RE: Pausing a loop with spacebar, resume again with spacebard - bigmit37 - May-26-2017 (May-25-2017, 09:50 PM)Larz60+ Wrote: when you run your Popen, it should be done like: I tried implementing the code the way the docs provided by I"m still not fully understanding it, nor exactly how I can apply p.communicate to do what I want to do. from PIL import Image import time import subprocess from msvcrt import getch for i in bio: p = subprocess.Popen(["C:\Program Files\IrfanView\i_view64.exe", 'C:\\Users\Moondra\\Bioteck_charts\{}.png'.format(i)]) key = ord(getch()) try: outs, errs = p.communicate() if key == 32: ??? else: time.sleep(5) p.kill() print(outs) except Exception as e: p.kill() outs, errs = p.communicate()It opens up the image, but I have to manually close the image. Once, I close the image(pressing x in the corner of the window), the next image isn't popping up and the program hangs in Jupyter Notebook. I'm assuming outs is some sort of output data. It's not really printing anything despite explicitly writing print.
RE: Pausing a loop with spacebar, resume again with spacebard - nilamo - May-26-2017 First, if you have any desire for this to work cross-platform, you shouldn't use msvcrt. Or, at least, wrap it up so it'll work on other platforms, like so (stolen from myself): import sys def _find_getch(): try: import termios except ImportError: # Non-POSIX. Return msvcrt's (Windows') getch. import msvcrt return msvcrt.getch # POSIX system. Create and return a getch that manipulates the tty. import tty def _getch(): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch return _getch getch = _find_getch()Next, getch() is blocking, isn't it? So if you want images to pop in and out of existence without you needing to hit some key just to satisfy getch, then you'll need to use getch() in a separate thread, and use some sort of communication to get keypresses, so you don't block the image rotating. A queue would work fine for that. RE: Pausing a loop with spacebar, resume again with spacebard - bigmit37 - May-27-2017 (May-26-2017, 08:23 PM)nilamo Wrote: First, if you have any desire for this to work cross-platform, you shouldn't use msvcrt. Or, at least, wrap it up so it'll work on other platforms, like so (stolen from myself): I haven't looked in to queue yet but I read a little about threading. I found a moudle called keyboard that works really well at capturing key presses and the documentation is really well done.I've written some code below attempting to use threads, but the key presses are being read only in a very tiny time frame. While the program is sleeping during the time.sleep(3) portion, no keys are being read. Not sure why as I thought the separate thread would be a separate thread. from threading import Thread #function to use for threading def keyboard_press(): while p == True: if keyboard.is_pressed('down') == True: # here I'm using 'down' (down key) as the key to pause the cycle. print('yes') input() #I"m attempting to use this to pause the script/loop p.kill() #continue with open('C:\\Users\Moondra\\Bioteck.pickle', 'rb') as file: bio = pickle.load(file) #just getting a the name of images to open for i in bio[:5]: p = subprocess.Popen(["C:\Program Files\IrfanView\i_view64.exe",'C:\\Users\Moondra\\Bioteck_charts\{}.png'.format(i)]) t = Thread(target = keyboard_press, args =()) t.start() #I start the thread, which should run parallel with my program time.sleep(3) #normal continuation of the program, if the thread's `if` statement is not invoked. p.kill()What am I doing wrong here? RE: Pausing a loop with spacebar, resume again with spacebard - nilamo - May-28-2017 You call p.kill() all the time, regardless of whether down is pressed. Instead of starting a new thread for each file, I think you should start up a single thread before you start looping, and then use a Queue to pass keypresses to the main thread that you then process while looping over bio .
RE: Pausing a loop with spacebar, resume again with spacebard - bigmit37 - May-29-2017 (May-28-2017, 03:22 AM)nilamo Wrote: You call p.kill() all the time, regardless of whether down is pressed. Okay, I've tried to implement queue and I've simplified the code a little but, the thread is not affecting the main thread despite the conditions being met.import time import subprocess import pickle import keyboard import threading from threading import Thread import multiprocessing import queue import time with open('C:\\Users\Moondra\\Bioteck.pickle', 'rb') as file: bio = pickle.load(file) q = queue.LifoQueue(0) def keyboard_press(): while True: q.put(keyboard.is_pressed('down')) x = q.get() print(x) if x == True: time.sleep(20) #keyboard.wait('esc') t = Thread(target = keyboard_press, args= ()) t.start() if __name__ == "__main__": for i in bio[:5]: p = subprocess.Popen(["C:\Program Files\IrfanView\i_view64.exe",'C:\\Users\Moondra\\Bioteck_charts\{}.png'.format(i)]) time.sleep(3) p.kill()So if the condition is met, I want to freeze the main process by 20 seconds (time.sleep(20)). The condition is being met, because 'True' is being outputted by the print statement. But the main thread is not responding to the thread. Thank you. RE: Pausing a loop with spacebar, resume again with spacebard - nilamo - May-29-2017 (May-29-2017, 04:00 PM)bigmit37 Wrote: But the main thread is not responding to the thread. Thank you.The whole reason to use a queue, is so the main thread can read from it while the thread writes. If you sleep the thread, you only sleep the thread, not the main thread. So try this: from threading import Thread import queue import time import sys def _find_getch(): try: import termios except ImportError: # Non-POSIX. Return msvcrt's (Windows') getch. import msvcrt return msvcrt.getch # POSIX system. Create and return a getch that manipulates the tty. import tty def _getch(): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch return _getch getch = _find_getch() def keyboard_press(q): while True: key = getch() if key == b" ": q.put("down") if not q.empty(): val = q.get() if val == "done": break # if we don't process the event, put it back in the queue q.put(val) if __name__ == "__main__": q = queue.Queue() t = Thread(target=keyboard_press, args=(q, )) t.start() for i in range(50): print(i) time.sleep(0.01) delayed = True while delayed: delayed = False if not q.empty(): val = q.get() if val == "down": print("waiting...") delayed = True time.sleep(5) q.put("done") t.join()I got rid of the subprocess module, and the file loading, to simplify the problem to help show it. I also replaced the keyboard module, since it wasn't working on my machine. RE: Pausing a loop with spacebar, resume again with spacebard - bigmit37 - May-29-2017 (May-29-2017, 04:33 PM)nilamo Wrote:(May-29-2017, 04:00 PM)bigmit37 Wrote: But the main thread is not responding to the thread. Thank you.The whole reason to use a queue, is so the main thread can read from it while the thread writes. If you sleep the thread, you only sleep the thread, not the main thread. Thank you. For some reason, pressing 'down' (even tried pressing continously) is not evoking the time.sleep(5). I'm going over your code, I just want to make sure I fully understand it. It seems you are only putting into the queue, 'down' keypresses as opposed to all key presses. I'm not quite sure why you are putting the value back into the queue in the last line of this loop: if not q.empty(): val = q.get() if val == "done": break # if we don't process the event, put it back in the queue q.put(val)When we get the value, won't that always trigger the time.sleep(5)? Another thing I'm trying to wrap my head around the code is, it seems you are queing my the keypresses, and then unpacking their values within the main loop to trigger time.sleep(5). If I had pressed 'down' twice instead of once, would that mean the while loop would run twice before breaking out of the while loop and going back into the outer for loop? while delayed: delayed = False if not q.empty(): val = q.get() if val == "down": print("waiting...") delayed = True time.sleep(5)Last question, is despite the two process running in parallel, it seems I can't affect the flow of the main thread in real-time (for example, the exact second one thread receives a keypress 'down', I would like do something to the mainthread in realtime). From your code, it seems I'm limited to unpacking my side thread values somewhere in my main thread and that is the only point I can affect the main thread. Is this a limitation of programming? RE: Pausing a loop with spacebar, resume again with spacebard - nilamo - May-30-2017 You need some sort of communication between threads for the different threads to interact with each other. A thread running in the background can't just interrupt the main thread, and there isn't really a reason you'd want your code to arbitrarily start doing things you didn't want it to do. If you want it to stop, you need to specify when and how it stops. The reason there's some oddities with the queue, is because pushing the "done" message to the secondary thread was... probably not the right choice on my end. But the reason for putting the message back into the queue after pulling it out if it isn't the "done" message, is so the main thread can see the button press. Once a message is taken out of the queue, it's out of the queue for all threads, so we had to put it back in for the main thread. Which is a little backward, and, as you saw, a pretty clear indication that there's a better option. And that option is probably another queue, one for each direction of communication. As to why the "down" key wasn't working, I guess I didn't realize that's what you were going for, because I had it pausing when the space bar was hit. So here's a version that uses two different queues, to avoid wacky things like re-adding items. And also with the down key working to pause it. from threading import Thread import queue import time import sys def _find_getch(): try: import termios except ImportError: # Non-POSIX. Return msvcrt's (Windows') getch. import msvcrt return msvcrt.getch # POSIX system. Create and return a getch that manipulates the tty. import tty def _getch(): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch return _getch getch = _find_getch() def keyboard_press(keys, control): while True: key = getch() if key == b"\xe0": # a "control key" was hit, call again to get the actual key key = getch() # P == Down, K == Left, H == Up, M == Right, # I have no idea if this works the same way on linux if key == b"P": keys.put("pause") if not control.empty(): val = control.get() if val == "stop": break if __name__ == "__main__": key_presses = queue.Queue() control_messages = queue.Queue() t = Thread(target=keyboard_press, args=(key_presses, control_messages)) t.start() for i in range(50): print(i) time.sleep(0.01) delayed = True while delayed: delayed = False if not key_presses.empty(): val = key_presses.get() if val == "pause": print("waiting...") delayed = True time.sleep(5) control_messages.put("stop") t.join() |