Kivy image does not reload and app stops responding - python-3.x

I am trying to build an application with Python using Kivy. This is what I am trying to achieve:
Use input from a video or camera to detect images using a detector that (to simplify) finds all circles in an image, and highlights them somehow, producing a new image as a result. This is done by a class which I call detector. Detector makes use of OpenCV for image processing.
The detector's output image is written into display.jpg
A Kivy app has an image field of which source is display.jpg
(This doesn't work) I reload the image source using self.image.reload() so that my application refreshes the output for the user
Here is my code
class GUI(App):
def build(self):
self.capture = cv2.VideoCapture(VIDEO_SOURCE)
self.detector = detector(self.capture)
layout = GridLayout(cols=2)
self.InfoStream = Label(text = 'Info', size_hint=(20,80))
StartButton = Button(text = 'Start', size_hint=(80,20))
StartButton.bind(on_press=lambda x:self.start_program())
self.image = Image(source = 'display.jpg')
layout.add_widget(self.image)
layout.add_widget(self.InfoStream)
layout.add_widget(StartButton)
layout.add_widget(Label(text = 'Test'), size_hint=(20,20))
return layout
def start_program(self):
while True:
self.detector.detect_frame()
self.update_image() # After detecting each frame, I run this
def update_image(self, *args):
if self.detector.output_image is not None:
cv2.imwrite('display.jpg', self.detector.output_image)
self.image.reload() # I use reload() to refresh the image on screen
def exit(self):
self.stop()
def on_stop(self):
self.capture.release()
if __name__ == '__main__':
GUI().run()
What happens is that I can successfully start the application by pressing my StartButton. I see output on console proving it's cyclically running through frames from my video stream, and I also see the source image display.jpg being updated in real time.
However, after I start, the app window seems to simply freeze. It goes "Not responding" and greys out, never showing any refreshed image.
Following some existing code from other sources, I also attempted to refresh the image using a scheduled task, with Clock.schedule_interval(self.update_image, dt=1), but the result was the same.
Is my way of refreshing the image correct? Are there better ways to do so?

The while True loop on the main thread will cause your App to be non-responsive. You can eliminate that loop by using Clock.schedule_interval as below:
class GUI(App):
def build(self):
self.capture = cv2.VideoCapture(VIDEO_SOURCE)
self.detector = detector(self.capture)
layout = GridLayout(cols=2)
self.InfoStream = Label(text = 'Info', size_hint=(20,80))
StartButton = Button(text = 'Start', size_hint=(80,20))
StartButton.bind(on_press=lambda x:self.start_program())
self.image = Image(source = 'display.jpg')
layout.add_widget(self.image)
layout.add_widget(self.InfoStream)
layout.add_widget(StartButton)
layout.add_widget(Label(text = 'Test'), size_hint=(20,20))
return layout
def start_program(self):
Clock.schedule_interval(self.update_image, 0.05)
def update_image(self, *args):
self.detector.detect_frame()
if self.detector.output_image is not None:
cv2.imwrite('display.jpg', self.detector.output_image)
self.image.reload() # I use reload() to refresh the image on screen
def exit(self):
self.stop()
def on_stop(self):
self.capture.release()
if __name__ == '__main__':
GUI().run()
Here is another question where the poster is using a Texture instead of writing the captured image to a file. Might be a more efficient approach.

Related

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

wx.DirDialog not closing

Using wxPython version 4.0.1 (pheonix) with python 3.6.5
I use a wxPython DirDialog to allow my user to input a start directory. It correctly selects and populates my "working directory" variable (using GetPath()) but then doesn't ever close the directory dialog prompt box.
I read through the wxPython-user google pages and the only related question I found referred to this as being "intended behavior," implying it would happen later in execution (https://groups.google.com/forum/#!searchin/wxpython-users/close%7Csort:date/wxpython-users/ysEZK5PVBN4/ieLGEWc6AQAJ).
Mine, however, doesn't close until the entire script has completed running (which takes a fair amount of time), giving me the spinning wheel of death. I have tried a combination of calls to try to force the window to close.
app = wx.App()
openFileDialog = wx.DirDialog(None, "Select", curr, wx.DD_DIR_MUST_EXIST)
openFileDialog.ShowModal()
working_directory = openFileDialog.GetPath()
openFileDialog.EndModal(wx.CANCEL) #also wx.Close(True) and wx.Destroy()
openFileDialog.Destroy()
openFileDialog=None
I have also tried creating a window, passing it as the parent of the DirDialog, and then closing the window and it gives the same behavior.
You don't mention which operating system you are on or version of wx but in the partial code that you supplied there is no MainLoop, which is what was mentioned by Robin Dunn in his answer, in your link.
Try this and see if it works the way you would expect.
import wx
from os.path import expanduser
import time
class choose(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Dialog")
panel = wx.Panel(self,-1)
text = wx.StaticText(panel,-1, "Place holder for chosen directory")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.Show()
curr = expanduser("~")
dlg = wx.DirDialog(None, message="Choose a directory", defaultPath = curr,
style=wx.DD_DEFAULT_STYLE|wx.DD_DIR_MUST_EXIST)
if dlg.ShowModal() == wx.ID_OK:
text.SetLabel(dlg.GetPath())
dlg.Destroy()
b = wx.BusyInfo("I'm busy counting",parent=None)
wx.Yield()
for i in range(30):
time.sleep(1)
del b
def OnClose(self, event):
self.Destroy()
if __name__ == '__main__':
my_app = wx.App()
choose(None)
my_app.MainLoop()

Cache overflowing while showing an image stream using python 3 on a Raspberry Pi 2 Model B running Jessie and up to date

I’m very new to python. I’m working on a ‘proof of concept’ piece of code; using PiCamera on a Raspberry Pi running Jessie.
I’ve based my code on a tutorial code from: https://pythonprogramming.net/tkinter-adding-text-images/
Once you hit the button to show the image, the code starts PiCamera and starts to get capture_continuous, passes it to a stream, applies crosshairs to it.
It works mostly well… but after a bit over two minutes, the disk drive lights up and it starts to slow drastically. Once I get the program to break, everything is fine. I’ve looked at a couple of logs, but I can’t for the life of me find out what cache is overflowing or why. I’ve tried a bunch of different ways and tried to leave those in as comments. I suspected it had something to do with having to clear the image in tkinter, but even that doesn’t seem to work and makes the video flash unevenly.
Any help would be great! I’ve started to explore using opencv instead. Still installing that.
Thanks!
The code:
# Simple enough, just import everything from tkinter.
from tkinter import *
import picamera
import picamera.array
import time
import threading
import io
import numpy as np
from PIL import Image, ImageTk
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):
# Create an array representing a 1280x720 image of
# a cross through the center of the display. The shape of
# the array must be of the form (height, width, color)
# Define settings upon initialization. Here you can specify
def __init__(self, master=None):
# parameters that you want to send through the Frame class.
Frame.__init__(self, master)
#reference to the master widget, which is the tk window
self.master = master
#with that, we want to then run init_window, which doesn't yet exist
self.init_window()
#Creation of init_window
def init_window(self):
# changing the title of our master widget
self.master.title("GUI")
# allowing the widget to take the full space of the root window
self.pack(fill=BOTH, expand=1)
# creating a menu instance
menu = Menu(self.master)
self.master.config(menu=menu)
# create the file object)
file = Menu(menu)
# adds a command to the menu option, calling it exit, and the
# command it runs on event is client_exit
file.add_command(label="Exit", command=self.client_exit)
#added "file" to our menu
menu.add_cascade(label="File", menu=file)
# create the file object)
edit = Menu(menu)
# adds a command to the menu option, calling it exit, and the
# command it runs on event is client_exit
edit.add_command(label="Show Img", command=self.showImg)
edit.add_command(label="Show Text", command=self.showText)
#added "file" to our menu
menu.add_cascade(label="Edit", menu=edit)
self.trim_running_bool = False
def showImg(self):
self.trim_running_bool = True
trim_thrd_thread = threading.Thread(target=self._cam_thread_def)
trim_thrd_thread.start()
self.update_idletasks()
def _cam_thread_def(self):
img_stream = io.BytesIO()
frame_count = 0
with picamera.PiCamera() as camera:
camera.resolution = (400, 300)
## while True: ### tried it this way too
for xxx in range(0,900):
img_stream = io.BytesIO()
frame_count = frame_count + 1
print(frame_count," ", xxx)
if self.trim_running_bool == False:
print("break")
break
camera.capture(img_stream, 'jpeg', use_video_port=True)
img_stream.seek(0)
img_load = Image.open(img_stream)
for xl_line in range(0,196,4):
img_load.putpixel((xl_line, 149), (xl_line, 0, 0))
xll=xl_line+2
img_load.putpixel((xl_line, 150), (xl_line, xl_line, xl_line))
img_load.putpixel((xl_line, 151), (xl_line, 0, 0))
(xl_line)
for xr_line in range(208,400,4):
clr = 400 - xr_line
img_load.putpixel((xr_line, 149), (clr, 0, 0))
img_load.putpixel((xr_line, 150), (clr, clr, clr))
img_load.putpixel((xr_line, 151), (clr, 0, 0))
(xr_line)
for yt_line in range(0,146,4):
clrt = int(yt_line * 1.7)
img_load.putpixel((199, yt_line), (clrt, 0, 0))
img_load.putpixel((200, yt_line), (clrt, clrt, clrt))
img_load.putpixel((201, yt_line), (clrt, 0, 0))
(yt_line)
for yb_line in range(158,300,4):
clrb = int((300 - yb_line) * 1.7)
img_load.putpixel((199, yb_line), (clrb, 0, 0))
img_load.putpixel((200, yb_line), (clrb, clrb, clrb))
img_load.putpixel((201, yb_line), (clrb, 0, 0))
(yb_line)
img_render = ImageTk.PhotoImage(img_load)
# labels can be text or images
img = Label(self, image=img_render)
img.image = img_render
img.place(x=0, y=0)
self.update_idletasks()
img_stream.seek(0)
img_stream.truncate(0)
# tried these:
## img_stream.flush()
## print("flushed ", img_stream)
## print("2nd ",img_stream)
## del img_load
##
##
## rawCapture.truncate(0)
##
## rawCapture.seek(0)
## rawCapture.truncate(0)
## del render
## img.image = None
## foregnd_image = None
(xxx)
pass
def showText(self):
text = Label(self, text="Hey there good lookin!")
text.pack()
def client_exit(self):
self.trim_running_bool = False
exit()
# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()
root.geometry("400x300")
#creation of an instance
app = Window(root)
#mainloop
root.mainloop()
Each time through your loop you are creating a new image object and a new label, as well as some other objects. That is a memory leak, since you never destroy the old image or old label.
Generally speaking, you should create exactly one label, then use the_label.configure(image=the_image) every time through the loop. With that, you don't need to create new labels or call place on it.
Even better, since a label automatically updates when the associated image changes, you only need to change the the bits that are in the image object itself and the label should update automatically.
The simplest solution is to move image creation to a function so that all of those objects you are creating are local objects that can get automatically garbage collected when the function returns.
The first step is to create a single label and single image in your main thread:
class Window(Frame):
def __init__(self, master=None):
...
self.image = PhotoImage(width=400, height=300)
self.label = Label(self, image=self.image)
...
Next, create a function that copies new data into the image. Unfortunately, tkinter's implementation of the copy method doesn't support the full power of the underlying image object. A workaround is described here: http://tkinter.unpythonic.net/wiki/PhotoImage#Copy_a_SubImage.
Note: the workaround example is a general purpose workaround that uses more arguments than we need. In the following example we can omit many of the arguments. The documentation for the underlying tk photo object copy method is here: http://tcl.tk/man/tcl8.5/TkCmd/photo.htm#M17
The implementation would look something like this (I'm guessing; I don't have a good way to test it):
def new_image(self):
# all your code to create the new image goes here...
...
img_render = ImageTk.PhotoImage(img_load)
# copy the new image bits to the existing image object
self.tk.call(self.image, 'copy', img_render)
Finally, your loop to update the image would be much simpler:
while True:
self.new_image()
# presumeably there's some sort of sleep here so you're
# not updating the image faster than the camera can
# capture it.
I don't know how fast new_image can run. If it can run in 200ms or less you don't even need threads. Instead, you can use after to run that function periodically.
Note: I haven't worked much with Tkinter photo images in a long time, and I have no good way to test this. Use this as a guide, rather than as a definitive solution.

wxPython Panel Text Not Rendering

The problem I'm having is that when I set the static text inside a panel (see #Case 2) it only renders the first letter. The code below is a super stripped down version of my actual code but it produces an identical result:
import wx
import time
class TestInterface(wx.Frame):
testStatusFlag = 0
def __init__(self, *args, **kw):
super(TestInterface, self).__init__(*args, **kw)
self.pnl = wx.Panel(self)
self.SetSize((450, 225))
self.SetTitle('example')
self.Centre()
self.Show(True)
self.indicatorFullTest = None
self.buttonFullTest = None
self.setTestStatus(status=1)
def runTest(self, ev=None):
self.setTestStatus(status=2)
#the test is a bunch of functions that take a bunch of time to run
#they're all located in separate files but all access a single piece of hardware
#so multithreading is effectively impossible (I don't want to spend days re-writing stuff to accomodate it)
time.sleep(10)
self.setTestStatus(status=3)
return 0
def setTestStatus(self, ev=None, status=None):
#Handle the optional status argument
if (status in [1,2,3]):
self.testStatusFlag = status
#Remove any old stuff since if we're calling this function they have to get removed
if (self.indicatorFullTest != None):
self.indicatorFullTest.Hide()
if (self.buttonFullTest != None):
self.buttonFullTest.Hide()
#Case 1
if (self.testStatusFlag == 1):
self.buttonFullTest = wx.Button( self.pnl, label='Run Test', pos=(125, 100), size=(250, 50))
self.buttonFullTest.Bind(wx.EVT_BUTTON, self.runTest)
#Case 2
elif (self.testStatusFlag == 2):
self.indicatorFullTest = wx.Panel( self.pnl, pos=(125, 100), size=(250, 50))
wx.StaticText(self.indicatorFullTest, wx.ID_ANY, "Full-Board Test now in progress\nAllow up to 6 min to finish...",
style=wx.ALIGN_CENTRE_HORIZONTAL, pos=(18,7))
self.indicatorFullTest.SetBackgroundColour( 'Tan' )
self.Update()
#Case 3
elif (self.testStatusFlag == 3):
self.buttonFullTest = wx.Button( self.pnl, label='Test Complete\nPress to reset GUI',
pos=(125, 100), size=(250, 50) )
self.buttonFullTest.SetBackgroundColour( (130,255,130) )
self.buttonFullTest.Bind(wx.EVT_BUTTON, self.resetGUI)
#Resets the GUI after a test is complete
def resetGUI(self, ev=None):
self.setTestStatus(status=1) #Reset the fullTest button/indicator thing
if __name__ == '__main__':
ex = wx.App()
gui = TestInterface(None)
ex.MainLoop()
Basically, how do I make the UI fully render the text? I imagine it has something to do with not going back to wx's main loop after changing that indicator, but I feel like calling self.Update() should make that unnecessary. It may also have something to do with how I'm switching between using a button and using a panel (which is probably bad but I'm not sure how else to do it). I know that I could solve this by making my test function run in a separate thread but the problem is that the test function calls separate libraries which I literally do not have the time to re-write.
Thanks
This is a bit easier when you use wxPython's sizers because then you can just Show and Hide widgets. You can see a good example in this tutorial. I would recommend learning sizers just to make your UI more dynamic when you resize the frame.
But regardless, the answer to this conundrum is pretty simple. Instead of calling self.Update() in Case #2, you need to call wx.Yield(). I swapped that one line change in and it worked for me on Linux.

PyQt: How do I handle QPixmaps from a QThread?

This has to be the biggest nuisance I've encountered with PyQT: I've hacked together a thumbnailing thread for my application (I have to thumbnail tons of big images), and it looks like it would work (and it almost does). My main problem is this error message whenever I send a SIGNAL from my thread:
QPixmap: It is not safe to use pixmaps outside the GUI thread
I can't figure out how to get around this. I've tried passing a QIcon through my SIGNAL, but that still generates the same error. If it helps, here's the code blocks which deal with this stuff:
The Thumbnailer class:
class Thumbnailer(QtCore.QThread):
def __init__(self, ListWidget, parent = None):
super(Thumbnailer, self).__init__(parent)
self.stopped = False
self.completed = False
self.widget = ListWidget
def initialize(self, queue):
self.stopped = False
self.completed = False
self.queue = queue
def stop(self):
self.stopped = True
def run(self):
self.process()
self.stop()
def process(self):
for i in range(self.widget.count()):
item = self.widget.item(i)
icon = QtGui.QIcon(str(item.text()))
pixmap = icon.pixmap(72, 72)
icon = QtGui.QIcon(pixmap)
item.setIcon(icon)
The part which calls the thread (it occurs when a set of images is dropped onto the list box):
self.thread.images.append(f)
item = QtGui.QListWidgetItem(f, self.ui.pageList)
item.setStatusTip(f)
self.thread.start()
I'm not sure how to handle this kind of stuff, as I'm just a GUI newbie ;)
Thanks to all.
After many attempts, I finally got it. I can't use a QIcon or QPixmap from within a non-GUI thread, so I had to use a QImage instead, as that transmits fine.
Here's the magic code:
Excerpt from the thumbnailer.py QThread class:
icon = QtGui.QImage(image_file)
self.emit(QtCore.SIGNAL('makeIcon(int, QImage)'), i, icon)
makeIcon() function:
def makeIcon(self, index, image):
item = self.ui.pageList.item(index)
pixmap = QtGui.QPixmap(72, 72)
pixmap.convertFromImage(image) # <-- This is the magic function!
icon = QtGui.QIcon(pixmap)
item.setIcon(icon)
Hope this helps anyone else trying to make an image thumbnailing thread ;)

Resources