Break a loop externally with the button that started the loop - python

I want to break a loop with the same button that I started the loop with.
For all intents and purposes, it does what I need it to, up until I click the button again. If I hit the button again, it crashes. Right now with the debug visual setup, I can hit q to break the loop. But down the road I obviously want to turn that off and just have it all go through the button instead.
I should also note that the button does not update unless the window is interacted with/moved. This goes for when the button is pushed. It doesn't crash until you move the window.
def bttnup():
global is_on
#starts autoaccept
if is_on:
button.config(image = selected,
bg=selected_bg
)
start()
is_on = False
else:
#Stops autoaccept
button.config(image = unselected,
bg=unselected_bg,
)
is_on = True
#Object detection and keystroke
def start():
while(True):
screenshot = wincap.get_screenshot()
points = vision_accept.find(screenshot, .325, 'rectangles')
if cv.waitKey(1) == ord('q'):
cv.destroyAllWindows()
break
#Button
button = Button(window,
image=unselected,
command=bttnup)
button.pack()
I have also tried:
def bttnup():
#starts autoaccept
if button['bg'] == 'unselected_bg':
threading.join(1)
button.config(image=selected,
bg=selected_bg
)
start()
return
#Stops autoaccept
if button['bg'] == 'selected_bg':
threading.start()
button.config(image = unselected,
bg=unselected_bg,
)
return
#start object detection
def start():
i = 1
while button['bg'] == 'selected_bg':
i = i+1
# get an updated image of the game
screenshot = wincap.get_screenshot()
# screenshot = cv.resize(screenshot, (800, 600))
points = vision_accept.find(screenshot, .325, 'rectangles')
sleep(5)
#Button!
button = Button(window,
image=unselected,
command=lambda: bttnup())

So I went and changed a majority of the code to use .after instead of using a while statement. Its not as clean looking, but it does what it needs to.
created a function for on, and one for off and have the button cycle when the button is fixed. when the on button is pushed it calls to the start function which then cycles every second.
# initialize the WindowCapture class
wincap = WindowCapture()
# Initialize the Vision class
vision_accept = Vision('accept.jpg')
#window parameters
window = Tk()
window.resizable(False, False)
window.title('AutoAccept')
unselected = PhotoImage(file='Button_unselected.png')
selected = PhotoImage(file='matching.png')
unselected_bg='white'
selected_bg='black'
is_on = True
def start_on():
global is_on
#starts autoaccept
button.config(image = selected,
bg=selected_bg,
command = start_off
)
start()
is_on = True
def start_off():
global is_on
button.config(image = unselected,
bg=unselected_bg,
command = start_on
)
is_on = False
#start object detection
def start():
if is_on:
# get an updated image of the game
screenshot = wincap.get_screenshot()
points = vision_accept.find(screenshot, .32, 'rectangles')
window.after(500, start)
button = Button(window,
image=unselected,
command=start_on
)
button.pack()
window.mainloop()

Related

Tkinter OpenCV Python - Fail to allocate memory after 2h

