How to embed vispy graph in PyQt? - python

I'm trying to embed a vispy plot (more specifically, a Vispy SceneCanvas) as a QWidget into PyQt4. I would presume the answer would be something like this:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import vispy.mpl_plot as plt
app = QApplication(sys.argv)
win = QMainWindow()
plt.plot([1,2,3,4], [1,4,9,16])
vispyCanvas=plt.show()[0]
win.setCentralWidget(vispyCanvas)
However, when I try this the last line gives me the expected error that vispyCanvas is type SceneCanvas and not of type QWidget. When I print(vispyCanvas), it prints out <Vispy canvas (PyQt4 (qt) backend) at 0x142bcb00L>, which is why I suspect that it should be possible to treat it or one of its attributes as a QWidget object.

The answer is simple:
win.setCentralWidget(vispyCanvas.native)
As long as vispy is using Qt as its backend, then Canvas.native refers to the underlying QGLWidget.

Related

How to add stretch for QGridLayout in PyQt5?

I created widgets in a grid-layout. The widgets are stretching based on the window. Is it possible to avoid the stretching and align them as shown in picture below? I created a code to achieve this, but I feel it is not a straightforward solution. If there are any better solutions to achieve this, please share them.
Grid layout result:
from PyQt5.QtWidgets import *
app =QApplication([])
window=QWidget()
GL=QGridLayout(window)
GL.addWidget(QPushButton('R1C1'),0,0)
GL.addWidget(QPushButton('R1C2'),0,1)
GL.addWidget(QPushButton('R2C1'),1,0)
GL.addWidget(QPushButton('R1C1'),1,1)
window.showMaximized()
app.exec_()
Required Result:
My code:
from PyQt5.QtWidgets import *
app =QApplication([])
window=QWidget()
VL=QVBoxLayout(window);HL=QHBoxLayout();VL.addLayout(HL)
GL=QGridLayout();HL.addLayout(GL)
GL.addWidget(QPushButton('R1C1'),0,0)
GL.addWidget(QPushButton('R1C2'),0,1)
GL.addWidget(QPushButton('R2C1'),1,0)
GL.addWidget(QPushButton('R1C1'),1,1)
HL.addStretch();VL.addStretch()
window.showMaximized()
app.exec_()
The QGridLaout class doesn't have any simple convenience methods like QBoxLayout.addStretch() to do this. But the same effect can be achieved by adding some empty, stretchable rows/columns, like this:
GL.setRowStretch(GL.rowCount(), 1)
GL.setColumnStretch(GL.columnCount(), 1)

How to stop display resolution from affecting axes in pyqtgraph plots

I am using pyqtgraph to plot some data and noticed that when I move the plot from my laptop screen to a second monitor, the scaling on the plot is affected:
laptop monitor:
external monitor:
notice that the axes got "compressed", and the plot is no longer scaled properly on the second monitor.
I found others reporting similar issues on the web, but could not find any real solution. One solution suggested was to make the monitors' resolutions the same. I don't like this solution because I'd have to sacrifice laptop resolution to accommodate my lower resolution external monitor.
The other solution I found was to add the line app.setAttribute(QtCore.Qt.AA_Use96Dpi) to the main loop, prior to instantiating the Qapplication as shown below, to allegedly have Qt ignore the OS's DPI settings:
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
app.setAttribute(QtCore.Qt.AA_Use96Dpi)
MainWindow =GraphWindow()
MainWindow.show()
sys.exit(app.exec_())
This seems at first to work, because the plotted data is scaled properly on the axes. However, it doesn't seem to really work -- the addition of this line affected the scaling of the axes on the laptop as shown below (same data is now plotted on axes that span 0 to 7000 on the x-axis, and -2 to -26dB on the Yaxis):
,
but did "fix" the issue when moving the plot onto the second monitor to look like the first "original" laptop plot shown above.
This is particularly worrisome, because in the case of the laptop output after the app.setAttribute(QtCore.Qt.AA_Use96Dpi) instruction "looks" right, but misrepresents the actual data. I could have easily missed this had included this instruction when I first plotted the data.
What is the right way to have the plot accurately display regardless of the OS's DPI setting and monitor resolutions? It is very strange that the plotted data seems disassociated with the axis values.
Here is a mininimal reproducible sample:
from PyQt5 import QtWidgets, QtCore
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
import sys # We need sys so that we can pass argv to QApplication
import os
from numpy.random import seed
from numpy.random import randint
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.graphWidget = pg.PlotWidget()
self.setCentralWidget(self.graphWidget)
x = [1,2,3,4,5,6,7,8,9,10]
seed(1)
y = randint(5,35,10)
# plot data: x, y values
self.graphWidget.plot(x, y)
def main():
app = QtWidgets.QApplication(sys.argv)
app.setAttribute(QtCore.Qt.AA_Use96Dpi)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The setAttribute solution never worked for me in that way and the windll manipulation makes the gui blurry...
adding following two lines before app = QApplication(sys.argv) solved my problem:
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
Answers can be found here: https://github.com/pyqtgraph/pyqtgraph/issues/756
Quick Summary of this issue:
There are essentially two ways to solve this problem.
Make your app DPI-aware (by Androwei)
import ctypes
import platform
def make_dpi_aware():
if int(platform.release()) >= 8:
ctypes.windll.shcore.SetProcessDpiAwareness(True)
# add this code before "app = QtWidgets.QApplication(sys.argv)"
make_dpi_aware()
set Qt.HighDpiScaleFactorRoundingPolicy to PassThrough (by andybarry)
# add this code before "app = QtWidgets.QApplication(sys.argv)"
QtWidgets.QApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
I have tried both, and they both work perfectly! Thanks to these contributors. Hope you can find this useful as well.

