In my epic struggle to process raw mouse data on my ubuntu (14.04) OS with python, with alot help from here i am stuck again. It seems very hard for me to understand the "easyness" of pyqtgraph. all i want to do is to wrap the code i have now into a nice little gui with a start/pause/stop button, a list widget to show the numbers and a plot to let me see whats happening. I guess the main problem for me is, that i dont quite understand this whole event thing in pyqt.
anyway taking a "easy" example which has the widgets i want, i fail to implement my code(edited more minimalistic):
#!/usr/bin/python
import threading
import struct
import time
import numpy as np
from PyQt4 import QtGui # (the example applies equally well to PySide)
from PyQt4 import QtCore
import pyqtgraph as pg
##
data =[(0,0)]
sum_data = [(0,0)]
file = open("/dev/input/mouse2", "rb")
def getMouseEvent():
buf = file.read(3);
#python 2 & 3 compatibility
button = buf[0] if isinstance(buf[0], int) else ord(buf[0])
x,y = struct.unpack( "bb", buf[1:] );
print x,y
return x, y
def mouseCollect():
while True:
data.append(getMouseEvent())
sum_data.append(tuple(map(sum,zip(sum_data[-1],data[-1]))))
plot.plot(sum_data[0], clear=True)
pg.QtGui.QApplication.processEvents()
print sum_data[-1]
## Always start by initializing Qt (only once per application)
app = QtGui.QApplication([])
## Define a top-level widget to hold everything
w = QtGui.QWidget()
## Create some widgets to be placed inside
btn1 = QtGui.QPushButton('Start')
listw = QtGui.QListWidget()
plot = pg.PlotWidget()
def start_btn():
print 'test'
threading.Thread(target=mouseCollect).start()
btn1.clicked.connect(start_btn)
## Create a grid layout to manage the widgets size and position
layout = QtGui.QGridLayout()
w.setLayout(layout)
## Add widgets to the layout in their proper positions
layout.addWidget(btn1, 0, 0) # button goes in upper-left
layout.addWidget(plot, 0, 1, 4, 1)
## Display the widget as a new window
w.show()
## Start the Qt event loop
app.exec_()
##------------------------------------------------------------------
when i press the start button, the window just freezes and nothing happens. my thought was, if i press the button, it connects to the methot state there and it just doing its stuff. okay i have an infinite loop, but at least i thought i should see something. any help is apreciated, also any tips for a good reading about the matter is very welcome.
regards
Edit: inserted a thread as suggested by echocage
The best approach for repetitive updates is to use a QTimer. In this way you allow program control to go back to the Qt event loop after every update (no infinite loops allowed), and Qt periodically invokes your update function for you.
For example, see one of the many the updating plot examples included with pyqtgraph: https://github.com/pyqtgraph/pyqtgraph/blob/develop/examples/Plotting.py#L58
Threading is very difficult to do correctly and when it's done wrong you usually end up with crashing that is difficult to debug. I recommend to avoid threading until you are very confident with the event system and familiar with the pitfalls of threading.
I finally found it. I somehow never realized, that you HAVE to use numpy arrays. also i didnt use curve.setdata for plotting.
the more or less final code(not full code) now looks like this:
class mouseCollect(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run (self):
global e, curve, curve2, data1, data2
while e.wait():
tmp = getMouseEvent() #returns tuples in form of (x,y)
data1 = np.append(data1, tmp[0] )
data2 = np.append(data2, tmp[1] )
sum1= np.cumsum(data1)
sum2= np.cumsum(data2)
curve.setData(y=sum1)
curve2.setData(y=sum2)
guiPE # process event thingy
def stop(self):
e.clear()
well, it is a not really written efficiently, but it works :)
Related
I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?
I'm using gtk.Table in combination with an EventBox for every cell to draw a colored grid. After trying out a minimal example i've discovered that the window is twice as big as the actual table. Also it's not possible to shrink the window any further when the application is running.
It seems like something went horrible wrong but i'm not able to figure out the cause. Here's the minimal example to reproduce the misbehaviour:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyProgram:
def __init__(self):
app_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
app_window.set_border_width(10)
app_window.connect("delete_event", lambda w,e: gtk.main_quit())
vbox_app = gtk.VBox(False, 0)
table_layout = gtk.Table(rows=1,columns=1, homogeneous=True)
for col in range(1,10):
for row in range(1,5):
event_box = gtk.EventBox()
label_day = gtk.Label("")
label_day.set_size_request(18,18)
label_day.show()
event_box.add(label_day)
event_box.modify_bg(gtk.STATE_NORMAL,
event_box.get_colormap().alloc_color("orange"))
event_box.set_border_width(25)
event_box.show()
table_layout.attach(event_box, 0, col, 0, row, 0,0,0,0)
vbox_app.pack_start(table_layout)
app_window.add(vbox_app)
app_window.show_all()
return
def main():
gtk.main()
return 0
if __name__ == "__main__":
MyProgram()
main()
It turns out it's your event_box.set_border_width(25) that hurts. If you just want to space evenly your labels, us the padding arguments of GtkTable::attach instead.
Here are unrelated improvements:
don't connect gtk_main_quit to the delete-event signal, connect it to the destroy signal instead. delete-event is when you want to do something before quitting (for example, display a popup "are you sure ? yes/no"), but what you want is to quit gtk when the window is really destroyed.
Also, instead of prepending your widgets when adding them in the table, append them and use a range starting from 0 so it's easier to see where the widgets are added (the indexes in the table are zero-based).
As for your VBox, in this context it's useless. If you only have widget which content takes the whole window, just add it directly to your GtkWindow (but maybe it's needed in our unstripped version of the program).
Finally, you don't need to call gtk_widget_show on each widget. Just focus on constructing your widget hierarchy, and then run gtk_widget_show_all on the toplevel window. It will recursively show all the widgets in it.
That gives us in the end:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyProgram:
def __init__(self):
app_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
app_window.set_border_width(10)
app_window.connect('destroy', lambda w: gtk.main_quit())
table = gtk.Table(rows=1, columns=1, homogeneous=True)
for col in range(0,9):
for row in range(0,4):
event_box = gtk.EventBox()
label_day = gtk.Label('')
label_day.set_size_request(18, 18)
event_box.add(label_day)
event_box.modify_bg(gtk.STATE_NORMAL,
event_box.get_colormap().alloc_color("orange"))
# event_box.set_border_width(25)
table.attach(event_box, col, col + 1, row, row + 1, 0, 0, 12, 12)
app_window.add(table)
app_window.show_all()
if __name__ == '__main__':
MyProgram()
gtk.main()
You also have a tool named gtk-inspector but I don't know if it works with GTK 2. If not, fallback on gtk-parasite. These tools will help you analyze a running gtk user interface and see the characteristics of its widgets.
Also, GTK 3 has been out for a few years now and GTK 4 is on the road. Consider using GTK 3 for new code. It doesn't use pygtk anymore, it's pygobject instead trough the gi package (GObject introspection).
Here's the GTK 3 in python official tutorial:
https://python-gtk-3-tutorial.readthedocs.io/en/latest/index.html
I have implemented a sync block which plots inside its work function using the input_items values. Now the problem is that the plotting mechanism isn't fast enough for the input stream ( the value of input_items keeps on changing ).
I have tried to simplify the code as much as possible and added comments. Here it is:
....
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
temp = ''
class xyz(gr.sync_block):
def __init__(self,parent,title,order):
a = []
self.count = 1
gr.sync_block.__init__(self,
name="xyz",
in_sig=[numpy.float32,numpy.float32],
out_sig=None)
self.win = xyzPlot(parent) #I have a Top Block(wxFrame). I am just making it the parent of xyzPlot(wxPanel) here.
def work(self, input_items, output_items):
global temp
if (self.count == 1):
temp = input_items+list()
bool = not(np.allclose(temp,input_items))#bool will be true if the value of `input_items` changes.
.......
#the code that evaluates z1,z2 depending on the value of input_items
.......
if ( bool or self.count == 1 ):
#If bool is true or it is the first time then I am calling plot() which plots the graph.
self.win.plot(tf(self.z1,self.z3),None,True,True,True,True)
self.count = 0
temp = input_items+list()
return len(input_items[0])
class xyzPlot(wx.Panel):
def __init__(self, parent, dB=None, Hz=None, deg=None):
wx.Panel.__init__(self , parent , -1 ,size=(600,475))
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
def plot(self, syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args , **kwargs):
self.axes.clear() #I clear the graph here so that new values can be plotted.
.....
self.axes.semilogx(omega,mag,*args,**kwargs)
self.canvas = FigCanvas(self, -1, self.fig)
self.canvas.draw()
As you can see I am working with wxPython but the panel freezes whenever I change the value of input_items too fast ( It works fine if I change it slowly ). Any recommendations? I am new to this.
To cite another answer I gave:
This will quickly also get a multithreading problem. To be clear: What
you're trying to do (call a plotting function from a block thread) is
problematic and usually won't work.
The problem is that you're working in a complex multithreading environment:
each GNU Radio block works in its own thread
The WX Gui main loop runs continously to update the screen.
What you're doing here is, from a GNU Radio block thread, change what is shown in the window. That is a bad thing, because it changes things that are in the context of the WX Gui thread. This can work, if these changes don't conflict, and if the WX Gui thread doesn't access this kind of data while you're changing it (at some point, it has to access it -- otherwise, noone will update your window).
This is a problem common to all kind of updated GUIs, not only to GNU Radio!
Whether or not that happens is a mere case of probability: With a slowly updated display, your probability of conflict is low, but when you update often, it approaches 1.
The existing visualizations are written in C++ and take very great care to do things the right way -- which is, letting your Gui toolkit (WX in your case, though I explicitely recommend, and have recommended, to move away from that) know that things need to be updated, and then offering WX a function to update the display in its own thread.
I know this has been asked many times before. I read all of those threads, and my case seems different. Everybody else who has this trouble has a few straightforward causes that I think I’ve ruled out, such as:
Starting a timer with no event loop running
Starting/stopping a timer from a thread other than the one that created the timer
Failing to set the parent property of a widget, leading to problems with the order of destruction
Below I have a minimal code sample that demonstrates the problem. Notice that I’ve started no threads or timers. I also have set the parent of every widget. If I remove the graph widgets, the problem goes away, so one is tempted to blame pyQtGraph, however, if I include the plot widgets but exclude all the blank tabs (i.e. every tab except tabCatchaTiger), the problem also goes away, and that seems to vindicate pyQtGraph.
Versions:
Windows 7
Python 2.7.8
Wing IDE 5.0.9-1
PyQt 4.11.1
PyQwt 5.2.1
PyQtGraph 0.9.8
Test case:
from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg
pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :
def __init__(self) :
# Make the window
QtGui.QWidget.__init__(self)
self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
self.setWindowTitle('Data Visualization')
# Create tab interface
tabWidget = QtGui.QTabWidget(self)
# define the tab objects
self.tabEeny = QtGui.QWidget(tabWidget)
self.tabMeeny = QtGui.QWidget(tabWidget)
self.tabMiney = QtGui.QWidget(tabWidget)
self.tabMoe = QtGui.QWidget(tabWidget)
self.tabCatchaTiger = QtGui.QWidget(tabWidget)
self.tabByThe = QtGui.QWidget(tabWidget)
self.tabToe = QtGui.QWidget(tabWidget)
# Initialize the tab objects
self.initTabCatchaTiger()
###########################################
############### Main Layout ###############
###########################################
tabWidget.addTab(self.tabEeny, 'Eeny')
tabWidget.addTab(self.tabMeeny, 'Meeny')
tabWidget.addTab(self.tabMiney, 'Miney')
tabWidget.addTab(self.tabMoe, 'Moe')
tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
tabWidget.addTab(self.tabByThe, 'By The')
tabWidget.addTab(self.tabToe, 'Toe')
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.addWidget(tabWidget)
self.setLayout(self.mainLayout)
def initTabCatchaTiger(self):
###########################################
############# ADC Capture Tab #############
###########################################
# define tab layout
grid = QtGui.QGridLayout(self.tabCatchaTiger)
# create copy of adc plot and add to row 3 of the grid
self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)
self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)
# set layout for tab
self.tabCatchaTiger.setLayout(grid)
def closeEvent(self, event) :
pass
def main() :
# open a QApplication and dialog() GUI
app = QtGui.QApplication([])
windowCrashy = crashyGUI()
windowCrashy.show()
app.exec_()
main()
There seem to be two closely-related issues in the example.
The first one causes Qt to print the QObject::startTimer: QTimer can only be used with threads started with QThread messages on exit.
The second one (which may not affect all users) causes Qt to print QPixmap: Must construct a QApplication before a QPaintDevice, and then dump core on exit.
Both of these issues are caused by python deleting objects in an unpredicable order when it exits.
In the example, the second issue can be fixed by adding the following line to the __init__ of the top-level window:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Unless QApplication.setQuitOnLastWindowClosed has been changed to False, this will ensure that the application quits at the right time, and that Qt has a chance to automatically delete all the children of the top-level window before the python garbage-collector gets to work.
However, for this to be completely successful, all the relevant objects must be linked together in a parent-child hierarchy. The example code does this where it can, but there seem to be some critical places in the initialization of the PlotWidget class where it is not done.
In particular, there is nothing to ensure that the central item of the PlotWidget has a parent set when it is created. If the relevant part of the code is changed to this:
class PlotWidget(GraphicsView):
...
def __init__(self, parent=None, background='default', **kargs):
GraphicsView.__init__(self, parent, background=background)
...
self.plotItem = PlotItem(**kargs)
# make sure the item gets a parent
self.plotItem.setParent(self)
self.setCentralItem(self.plotItem)
then the first issue with the QTimer messages also goes away.
Here's a better answer:
You are allowing the QApplication to be collected before python exits. This causes two different issues:
The QTimer error messages are caused by pyqtgraph trying to track its ViewBoxes after the QApplication has been destroyed.
The crash appears to be intrinsic to Qt / PyQt. The following crashes in the same way:
from PyQt4 import Qt, QtGui, QtCore
def main() :
app = QtGui.QApplication([])
x = QtGui.QGraphicsView()
s = QtGui.QGraphicsScene()
x.setScene(s)
x.show()
app.exec_()
main()
You can fix it by adding global app to your main function, or by creating the QApplication at the module level.
Try to write this in block of __init__:
self.setAttribute(Qt.WA_DeleteOnClose)
Personally, I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.
(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)
I had this happen as well and in my case it was caused by a call to deleteLater() on the aboutToQuit-Signal of the application, like so:
def closeEvent(self, *args, **kwargs):
self.deleteLater()
if __name__ == "__main__":
application = QtWidgets.QApplication(sys.argv)
window = testApplication()
# Handle application exit
application.aboutToQuit.connect(window.closeEvent)
# System exit
sys.exit(application.exec_())
Getting rid of the deleteLater on the whole window seemed to solve it.
progress = QtGui.QProgressDialog("Parsing Log", "Stop", 0,numberOfLinesInFile , self)
progress.setWindowModality(QtCore.Qt.WindowModal)
for lineNumber, line in enumerate(file):
# yield a bit to the Qt UI handler
QtGui.QApplication.processEvents()
progress.setValue(lineNumber + 1) # lineNumber is zero-based so need the plus one to match the more literal numberOfLinesInFile
if progress.wasCanceled():
progressWasCancelled = True
break
# ...read and parse lines from file (20mb takes ~10 seconds)
# crank the progress bar through to completion to get rid of it
# this seems to forgo the opportunity to use progress.wasCanceled() subsequently?
progress.setValue(numberOfLinesInFile)
if not progressWasCancelled:
self.updateTable(self.requestRoster)
After this, and regardless of the progress dialogue being cancelled or not, the progress dialogue is hidden (it slides back up into the toolbar). But if I switch application ('command tab' on the Mac) then switch back to my application, a ghost of the QProgressDialog is in front of the main application window! Its progress bar is at 100% and the stop button is blue but not pulsing. It is unresponsive. If I move the application window it disappears.
If I call progress.destroy() after progress.setValue(numberOfLinesInFile) that seems to help. But it seems worrying to copy the example from the docs and get bitten, and I don't know the ramifications of destroy().
I was using PySide, I switched to PyQt and same thing.
Also, sometimes progress.setValue(numberOfLinesInFile) causes subsequent reads of progress.wasCancelled() to return false (but sometimes it returns true!) which is why I set my own progressWasCancelled. Its randomness is disturbing.
I'm on Mac 10.6.8, Qt 4.8.2, Python 2.7. Tried with PySide 1.1.0 and PyQt 4.9.4.
Am I doing this all wrong?
I can't test on a Mac, but I'll try to make a few suggestions which could help solve your issues.
Firstly, if you use a modal progress dialog, there's no need to call processEvents(), as the dialog will handle this itself.
Secondly, this line in your code:
progress.setValue(lineNumber + 1)
is problematic, because to quote the Qt docs:
For the progress dialog to work as expected, you should initially set this property to 0 and finally set it to QProgressDialog::maximum(); you can call setValue() any number of times in-between.
so you should either call progress.setValue(0) before the loop, or just avoid adding the offset altogether. Also, on the final iteration, lineNumber + 1 will equal the maximum, which will reset the dialog at that point (unless autoReset has been set to False). It is for this reason that the Qt example calls setValue(maximum) after the loop has completed.
Finally, there is no problem with calling destroy() or deleteLater() after you've finished with the progress dialog - in fact, it's a good idea. When you pass self to the QProgressDialog constructor, it will become the parent of the dialog and keep a reference to it. So, unless you explicitly delete it, a new child dialog (plus all it's child objects) will be added every time you call the function that uses it (which could potentially waste a lot of memory).
Here's a demo script that may be improvement:
import sys, time
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
file = range(30)
numberOfLinesInFile = len(file)
progressWasCancelled = False
progress = QtGui.QProgressDialog(
"Parsing Log", "Stop", 0, numberOfLinesInFile, self)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setMinimumDuration(0)
for lineNumber, line in enumerate(file):
progress.setValue(lineNumber)
if progress.wasCanceled():
progressWasCancelled = True
break
time.sleep(0.05)
progress.setValue(numberOfLinesInFile)
print 'cancelled', progress.wasCanceled(), progressWasCancelled
progress.deleteLater()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())