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.
Related
So lets say I have a simple QGraphicsView and QGraphicsScene, and I want to animate the view to smoothly scroll/scale over to an arbitrary QRecF within the scene. I know you can call view.fitInView() or view.centerOn() to 'frame up' on the rect, however neither of those provide for much animation. I can iteratively call view.centerOn() to animate the scrolling nicely, but this doesn't handle scale. I believe I need to modify the view's viewport but I'm not quite sure how to do that (calling view.setSceneRect() seemed promising but that just sets the area that the view can manage, not what it is currently 'looking at').
Based on some C++ answer I figured out how to re-write the fitInView method for the most part, however I am stuck on the part where I actually transform the view to look at a different part of the scene without calling centerOn. Here's what I have so far:
view = QtWidgets.QGraphicsView()
scene = QtWidgets.QGraphicsScene()
targetRect = QtCore.QRectF(500, 500, 500, 500)
viewRect = view.viewport().rect()
sceneRect = view.transform().mapRect(targetRect)
xratio = viewRect.width() / sceneRect.width()
yratio = viewRect.height() / sceneRect.height()
# keep aspect
xratio = yratio = min(xratio, yratio)
# so... now what to do with the ratio? I need to scale the view rect somehow.
# animation
start = view.mapToScene(viewRect).boundingRect()
end = sceneRect # I guess?
animator = QtCore.QVariantAnimation()
animator.setStartValue(start)
animator.setEndValue(end)
animator.valueChanged.connect(view.????) # what am I setting here?
animator.start()
I went down many twisty roads only to end up with an incredibly simple answer, thought I'd post it in case anyone finds it useful to animate both scroll and scale at the same time:
view = QtWidgets.QGraphicsView()
scene = QtWidgets.QGraphicsScene()
targetRect = QtCore.QRectF(500, 500, 500, 500)
animator = QtCore.QVariantAnimation()
animator.setStartValue(view.mapToScene(view.viewport().rect()).boundingRect())
animator.setEndValue(targetRect)
animator.valueChanged.connect(lambda x: view.fitInView(x, QtCore.Qt.KeepAspectRatio))
animator.start()
A simple, basic solution is to map the target rectangle and use that value as end value for the animation.
Note that this solution is not really optimal, for two reasons:
it completely relies on fitInView(), which has to compute the transformation for the whole scene at each iteration of the animation by checking the current viewport size; a better (but more complex) implementation would be to use scale() or setTransform() and also call centerOn() on the currently mapped rectangle of the transformation;
since the scene rect might be smaller than what the viewport is showing, zooming out could be a bit awkward;
class SmoothZoomGraphicsView(QtWidgets.QGraphicsView):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
self.pixmap = scene.addPixmap(QtGui.QPixmap('image.png'))
self.animation = QtCore.QVariantAnimation()
self.animation.valueChanged.connect(self.smoothScale)
self.setTransformationAnchor(self.AnchorUnderMouse)
def wheelEvent(self, event):
viewRect = self.mapToScene(self.viewport().rect()).boundingRect()
# assuming we are using a basic x2 or /2 ratio, we need to remove
# a quarter of the width/height and translate to half the position
# betweeen the current rectangle and cursor position, or add half
# the size and translate to a negative relative position
if event.angleDelta().y() > 0:
xRatio = viewRect.width() / 4
yRatio = viewRect.height() / 4
translate = .5
else:
xRatio = -viewRect.width() / 2
yRatio = -viewRect.height() / 2
translate = -1
finalRect = viewRect.adjusted(xRatio, yRatio, -xRatio, -yRatio)
cursor = self.viewport().mapFromGlobal(QtGui.QCursor.pos())
if cursor in self.viewport().rect():
line = QtCore.QLineF(viewRect.center(), self.mapToScene(cursor))
finalRect.moveCenter(line.pointAt(translate))
self.animation.setStartValue(viewRect)
self.animation.setEndValue(finalRect)
self.animation.start()
def smoothScale(self, rect):
self.fitInView(rect, QtCore.Qt.KeepAspectRatio)
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.
Is tkinter able to make a smooth text transition(slowly appear into the Window)? In Windows 10, Python 3 ? I have tried searching through the web but no similar questions, I have tried seeing if the widget has an option to do that, but no luck!
Is tkinter able to make a smooth text transition
If you are talking about a tkinter.Label, then you may be able to fake it by interpolating between two colors (the start color being the background color of the label, the end color being the desired foreground color of the label). Here's an example I came up with, where a label fades in from the background color (to fake transparency) into the desired foreground color (red in this case):
import tkinter as tk
def interpolate(color_a, color_b, t):
# 'color_a' and 'color_b' are RGB tuples
# 't' is a value between 0.0 and 1.0
# this is a naive interpolation
return tuple(int(a + (b - a) * t) for a, b in zip(color_a, color_b))
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Font Color Test")
self.geometry("256x64")
self.resizable(width=False, height=False)
self.label = tk.Label(self, text="Hello World", pady=32)
self.label.pack()
# On my system (Windows 7, classic theme) this is "SystemButtonFace"
label_background_system_color = self.label.cget("background")
label_background_16_bit_color = self.label.winfo_rgb(label_background_system_color)
# Again, on my system, this is RGB(212, 208, 200)
label_background_8_bit_color = tuple(value >> 8 for value in label_background_16_bit_color)
# This isn't really required. Making a custom label foreground color just to show it doesn't have to be black.
label_foreground_8_bit_color = tuple((255, 0, 0))
# I want the the label to "fade in" from the background color to completely red
self.start_color = label_background_8_bit_color
self.end_color = label_foreground_8_bit_color
# Let's say I want a smooth fade in transition at a rate of 60 fps and a duration of 1 second
self.duration_ms = 1000
self.frames_per_second = 60
self.ms_sleep_duration = 1000 // self.frames_per_second
self.current_step = 0
self.update_label()
def update_label(self):
t = (1.0 / self.frames_per_second) * self.current_step
self.current_step += 1
new_color = interpolate(self.start_color, self.end_color, t)
self.label.configure(foreground="#%02x%02x%02x" % new_color)
if self.current_step <= self.frames_per_second:
self.after(self.ms_sleep_duration, self.update_label)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
You might be able to fake it using images. Use a timeout function to replace them one after the other. Not sure if that would be fast enough to appear smooth.
But for things like this I think other toolkits would be better suited. For example pysdl2.
I simply want some elements inside a QDialog to be blinking (altering background color).
Now preferably I'd like to be able to use something that already exists and encapsulates the blinking state, i.e. blinking with css3 or maybe it is possible with QPropertyAnimation?
Since I didn't find any nice info on that option I tried the less optimal solution:
excerpt from the Dialogs __init__:
self.timer = QTimer()
self.timer.timeout.connect(self.update_blinking)
self.timer.start(250)
self.last_blinked = None
and
def update_blinking(self):
self.frame.setStyleSheet(
self.STYLE_BLINK_ON if self.blink else self.STYLE_BLINK_OFF)
self.blink = not self.blink
where STYLE_BLINK_ON and STYLE_BLINK_OFF are some css specifying the background colors.
That works but
I find it super ugly, it feels like code from the 90s
It isn't usable as the frequent style-update interrupts button-clicks.
Explanation for 2.: Assume the widget that should be blinking is a frame.
When a button inside that frame is clicked, the clicked signal isn't emitted if a style-update of the frame occurs before the mouse-button is released.
A completely different solution that encapsulates things and doesn't require me to manually start a timer would of course be preferred.
But I would be grateful if someone at least came up with a solution which solves point 2.
The one way is to use QPropertyAnimation. QPropertyAnimation interpolates over Qt properties - this fact causes difficulties:
1) Change appearance via style sheet -- animation cannot work with strings, because they're not interpolable.
2) Manipulate background directly -- background color is stored deep inside QWidget.palette, it's not a QProperty. The possible solution is to transform background color into a widget's property:
class AnimatedWidget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
color1 = QtGui.QColor(255, 0, 0)
color2 = QtGui.QColor(0, 255, 0)
self.color_anim = QtCore.QPropertyAnimation(self, 'backColor')
self.color_anim.setStartValue(color1)
self.color_anim.setKeyValueAt(0.5, color2)
self.color_anim.setEndValue(color1)
self.color_anim.setDuration(1000)
self.color_anim.setLoopCount(-1)
self.color_anim.start()
def getBackColor(self):
return self.palette().color(QtGui.QPalette.Background)
def setBackColor(self, color):
pal = self.palette()
pal.setColor(QtGui.QPalette.Background, color)
self.setPalette(pal)
backColor = QtCore.pyqtProperty(QtGui.QColor, getBackColor, setBackColor)
The other approach is dealing with QStateMachines. They're able to manipulate any properties, not only interpolable ones:
class StateWidget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
style1 = "background-color: yellow"
style2 = "background-color: black"
# animation doesn't work for strings but provides an appropriate delay
animation = QtCore.QPropertyAnimation(self, 'styleSheet')
animation.setDuration(150)
state1 = QtCore.QState()
state2 = QtCore.QState()
state1.assignProperty(self, 'styleSheet', style1)
state2.assignProperty(self, 'styleSheet', style2)
# change a state after an animation has played
# v
state1.addTransition(state1.propertiesAssigned, state2)
state2.addTransition(state2.propertiesAssigned, state1)
self.machine = QtCore.QStateMachine()
self.machine.addDefaultAnimation(animation)
self.machine.addState(state1)
self.machine.addState(state2)
self.machine.setInitialState(state1)
self.machine.start()
I am trying to make an "Office-like" TextView. That is:
The TextView itself has a fixed width (so it kinda shows what the text would look like on a sheet of paper)
If the window (on which the TextView is packed) is smaller than the fixed width: The TextView should be scrollable
If the window is bigger, add margins to the left/right to keep the fixed width
This is what i came up with, and it actually behaves like it should, except that it doesn't scroll if your cursor gets out of the viewport, when you for example write a line that needs more space than the windows current width.
What would be the best way to keep the viewport "in sync"? Do I have to create a custom Viewport?
Thanks in advance!
#!/usr/bin/env python2
# encoding: utf-8
import gtk
class SheetTextView(gtk.TextView):
WIDTH = 700
def __init__(self):
gtk.TextView.__init__(self)
self.set_wrap_mode(gtk.WRAP_WORD)
self.set_size_request(self.WIDTH, -1)
self.connect('size-allocate', self._on_size_allocate)
def _on_size_allocate(self, widget, event, data=None):
# Reset left/right margin to simulate a fixed line width
x, y, width, height = self.get_allocation()
if width > self.WIDTH:
margin = (width - self.WIDTH) / 2
self.set_left_margin(margin)
self.set_right_margin(margin)
if __name__ == "__main__":
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.connect('delete_event', gtk.main_quit)
view = SheetTextView()
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.add_with_viewport(view)
window.add(scroll)
window.show_all()
gtk.main()
You mean something like this?
Dropbox link
Please note this is just a test, all it does for now is change the size when necessary,
is that what you meant?
If so, please tell me and I'll fix the bugs and improve.
EDIT: Please note this is just scratch code, messy coded...