Using qtDesigner with python seamlessly [duplicate]

This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 4 years ago.
I've been looking for a better way to work with frontends made using qtDesigner connected to a python backend. All the methods I have found have the following form:
Make GUI in designer
Output to python code using pyuic (usually with -x option)
Write backend code inside this output file
This methodology is not simple to maintain or edit. Any time you change the UI, it completely breaks workflow: you have to reconvert, generate a new file, fix that file back up to where you were before, then finally get back on track. This requires a lot of manual copy-paste of code, which is an invitation to errors on multiple levels (newly generated file layout may not be the same, manually fixing name changes while pasting, etc.). You can also end up losing work if you aren't careful, since you could accidentally overwrite the file and destroy the backend code.
Also, this doesn't use any of the control in qtDesigner like the Signal/Slot and Action editors. These must be here for something, but I can't find a way to actually direct these to call backend functions.
Is there a better way to work with this, possibly using the features of qtDesigner?
You don't have to add your code in the output file :
If you take a 'home.py' generated by PYUIC, containing a QMainWindow which name set by QtDesigner/generated by Puic would be Ui_Home(), your main code could be :
from home import Ui_Home
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class window_home(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
#set up the user interface from Designer
self.ui = Ui_Home()
self.ui.setupUi(parent)
#then do anything as if you were in your output file, for example setting an image for a QLabel named "label" (using QtDesigner) at the root QMainWindow :
self.ui.label.setPixmap(QPixmap("./pictures/log.png"))
def Home():
f=QMainWindow()
c=window_home(f)
f.show()
r=qApp.exec_()
if __name__=="__main__":
qApp=QApplication(sys.argv)
Home()
I found an even cleaner method for working with this, that does not require preemptive conversion after each edit at all. Instead it takes the .ui file itself, so all you need to do is restart the program itself to update the design.
import sys
import os
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5 import uic
path = os.path.dirname(__file__) #uic paths from itself, not the active dir, so path needed
qtCreatorFile = "XXXXX.ui" #Ui file name, from QtDesigner, assumes in same folder as this .py
Ui_MainWindow, QtBaseClass = uic.loadUiType(path + qtCreatorFile) #process through pyuic
class MyApp(QMainWindow, Ui_MainWindow): #gui class
def __init__(self):
#The following sets up the gui via Qt
super(MyApp, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
#set up callbacks
self.ui.NAME OF CONTROL.ACTION.connect(self.test)
def test(self):
#Callback Function
if __name__ == "__main__":
app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
window = MyApp()
window.show()
sys.exit(app.exec_())
Note that this is Qt5. Qt5 and Qt4 are not API compatible, so it will be a little different in Qt4 (and presumably earlier as well).

Crash when running PyQt5 with matplotlib on iPython

I want to use matplotlib on my own GUI application, so decided to implement it using PyQt5. I wrote the test code below.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
import matplotlib as mpl
mpl.use('Qt5Agg')
import matplotlib.pyplot as plt
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.show()
self.raise_()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
However, I was faced with a weird problem.
When I ran the code on iPython, there is no problem.
The 2nd running is also OK.
But, after I launched the application 3rd times and closed it, iPython freezed with some messages. The message is as follows.
QEventLoop: Cannot be used without QApplication
QSocketNotifier: Can only be used with threads started with Thread
This problem can be reproduced on my PC. Why such a crash occurs?
I use python (3.5.2), iPython (5.1.0), and matplotlib (1.5.3). PC's OS is Mac OS X (10.11.4).

Change icon in a Matplotlib figure window

Is it possible to change the icon of a Matplotlibe figure window? My application has a button that opens a Figure window with a graph (created with Matplotlib). I managed to modify the application icon, but the figure window still has the 'Tk' icon, typical of Tkinter.
I solved it in this way:
BEFORE I press the button that creates the figure with imshow() and show(), I initialize the figure in this way:
plt.Figure()
thismanager = get_current_fig_manager()
thismanager.window.wm_iconbitmap("icon.ico")
so when I press show() the window has the icon I want.
For me the previous answer did not work, rather the following was required:
from Tkinter import PhotoImage
import matplotlib
thismanager = matplotlib.pyplot.get_current_fig_manager()
img = PhotoImage(file='filename.ppm')
thismanager.window.tk.call('wm', 'iconphoto', thismanager.window._w, img)
Just adding this here, now that the Qt5Agg backend has made it's way into the mainstream. It's similar (pretty much the same) to the Qt4Agg backend as outlined by Sijie Chen's answer.
import os
import matplotlib.pyplot as plt
from PyQt5 import QtGui
# Whatever string that leads to the directory of the icon including its name
PATH_TO_ICON = os.path.dirname(__file__) + '/static/icons/icon.ico'
plt.get_current_fig_manager().window.setWindowIcon(QtGui.QIcon(PATH_TO_ICON))
If you are using Qt4Agg backend, the following code may help you:
thismanager = plt.get_current_fig_manager()
from PyQt4 import QtGui
thismanager.window.setWindowIcon(QtGui.QIcon((os.path.join('res','shepherd.png'))))
I found that under OS X with PyQT5, doing plt.get_current_fig_manager().window.setWindowIcon() has no effect. To get the dock icon to change you have to call setWindowIcon() on the QApplication instance, not on the window.
What worked for me is:
QApplication.instance().setWindowIcon(QtGui.QIcon(icon_path))
Do mind that QApplication.instance() will be None until you have actually created a figure, so do that first.

Categories