I have created a simple video game BOT using openCV 4.6.0 and 64-bit python.
Program flow listed below;
1. Open GUI using tkinker
2. Upon pressing start a new process opens in a loop (main loop)
3. Program takes a screenshot of my screen (850x650 pixels) and saves as PNG
4. Load in the screenshot to openCV MAT and start image processing with cv.matchtemplate
5. Repeat main loop. After 1 hour kill this process, open a new process and repeat
The reason why I have this running on a separate process was because I thought that maybe openCV was locking memory in the background so I'm essentially doing a "Reboot".
All the OpenCV related code is below;
#Static Images
hp_img = cv.imread(LOCAL_DIRECTORY + 'hp.png', cv.COLOR_BGR2GRAY)
worldmap_img = cv.imread(LOCAL_DIRECTORY + 'worldmap.png', cv.COLOR_BGR2GRAY)
settings_menu = cv.imread(LOCAL_DIRECTORY + 'settings.png', cv.COLOR_BGR2GRAY)
black_screen = cv.imread(LOCAL_DIRECTORY + 'blackscreen.png', cv.COLOR_BGR2GRAY)
def checkBlackScreen():
global threshold
screenShot = pyautogui.screenshot(LOCAL_DIRECTORY + "screen.png", region=(0, 0, 850, 650))
screen_img = cv.imread(LOCAL_DIRECTORY + 'screen.png', cv.COLOR_BGR2GRAY)
result = cv.matchTemplate(screen_img, black_screen, cv.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv.minMaxLoc(result)
del result
del screen_img
del screenShot
gc.collect()
if (max_val > threshold):
print("[BAD] Stuck in black screen")
return True
else:
print("[Good] Not stuck in black screen : ", max_val)
return False
The main loop just calls the function above and presses buttons accordingly. Most of the program provided below;
#Main entry of the program
if __name__ == '__main__':
GUI()
#GUI using TK
def GUI():
root = Tk()
frm = ttk.Frame(root, padding=10)
frm.grid()
ttk.Label(frm, text="Hello World!").grid(column=0, row=0)
ttk.Button(frm, text="Quit", command=root.destroy).grid(column=1, row=0)
ttk.Button(frm, text="Start", command=startBotProcess).grid(column =2, row=0)
ttk.Button(frm, text="Stop", command=stopBot).grid(column =3, row=0)
ttk.Button(frm, text="Set WorldMap XY", command=StartListenClick).grid(column =4, row=0)
#ttk.Button(frm, text="Set WorldMap XY", command=checkEXPMap).grid(column =4, row=0)
root.mainloop()
#Set world map X,Y
def on_click(x, y, button, pressed):
listen_for_mouse = False
if pressed:
global MOUSE_X
global MOUSE_Y
MOUSE_X = x
MOUSE_Y = y
print('World Map set to : ',(MOUSE_X, MOUSE_Y))
listen_for_mouse = False
if not pressed:
# Stop listener
return False
# Collect events until released
def StartListenClick():
listen_for_mouse = True
with Listener(on_click=on_click) as listener:
if (listen_for_mouse == True):
listener.join()
#Start new BOT Process
def startBotProcess():
print("[SYSTEM] New BOT Process STARTED")
time_start = time.time()
ps = Process(target=processMain, args=(MOUSE_X,MOUSE_Y))
ps.start()
ps.join()
stopBot()
print("[SYSTEM] Bot Process CLOSED")
if (time.time() - time_start > SLEEP):
print("[SYSTEM] Restart Timer Created")
t = Timer(SLEEP_TIME, botSleep)
t.start()
def processMain(x,y):
global MOUSE_X, MOUSE_Y
MOUSE_X = x
MOUSE_Y = y
startBot()
def startBot():
print("[SYSTEM] STARTING BOT - Click Window")
global running
global GAME_STATE
global CUT_SCENE
global CHAT_FOUND
global TRANSITION_TIME
printData()
time.sleep(2)
running = True
start_time = time.time()
while (running == True):
if (GAME_STATE == 0): #First, lets see if we are in a map
if (checkMap() == True):
GAME_STATE = 1
else:
print("GAME_STATE is 0 - Cannot determine if player is on a map")
debugger()
Now eventually after ~ 2hours of running the python command prompt will show a fail to allocate memory error
enter image description here
The line that its referring too is the cv.matchtemplate.
Task manager shows the program only using 50mb of memory. Total memory usage on my computer is 51%
Does anyone know what I can do to resolve this? Task manag
Updated screenshot with task manager and failed allocation;
enter image description here

Why is the execution of my code (the program) happening faster, every time I hit the start button?

I just recently made a pomodoro clock implementation on python and I was facing some trouble with its execution but I came up with a solution for it but I observed a discrepancy in its execution.
So my whole code and implementation looks something like this (my code is not clean or pretty; sorry about that, I am one of those people who uses a mixture of tabs and spaces):
from tkinter import *
from time import *
from threading import Thread
# ---------------------------- CONSTANTS ------------------------------- #
PINK = "#e2979c"
RED = "#e7305b"
GREEN = "#9bdeac"
YELLOW = "#f7f5dd"
FONT_NAME = "Courier"
WORK_MIN = 25
SHORT_BREAK_MIN = 5*60000
LONG_BREAK_MIN = 20*60000
class Reset:
def __init__(self):
self.click = False
self.activation = False
def button_command(self):
self.click = True
def timer_command(self):
return self.click
# ---------------------------- TIMER RESET ------------------------------- #
timer_reset = Reset()
reset = timer_reset.click
# ---------------------------- TIMER MECHANISM ------------------------------- #
def timer_mechanism():
minute=0
if counter[0]<60000:
if counter[0] % 1000 == 0 and counter[0]/1000<10:
canvas.itemconfig(clock_counter, text=(f"00:0{int(counter[0]/1000)}"))
canvas.update_idletasks()
elif counter[0]% 1000==0 and counter[0]/1000>=10:
canvas.itemconfig(clock_counter, text=(f"00:{int(counter[0] / 1000)}"))
canvas.update_idletasks()
elif counter[0]>=60000:
if counter[0]%1000==0:
transition=int(counter[0]/1000)
while transition>=60:
minute+=1
transition-=60
second=transition
if second<10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:0{second}"))
canvas.update_idletasks()
elif second>=10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:{second}"))
canvas.update_idletasks()
elif minute>=10 and second<10:
canvas.itemconfig(clock_counter, text=(f"{minute}:0{second}"))
canvas.update_idletasks()
else:
canvas.itemconfig(clock_counter,text=(f"{minute}:{second}"))
canvas.update_idletasks()
# -----------------------------New countdown mechanism----------------------------#
i = [4]
def start_countdown():
if not timer_reset.click and i[0] > 0:
# keep listening to the function that receives from the button while doing an after for the 25 min and 10 min respectively.3
timer.config(text="Work", fg=GREEN)
window.update_idletasks()
# the solution to this problem is to break up the after process instead of doing it all at the same time make use of small increments so that this after could finish withing a second or two and then go to another function that contains all the timer_reset.click values update and come everytime until we reach a limit somewhere probably an array value that will be updated until we get to the 25 minute mark so this insures as the signal from the button will be accepted.
execute("Work")
# start=time()
# end=time()
# while end-start<5:
# end=time()
# timer_reset.click=timer_reset.timer_command()
else:
window.destroy()
timer_reset.click = False
i[0] = 4
beginning()
def rest():
global start_time
start_time=0
timer_reset.activation = True
if not timer_reset.click:
global restloop
restloop = True
global workloop
workloop = False
timer.config(text="Break", fg=PINK)
if i[0] == 4:
global frame
frame = Frame(window, width=20, height=20)
frame.grid(row=3, column=1)
tick = Label(frame, text="✔", font=(FONT_NAME, 12, "bold"), bg=YELLOW, fg=GREEN)
tick.pack(side=LEFT)
window.update_idletasks()
if i[0] > 0:
execute("Break")
if timer_reset.click:
timer_reset.click = False
window.destroy()
beginning()
else:
window.destroy()
beginning()
else:
window.destroy()
i[0] = 4
timer_reset.click = False
beginning()
i[0] -= 1
counter=[0]
def execute(identifier):
if identifier=="Work":
window.after(1,mirror,LONG_BREAK_MIN)
if identifier=="Break":
window.after(1,mirror,SHORT_BREAK_MIN)
def mirror(value):
if timer_reset.click:
window.destroy()
i[0]=4
timer_reset.click=False
counter[0]=0
beginning()
elif counter[0]<value:
counter[0]+=1
timer_mechanism()
if value==LONG_BREAK_MIN:
execute("Work")
if value==SHORT_BREAK_MIN:
execute("Break")
elif value==LONG_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
rest()
elif value==SHORT_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
start_countdown()
# ---------------------------- UI SETUP ------------------------------- #
def beginning():
global window
window = Tk(className="Pomodoro")
window.title("Pomodoro")
window.config(pady=50, padx=100, bg=YELLOW)
window.wm_state("normal")
global timer
timer = Label(text="Timer", font=(FONT_NAME, 50, "bold"), bg=YELLOW, foreground=GREEN)
timer.grid(row=0, column=1)
global canvas
canvas = Canvas(width=200, height=223, bg=YELLOW, highlightthickness=0)
tomato = PhotoImage(file="tomato.png")
canvas.create_image(100, 100, image=tomato)
global clock_counter
clock_counter=canvas.create_text(100, 120, text="00:00", fill="white", font=(FONT_NAME, 35, "bold"))
canvas.grid(row=1, column=1)
start = Button(text="Start", highlightthickness=0, borderwidth=0, command=start_countdown)
start.grid(row=2, column=0)
end = Button(text="Reset", highlightthickness=0, borderwidth=0, command=timer_reset.button_command)
end.grid(row=2, column=2)
window.mainloop()
beginning()
And the UI part of my code looks something like this:
So the discrepnancy I was talking about was that after starting the program and the countdown is happening if by mistake or whatever reason the user presses the Start button again the countdown starts to happen faster and if you keep pressing the start button without hitting the reset button it starts counting down even faster and faster.
Now I think I understand why this might be but this is like a sliver of what the solution might be, I might be completely wrong for all I know but is it because of the after method since every time the user hits the button he is starting another thread of the after method process and this kind of creates a sense/illusion of multiprocessing(parallelism)? I really don't know, to be honest. I might be stretching too far, but please any type of explanation would really be helpful even if it is a jab at my answer. I am very genuinely curious as to why this is happening.
Any help/suggestion will be really helpful.

Change the label text, Tkinter

Here, is my code snippet:
def message(name, button):
button['state'] = DISABLED
mgs_label = ttk.Label(root)
if button["text"] == "Encryption":
mgs_label.pack_forget()
mgs = encryption(name)
mgs_label = ttk.Label(root, text=mgs).pack(side=LEFT)
if button["text"] == "Decryption":
mgs_label.pack_forget()
mgs = decryption(name)
mgs_label = ttk.Label(root, text=mgs).pack(side=LEFT)
When i am click the button whether it is encryption button or decryption button it is comes at mentioned position.
Here is the snapshot :
image1
And When i am click the another button the text comes after the previous one, i want to remove the previous text and then the last mgs should be displayed.
second snapshot : image2
Even i tried to make global variables but then the problem is encryption or decryption is done but the mgs is not showing on the GUI here is the code for that :
encryption_label = ttk.Label(root)
decryption_label = ttk.Label(root)
def message(name, button):
button['state'] = DISABLED
global encryption_label, decryption_label
if button["text"] == "Encryption":
if decryption_label.winfo_exists():
decryption_label.pack_forget()
mgs = encryption(name)
encryption_label["text"] = mgs
encryption_label.pack(side=LEFT)
if button["text"] == "Decryption":
if encryption_label.winfo_exists():
encryption_label.pack_forget()
mgs = decryption(name)
decryption_label["text"] = mgs
decryption_label.pack(side=LEFT)
I suggest creating the label only once and use label.configure to change the text.
You should provide a working example instead of a code snippet. I made one for you.
Thank you for your question.
import tkinter as tk
from tkinter import ttk
def e():
message(None,x_button)
def d():
message(None,y_button)
def message(name, button):
#button['state'] = tk.DISABLED
if button["text"] == "Encryption":
mgs_label.configure(text = 'encrypted stuff')
if button["text"] == "Decryption":
mgs_label.configure(text = 'decrypted stuff')
root = tk.Tk()
x_button = tk.Button(root,text = 'Encryption',
command = e)
y_button = tk.Button(root,text = 'Decryption',
command = d )
mgs_label = ttk.Label(root)
y_button.pack()
x_button.pack()
mgs_label.pack()
root.mainloop()

How to Update Tkinter Label while looking for keyboard input

I am Trying to make a tkinter based Cube Timer Program for which I have done a console based program before I wanted to improve on it by making a GUI for it... I have made some tkinter projects before and I have a decent knowledge of how stuff works the thing is I want the Label in the tkinter window to update in every second while listening for a spacebar key on press I couldn't figure out a way to do it can someone help me?
def on_press(key):
global chk,start,end,timer_label
print('{0} pressed'.format(key))
if key == Key.space:
if chk == True:
end = time.time()
chk = False
messagebox.showinfo('Your Time',"Your Time =>"+str(round(end-start,3)))
def on_release(key):
global chk,start,end,timer_label
if key == Key.space:
if chk == False:
start = time.time()
chk = True
with Listener(on_press=on_press,on_release=on_release) as listener:
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
global timer_label
timer_label = tk.StringVar()
timer_label.set("0.0")
tk.Label(timer_frame,text = timer_label.get()+"s",font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
main.mainloop()
This is what I have tried but didn't work
You need main.after to periodically run a function like updating the label. To listen to keyboard event, you need main.bind. See example below.
Enhancement: use tk.Label(..., textvariable=timer_label) means the label will automatically update when you set timer_label
import tkinter as tk
def key_press(event):
if event.keysym == 'space':
print('pressed')
def key_release(event):
if event.keysym == 'space':
print('released')
def update_label():
# get the time from the string
time = float(timer_label.get()[:-1])
# increment the time and put it back in timer_label
timer_label.set(str(time+1) + 's')
# calling this function again 1000ms later, which will call this again 1000ms later, ...
main.after(1000, update_label)
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
#textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
timer_label = tk.StringVar()
timer_label.set("0.0s")
# using textvariable, you can simply update timer_label and it will be reflected
tk.Label(timer_frame,textvariable = timer_label ,font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
# bind keypress and keyrelease to the functions
main.bind("<KeyPress>", key_press)
main.bind("<KeyRelease>", key_release)
# call update label function for the first time
update_label()
main.mainloop()

Python cancel while loop via new window button

So here´s the deal. I have a button. That button triggers a while loop and opens a new window with a "cancel" button. Through that cancel-button I want to terminate the while loop.
NEW APPROACH:
def process():
parent,child = Pipe()
p = process(target=new_window(),args=(child))
p.start()
parent.send("True")
while true:
if child.recv()!="False":
do something
else:
p.join()
def new_window(p):
id = "cancel"
window = Toplevel(root)
window.geometry("240x160")
state = p.recv()
def closewindow():
global state
state = "False"
p.send(state)
window.destroy()
label = Label(window, text="Überwache...").place(x=90,y=60)
buttonc = Button(window,text="Abbrechen!",fg="black",command=closewindow).place(relx=0.35, rely=0.5)
The idea is that the parent (def process) executes the while loop as long as it doesn´t get a "False" from the child process (new_window).
Do you see any errors? Can´t test it right now, since the code replacing my placeholder "do something" is another difficult task...
OLD POST:
state variable
state = bool(True)
button that triggers loop and new window
button3 = Button(containerd,text="Starte Überwachung",fg="black",height=10,width=16,command=sound)
function new window
def new_window():
id = "cancel"
window = Toplevel(root)
window.geometry("240x160")
def closewindow():
global state
state = bool(False)
window.destroy()
label = Label(window, text="Überwache...").place(x=90,y=60)
buttonc = Button(window,text="Abbrechen!",fg="black",command=closewindow).place(relx=0.35, rely=0.5)
function while loop
def sound():
global inp
global state
new_window()
while state:
l,data = inp.read()
if l > 60:
subprocess.call(['/home/pi/RPi_Cam_Web_Interface/cycle.sh'],shell=True)
else:
continue
The problem is the new window never opens. The while loop keeps the button / programm "occupied". Any ideas how to solve this?

Categories

Resources