TL;DR
How to create a simple "UI" to display a few pictures ("live feed") and update a few sliders to show the state of the application. Basically, how to tell the UI: "show this new image instead of the current one" and "highlight this unclickable button to show it is activated".
I have absolutely zero experience with user interface and graphical display, so I don't know what to google for this question. I have some python code that uses the raspberry pi camera to take pictures, process them a bit (filters) and that's it. I also have a few hardware components (buttons, joystick) to control that camera and the processing applied to the pictures, like filter selection and filter intensity. The hardware events are already handled through a main loop that polls the buttons/joystick controller. The loop also captures the pictures and processes them. Basically, the application is done.
Now, what would be the best way to simply display this information ? What is the best Python framework to achieve this ?
Would PyQT or Kivy allow me to do this ? I would want to show the "live feed", some button information (which button has been pressed to show what processing is currently done on the pictures) in a simple cute layout. There does not need to be anything clickable on the interface, as the interaction occurs on the hardware.
Thanks !
I really like the Tkinter library for these simpler tasks. It is quite similar to java Swing should you be familiar with that. Here is a sample code that does pretty much all you need, even though it is quite messy. It should help you out though.
Note that Tkinter is very raw and not recommended for more elaborate needs.
import tkinter as tk
from PIL import ImageTk, Image
import time
#manipulating canvas items attributes
def change_color(canvas):
if canvas.itemcget(label, 'fill') == 'black':
canvas.itemconfig(label, fill='green')
else:
canvas.itemconfig(label, fill='black')
#changing image displayed
def change_image(canvas, current):
if current == 1:
canvas.itemconfig(img, image=photo2)
return 2
else:
canvas.itemconfig(img, image=photo1)
return 1
#slider gauge
def set_gauge(canvas, percentage):
coords = canvas.coords(gaugefg)
new_y = -2*percentage + 400
coords[1] = new_y
canvas.coords(gaugefg,coords)
color = 'black'
gauge = 0
delta = 10
root = tk.Tk()
#frame size and background
root.geometry('600x600')
root.configure(bg='white')
#make frame pop upfront
root.attributes("-topmost", True)
#uploading images from the same folder
photo1 = ImageTk.PhotoImage(Image.open('download.jpeg'))
photo2 = ImageTk.PhotoImage(Image.open('download1.jpeg'))
current_photo = 1
#canvas on with we'll be drawing
canvas = tk.Canvas(master=root, width=600, height=600)
#text label (xcenter,ycenter)
label = canvas.create_text(500, 500, text='Cool', fill=color, font=('purisa', 20))
#img component (xcenter, ycenter)
img = canvas.create_image(300, 300, image=photo1)
#gauge/slider = two superposed rectangles
#NOTE: (xtopleft, ytopleft, xbottomright, ybottomright)
gaugebg = canvas.create_rectangle(510, 200, 540, 400, fill='black')
gaugefg = canvas.create_rectangle(510, 300, 540, 400, fill='blue')
canvas.pack()
#Main Loop
while True:
change_color(canvas)
current_photo = change_image(canvas, current_photo)
gauge += delta
if gauge == 100 or gauge == 0:
delta = -delta
set_gauge(canvas, gauge)
#update frame and canvas
root.update()
time.sleep(1)
I hope that helps you accomplish what you need. Any questions just text.
Related
I've written a piece of code to play a video using OpenCV on tkinter. Its part of a game I've made and compiled into an application. But I've noticed that when I play the game in a different computers, since the screen sizes are different, the video doesn't fit exactly to screen size like I want it to. The same goes for the background images I used in different pages but I wrote a piece of code to resize the background images to screen size. Here it is:
def resizeimage(self,event) :
width, height = self.winfo_width(), self.winfo_height()
image = self.bg_image.resize((width,height))
self.image1 = ImageTk.PhotoImage(image)
self.bg_label.config(image = self.image1)
I've bound this function to the label that displays the background image like this:
self.bg_image = Image.open("project_pics\\start_pg.png")
bg_image = ImageTk.PhotoImage(self.bg_image)
self.bg_label = Label(self,image=bg_image)
self.bg_label.image = bg_image
self.bg_label.bind('<Configure>',self.resizeimage)
self.bg_label.grid(sticky="nwse")
here, self.bg_image is the image to be displayed as background and self.bg_label is the label that displays the image.
I know that I can implement something similar by resizing the frames, in my code to play the video, but I cant seem to figure out a quick, efficient a way to do so. Here is the code for the video player:
from tkinter import *
from tkinter.ttk import Button
from PIL import Image, ImageTk
import time
import cv2 as cv2
from threading import Thread
from Scripts.music_player import m_player
from Scripts.styles import Styles
# The Video Player
class VideoPlayer :
def __init__(self,parent) :
self.parent = parent
self.play = False
def player(self,vid_file,m_file,nxt_func):
def get_frame():
ret,frame = vid.read()
if ret and self.play :
return(ret,cv2.cvtColor(frame,cv2.COLOR_BGR2RGB))
else :
return(ret,None)
def update() :
ret,frame = get_frame()
if ret and self.play :
img = Image.fromarray(frame)
photo = ImageTk.PhotoImage(image=img)
photo.image=img
self.canvas.itemconfig(self.vid_frame,image=photo)
self.canvas.image=photo
self.parent.after(delay,lambda : update())
else :
time.sleep(0.01)
# stopping vid_music and starting game music
m_player.music_control(m_file,True,-1,0)
m_player.music_control("project_media\\signal.ogg",False,-1,0)
nxt_func()
def skip() :
self.play = False
self.parent.clear()
self.play = True
# starting music
m_player.music_control("project_media\\signal.ogg",True,-1,0)
m_player.music_control(m_file,False,-1,0)
vid = cv2.VideoCapture(vid_file)
width = vid.get(cv2.CAP_PROP_FRAME_WIDTH)
height = vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.canvas = Canvas(self.parent, width = width, height = height)
self.canvas.place(relx=0.5,rely=0.5,anchor=CENTER)
self.vid_frame = self.canvas.create_image(0, 0, anchor = NW)
# Skip button
if vid_file != "project_media\\glitch.mp4" :
skip_thread = Thread(target=skip)
skip = Button(self.parent,text="Skip",command=skip_thread.start,style="skip.TButton")
skip.place(relx=0.88,rely=0.04)
delay = 5
update()
My question is this. How could I efficiently resize my frames to fit screen size without slowing down execution?. Also, the function I'm using right now to resize my background images also seems to be slowing down execution. So I can see something like a glitch on the screen every time I change pages. So is there any other way I can resize my background images. Sorry if the code is a bit messy. I'm a beginner and this is the first game I've made .
I am currently working on a fruit ninja project for a class. Everything functionally works fine, however, when I try to put in a background image for the game runs extremely slow. In order for the game to look polished, I need everything to work smoothly while having the background of the game show. Other solutions I have come across and have tried to understand simply do not work or the file never ends up running.
FYI: I am working in python 2.7.
I have tried some other suggestions for adding a background, such as using a label function, however, when I try to implement it I get a variety of errors and it just does not seem to work in my animation framework.
def run(width=300, height=300):
def redrawAllWrapper(canvas, data):
canvas.delete(ALL)
canvas.create_rectangle(0, 0, data.width, data.height,
fill='white', width=0)
redrawAll(canvas, data)
canvas.update()
def mousePressedWrapper(event, canvas, data):
mousePressed(event, data)
redrawAllWrapper(canvas, data)
def keyPressedWrapper(event, canvas, data):
keyPressed(event, data)
redrawAllWrapper(canvas, data)
def timerFiredWrapper(canvas, data):
timerFired(data)
redrawAllWrapper(canvas, data)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
class Struct(object): pass
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
init(data)
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)
with my current framework, I write all the necessary code within the init, timerFired, redrawAll, keyPressed, and mousePressed functions above this run function.
With my current implementation of the background. I use PhotoImage on a 1200 x 700 gif file and draw the image across the whole screen in the redrawAll function (which is called every 10 milliseconds). Without drawing this one image, my game runs very smoothly, however, upon drawing the image in redrawAll, the game lags significantly, so I do know the source of the lag is drawing the background image.
Here is the line of code that draws it in redrawAll:
canvas.create_image(data.width//2, data.height//2, image = data.background)
Is this only because I do it in redrawAll which continuously draws the image every time the function is called making it slow? Is simply having an image that large in Tkinter making it slow? What is the source?
This there a way to simply draw the image once on the background and have it never change? Or is there any way to not have lag? I just find it odd. Again, this is in python 2.7 on a Mac.
Thanks!
You don't have to remove and add again all elements to refresh screen. You can move elements and canvas will draw it correctly
This code create 1000 small rectangles and move them randomly on background.
Tested with Python 3.7 but on 2.7 should work too.
With 5_000 rectangles it slows down but it still works good (but not perfect). With 10_000 it slows down too much.
from Tkinter import *
from PIL import Image, ImageTk
import random
IMAGE_PATH = 'background.jpg'
class Struct(object):
pass
def run(width=300, height=300):
def init(data):
# create 1000 rectangles in random position
for _ in range(1000):
x = random.randint(0, data.width)
y = random.randint(0, data.height)
data.data.append(canvas.create_rectangle(x, y, x+10, y+10, fill='red'))
def mousePressedWrapper(event, canvas, data):
#mousePressed(event, data)
pass
def keyPressedWrapper(event, canvas, data):
#keyPressed(event, data)
pass
def timerFiredWrapper(canvas, data):
# move objects
for rect_id in data.data:
x = random.randint(-10, 10)
y = random.randint(-10, 10)
canvas.move(rect_id, x, y)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
data.data = [] # place for small red rectangles
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
#canvas.create_rectangle(0, 0, data.width, data.height, fill='white', width=0)
img = Image.open(IMAGE_PATH)
img = img.resize((data.width, data.height))
photo = ImageTk.PhotoImage(img)
canvas.create_image(0, 0, image=photo, anchor='nw')
init(data) # init after creating canvas because it create rectangles on canvas
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)
I want to create a Board Game with Python and Tkinter
I want it to has a resize-function but I have two canvases for the GUI. First one is the square-Board (Spielfeld), the second one is the place where I want to add the control buttons for the player (Panel)
So if I want to resize my Board using <Configure> in my Master Window, it shall draw the Canvas with the New Size (self.FensterGroesse)
The If-Case is working well when I pass the else -function in resize
but if I run the Programm with the else function it resizes itself until its 1px big. Not just the canvas, the whole window.
I know the problem is the Panel being one third as high as the Board and when self.Panel.config sets the new size <Configure> is activated again.
But I dont know how I can have these two sanvases, one is a square, the other is a rectangle with the same widht and the square bit just 0.3*height
from Tkinter import *
class GUI:
def resize(self, event):
if event.height > (event.width*1.3):
self.FensterGroesse = event.width-2
else:
self.FensterGroesse = int(event.height/1.3)-2
self.Spielfeld.config(height=self.FensterGroesse, width=self.FensterGroesse)
self.Panel.config(height=self.FensterGroesse*0.3, width=self.FensterGroesse)
self.Spielfeld.pack()
self.Panel.pack()
def __init__(self):
self.FensterGroesse = 400
self.tkinter = __import__("Tkinter")
self.Master = self.tkinter.Tk()
self.Spielfeld = self.tkinter.Canvas(self.Master, height=self.FensterGroesse,
width=self.FensterGroesse, bg='#ffdead')
self.Panel = self.tkinter.Canvas(self.Master, height=self.FensterGroesse*0.3,
width=self.FensterGroesse, bg='brown')
self.Spielfeld.pack()
self.Panel.pack()
self.Master.bind("<Configure>", self.resize)
self.Master.mainloop()
GUI()
I have a serious problem this morning: I load a picture to display it on a Canvas. The picture is large, so I set scroll bars to allow the user to view it.
I allow the user to press the right button in order to draw random points with his mouse on the image.
It works, but there is a bug in its behaviour: When I scroll to the right or down of the image and click the right mouse button the point is drawn but not on the pixel I clicked on. It is drawn far away and my brain does not understand why.
A second but less alarming problem is that the points are drawn only in black whatever the color I set to them.
Please help me to resolve this or tell me why this occur and I will try to do something about it. I have been fighting with this problem for 2 hours by now and I did not find similar issues on this website and the whole Google:
import PIL.Image
import Image
import ImageTk
from Tkinter import *
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = self.y = 0
self.canvas = Canvas(self, cursor="cross",width=600,height=650)
self.sbarv=Scrollbar(self,orient=VERTICAL)
self.sbarh=Scrollbar(self,orient=HORIZONTAL)
self.sbarv.config(command=self.canvas.yview)
self.sbarh.config(command=self.canvas.xview)
self.canvas.config(yscrollcommand=self.sbarv.set)
self.canvas.config(xscrollcommand=self.sbarh.set)
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
self.sbarv.grid(row=0,column=1,stick=N+S)
self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.rect = None
self.start_x = None
self.start_y = None
self.im = PIL.Image.open("image.jpg")
self.wazil,self.lard=self.im.size
self.canvas.config(scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self,event):
print"({}, {})".format(event.x,event.y)
self.canvas.create_oval(event.x,event.y, event.x+1,event.y+1,fill='red')
# the fill option never takes effect, I do not know why
# When I scroll over the image, the pixels are not drawn where I click
if __name__ == "__main__":
root=Tk()
app = ExampleApp(root)
app.pack()
root.mainloop()
Thank you in advance for helping me.
1) You need to use canvasx and canvasy method.
See for example:
http://effbot.org/tkinterbook/canvas.htm#coordinate-systems
2) Your oval is so small you only see the border line.
Use outline= instead of fill=
I would like to show a real time graph with one or two curves an up to 50 samples per second using Python and wxPython.
The widget should support both Win32 and Linux platforms.
Any hints are welcome.
Edited to add:
I don't need to update the display at 50 fps, but up need to show up to 50 samples of data on both curves, with a reasonable update rate for the display (5..10 fps should be okay).
Edited to add:
I have used mathplotlib in a project with good success.
I have then settled for wx.lib.plot for other projects, which I found to be simpler, but somewhat easier to use and consuming less CPU cycles. As wx.lib comes as part of the standard wxPython distribution is is particularly easy to use.
If you want high performance with a minimal code footprint, look no farther than Python's built-in plotting library tkinter. No need to write special C / C++ code or use a large plotting package to get performance much better than 50 fps.
The following code scrolls a 1000x200 strip chart at 400 fps on a 2.2 GHz Core 2 duo, 1000 fps on a 3.4 GHz Core i3. The central routine "scrollstrip" plots a set of data points and corresponding colors at the right edge along with an optional vertical grid bar, then scrolls the stripchart to the left by 1. To plot horizontal grid bars just include them in the data and color arrays as constants along with your variable data points.
from tkinter import *
import math, random, threading, time
class StripChart:
def __init__(self, root):
self.gf = self.makeGraph(root)
self.cf = self.makeControls(root)
self.gf.pack()
self.cf.pack()
self.Reset()
def makeGraph(self, frame):
self.sw = 1000
self.h = 200
self.top = 2
gf = Canvas(frame, width=self.sw, height=self.h+10,
bg="#002", bd=0, highlightthickness=0)
gf.p = PhotoImage(width=2*self.sw, height=self.h)
self.item = gf.create_image(0, self.top, image=gf.p, anchor=NW)
return(gf)
def makeControls(self, frame):
cf = Frame(frame, borderwidth=1, relief="raised")
Button(cf, text="Run", command=self.Run).grid(column=2, row=2)
Button(cf, text="Stop", command=self.Stop).grid(column=4, row=2)
Button(cf, text="Reset", command=self.Reset).grid(column=6, row=2)
self.fps = Label(cf, text="0 fps")
self.fps.grid(column=2, row=4, columnspan=5)
return(cf)
def Run(self):
self.go = 1
for t in threading.enumerate():
if t.name == "_gen_":
print("already running")
return
threading.Thread(target=self.do_start, name="_gen_").start()
def Stop(self):
self.go = 0
for t in threading.enumerate():
if t.name == "_gen_":
t.join()
def Reset(self):
self.Stop()
self.clearstrip(self.gf.p, '#345')
def do_start(self):
t = 0
y2 = 0
tx = time.time()
while self.go:
y1 = 0.2*math.sin(0.02*math.pi*t)
y2 = 0.9*y2 + 0.1*(random.random()-0.5)
self.scrollstrip(self.gf.p,
(0.25+y1, 0.25, 0.7+y2, 0.6, 0.7, 0.8),
( '#ff4', '#f40', '#4af', '#080', '#0f0', '#080'),
"" if t % 65 else "#088")
t += 1
if not t % 100:
tx2 = time.time()
self.fps.config(text='%d fps' % int(100/(tx2 - tx)))
tx = tx2
# time.sleep(0.001)
def clearstrip(self, p, color): # Fill strip with background color
self.bg = color # save background color for scroll
self.data = None # clear previous data
self.x = 0
p.tk.call(p, 'put', color, '-to', 0, 0, p['width'], p['height'])
def scrollstrip(self, p, data, colors, bar=""): # Scroll the strip, add new data
self.x = (self.x + 1) % self.sw # x = double buffer position
bg = bar if bar else self.bg
p.tk.call(p, 'put', bg, '-to', self.x, 0,
self.x+1, self.h)
p.tk.call(p, 'put', bg, '-to', self.x+self.sw, 0,
self.x+self.sw+1, self.h)
self.gf.coords(self.item, -1-self.x, self.top) # scroll to just-written column
if not self.data:
self.data = data
for d in range(len(data)):
y0 = int((self.h-1) * (1.0-self.data[d])) # plot all the data points
y1 = int((self.h-1) * (1.0-data[d]))
ya, yb = sorted((y0, y1))
for y in range(ya, yb+1): # connect the dots
p.put(colors[d], (self.x,y))
p.put(colors[d], (self.x+self.sw,y))
self.data = data # save for next call
def main():
root = Tk()
root.title("StripChart")
app = StripChart(root)
root.mainloop()
main()
It's not difficult to create a C++ widget that would read from your data source, and truly update at 50 FPS. The beautiful thing about this approach is that very little (if any) Python code would be executing at 50FPS, it would all be in the C++, depending on how you hand your updated data to the widget.
You could even push an event handler into the custom real-time data viewer from the Python side, to handle all the mouse events and user interaction, and leave just the rendering in C++.
It would be a small C++ class that extends wxWidget's wxWindow class
class RealtimeDataViewer: public wxWindow {
...
and override OnPaint
void OnPaint(wxPaintEvent &WXUNUSED(event)) {
....
Then it would get a device context, and start drawing lines and shapes...
You would then have to take the .h file, and copy it to .i, and tweak it just a bit to make it a definition that SWIG could use to extend wxPython.
The build process could be handled by Python's own distutils using the following parameter to setup:
ext_modules=[Extension('myextension', sources,
include_dirs=includeDirs
library_dirs=usual_libs,
)],
It would be a few days work to get it looking great and working well... But it's probably the one option that would really accelerate your project into the future.
And all of this works well on Mac, Windows, and Linux.
wxPython is really a hidden Gem that would really take over the world with more professionally supported IDE / designer tools.
That said, try matplotlib first, it has lots of beautiful optimized rendering, and can do updates in real time too.
If you want really something fast with 50 frames per second, I think you need something like PyGame and kind of talk directly to the display, not a plotting module.
Check the related threads:
What is the fastest way to draw an image from discrete pixel values in Python?
https://stackoverflow.com/search?q=python+pygame
I use PyQtGraph for this kind of thing. It is much faster than Matplotlib for realtime plotting and has lots of nice convenience features like a context menu in the plotting canvas with auto-scaling and scrolling without any extra work.
Maybe Chaco? I don't know if it can do 50 frames per second, but I saw in a demonstration how it did very smooth realtime plotting. It should definitely be faster than matplotlib.