PySide6 as a process in a script - python

I am running a data analysis script and I much prefer plotly to matplotlib. I am curious if it is possible to do the same figure windows like in matplotlib without blocking the script.
This post gave the following code that generates a WebEngineWindget (I used PySide6 here) with an offline plotly chart
import numpy as np
import plotly.graph_objs as go
import plotly.offline
fig = go.Figure()
fig.add_scatter(x=np.random.rand(100), y=np.random.rand(100), mode='markers',
marker={'size': 30, 'color': np.random.rand(100), 'opacity': 0.6,
'colorscale': 'Viridis'});
import os, sys
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QUrl
from PySide6 import QtWebEngineWidgets
class PlotlyViewer(QtWebEngineWidgets.QWebEngineView):
def __init__(self, fig, exec=True):
# Create a QApplication instance or use the existing one if it exists
self.app = QApplication.instance() if QApplication.instance() else QApplication(sys.argv)
super().__init__()
self.file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "temp.html"))
plotly.offline.plot(fig, filename=self.file_path, auto_open=False)
self.load(QUrl.fromLocalFile(self.file_path))
self.setWindowTitle("Plotly Viewer")
self.show()
if exec:
self.app.exec()
def closeEvent(self, event):
os.remove(self.file_path)
win = PlotlyViewer(fig)
My question is how I can do this without blocking my script. If I do
win = PlotlyViewer(fig)
win2 = PlotlyViewer(fig2)
win3 = PlotlyViewer(fig3)
Then win2 doesnt pop up until the first one is closed. I understand I could make a widget that has 3 instead, but sometimes I am just looking at 1 plot, other times more, so I am curious if I can run as a process that would keep executing. Or have I run into the thing I read everywhere where the GIL turns people away from python?

Related

"event loop is already running" while using the pdb debugger in an application running with matplotlib and PySide2

I have an application in which the user has to click on images using matplotlib. I want to use the debugger pdb (line 53), however, when I launch the app and click on the button, a message
QCoreApplication::exec: The event loop is already running
appears and prevents me of using the debugger. I suspect that it comes from the following lines but I'm not sure
import matplotlib
try:
matplotlib.rcParams['backend.qt5'] = 'PySide2'
except (KeyError, Exception):
pass
matplotlib.use('Qt5Agg')
I did implement those lines thanks to the answer I got from this question I asked previously:
Same code with PyQT5/PySide2 runs on MacOS but throws an error on Linux
How could I retain the same code structure with backend and being able to use the pdb debugger?
import sys
import os
import subprocess
from subprocess import call
import matplotlib
import pdb
try:
matplotlib.rcParams['backend.qt5'] = 'PySide2'
except (KeyError, Exception):
pass
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtWidgets import QWidget, QApplication, QPushButton, QFileDialog
from PySide2.QtCore import QFile, QTextStream
os.environ['QT_MAC_WANTS_LAYER'] = '1'
from IPython.core import ultratb
import pdb
sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=True)
class GUI(QWidget):
def __init__(self):
super(GUI, self).__init__()
self.initUI()
def initUI(self):
height_btn = 40
width_btn = 350
button_position_x = 0
button_position_y = 20
button_position_x = button_position_x = 0
button_position_y = button_position_y + 400
btn15 = QPushButton('button', self)
btn15.clicked.connect(self.Plotfunction)
btn15.resize(width_btn, height_btn)
btn15.move(button_position_y, button_position_x)
self.show()
def Plotfunction(self):
pdb.set_trace()
print("ok")
def main():
app = QApplication(sys.argv)
ex = GUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
By default matplotlib will load the binding (PyQt or PySide2) that is already imported, or if there are none loaded then it will try to import them and the first one that manages to import it will use it. Apparently in the case of the OP it is first importing PyQt causing that problem. The solution is to import PySide2 and then matplotlib so that it prefers PySide2 as the backend binding.

how to embed Schemdraw inside PyQt

Schemdraw is using matplotlib to draw and show the schematic by python.
for example if you run the following code, it draws in matplot.
Now the question is how to embed this matplotlib inside PyQt5?
thanks
import schemdraw
import schemdraw.elements as elm
d = schemdraw.Drawing(fontsize=10)
d.add(elm.Capacitor())
r=d.add(elm.Resistor(theta=40))
d.add(elm.Diode(label="D1"))
d.draw()
With the version provided by pypi, you cannot embed schemdraw (at least in a simple way) but reviewing the repository I see that in the next release the draw method is modified to accept an axis so you must install schemdraw from the repository:
python -m pip install git+https://bitbucket.org/cdelker/schemdraw.git
Then modifying the official matplotlib example you can embed schemdraw:
import sys
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
import schemdraw
import schemdraw.elements as elm
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(canvas)
self.addToolBar(NavigationToolbar(canvas, self))
ax = canvas.figure.subplots()
d = schemdraw.Drawing(fontsize=10)
d.add(elm.Capacitor())
r = d.add(elm.Resistor(theta=40))
d.add(elm.Diode(label="D1"))
d.draw(ax=ax)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()

plot doesn't update on button click but does when window resized

What's going on:
Program in python3/qt5/matplotlib. I am plotting three subplots and few plots in each. I wanted to update plots if user changes any data. That proved rather complicated hence the button 'Replot'. It calls a function that clears canvas and ... reloads the entire widget. It doesn't work as expected: when I press the button nothing happens, but when window gets resized after button clicked it does update the plot.
What would be a correct way to do it without having to put all the plots in that reload function?
I've managed to cut out a minimum code that does what I described - only it'd require the GUI file (created in qt5 designer), sensor log files and a settings file for configparser. (That indentation change on mplwidget is botched on purpose so that code displays as code. Didn't know how to make it display correctly otherwise)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys, os
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuick
import matplotlib
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import dates
import numpy as np
import pandas as pd
import configparser
from RPGH_main_gui_signals_sorted import Ui_MainWindow
sys.path.append('..')
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__)) + "/"
config_f = 'settings_config_parser.py'
cfg = configparser.ConfigParser()
cfg.read('settings_config_parser.py')
class DesignerMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.replot_pushButton.clicked.connect(self.mpl_replot)
self.temperature_temp_max.setValue(cfg.getint('temperature', 'temp_max'))
self.temperature_temp_max.valueChanged.connect(self.write_temp_max)
self.mplwidget()
def mplwidget(self):
temp_max = cfg.getint('temperature', 'temp_max')
temp_log = 'sensor_logs/temperature_out.dat'
time_format = '%d,%m,%Y,%X'
temp_data = pd.read_csv(temp_log, header=None, delim_whitespace=True)
temp_data.columns = ['timestamp', 'temp_up', 'temp_down',
'temp_ground']
timestamp = pd.to_datetime(pd.Series(temp_data.timestamp),
format=time_format)
self.mpl.canvas.ax.plot(timestamp, temp_data.temp_up)
self.mpl.canvas.ax.fill_between(timestamp, temp_data.temp_up, temp_max,
where=temp_data.temp_up>=temp_max, edgecolor='red', facecolor='none',
hatch='/', interpolate=True)
def mpl_replot(self):
self.mpl.canvas.ax.clear()
self.mplwidget()
def write_temp_max(self):
send = self.temperature_temp_max.value()
message = str(send)
cfg.set('temperature', 'temp_max', message)
with open(config_f, 'w') as conffile:
cfg.write(conffile)
if __name__ == '__main__':
app = QtWidgets.QApplication([sys.argv])
gui = DesignerMainWindow()
gui.show()
sys.exit(app.exec_())

Embedding matplotlib figure in QtDesigner GUI

I am trying to muddle my way through embedding a matplotlib figure inside of a Qt GUI created using Qt Designer. I am already able to create the figure I want just through Python. I've created a basic GUI with widgets to allow me to select/load input files, and plot the data in those files in a matplotlib figure that is embedded in the GUI. I accomplish this by adding a blank widget to the GUI called plotwidget, and then calling the GraphInit class with this widget as input.
The problem I am currently facing is that while my plot shows up and updates as desired inside the GUI, it doesn't seem to pay attention to the size policy defined for it in either my Python code (where the FigureCanvas is created) or for the plotWidget widget in Qt Designer. I have been using this demo, among others, as a starting point for this project. However, all of these examples have generated the GUI entirely from within Python. I suspect I am doing something wrong in assigning the matplotlib figure to the widget, but I haven't been able to figure out what.
Code (import & plotting code removed, but the core where figure is added is there):
import sys
import os
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMessageBox, QFileDialog, QPushButton, QLabel, QRadioButton, QDoubleSpinBox, QSpinBox, QWidget, QSizePolicy, QMainWindow
import PyQt5.uic
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import json
from datetime import datetime
import numpy as np
class LogViewer(QApplication):
def __init__(self):
QApplication.__init__(self, sys.argv)
self.ui = UI()
#staticmethod
def main():
vwr = LogViewer()
vwr.run()
def run(self):
self.ui.win.show() # Show the UI
self.ui.run() # Execute the UI run script
self.exec_()
class Graph_init(FigureCanvas):
def __init__(self, parent=None):
fig = Figure()
self.axes = fig.add_subplot(111)
self.compute_initial_figure()
self.axes.grid()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class UI(object):
def __init__(self):
ui_path = os.path.dirname(os.path.realpath(__file__))
self.win = PyQt5.uic.loadUi(ui_path + '\\logview.ui')
self.filename = ui_path + '\\test.txt'
self.plotWin = Graph_init(self.win.plotWidget)
def run(self):
self.init_ui() # get initial values from the controllers
self.attach_ui_connections()
def init_ui(self):
w = self.win
w.txtLogFilename.setText(self.filename.split('/')[-1]) # just the file (no path)
def attach_ui_connections(self):
w = self.win
if __name__ == '__main__':
LogViewer.main()
GUI is available here. Any suggestions appreciated!
I checked your ui file and plotWidget has no layout at all. Try to give it one, I suggest a grid layout.
Then, after parenting the canvas
self.setParent(parent)
try adding it to the parent layout:
parent.layout().addWidget(self)

Python script crashes when closing after running matplotlib with qt4agg because of import

I have written a script using matplotlib, which runs just fine with the standard matplotlib. The script is written with the plot as a class, and calling Plot() is enough to get it running.
Now I want to add some buttons to the toolbar, and to do this I am using qt4agg because I have installed matplotlib via Anaconda. When writing the code for the main window I used this example, and it runs just fine. In order to use the plot script I have already written I want to pass the figure created in the QT-script to the Plot()-class.
This solution works just fine, until I try to close the window. The window closes, and python crashes. It crashes even though I do not call the Plot()-class, and the only way to get it to not crash is to remove the line importing the file. Is there something special I need to think about when importing a script into a window?
from __future__ import print_function
import sys
from matplotlib.figure import Figure
from matplotlib.backend_bases import key_press_handler
### LINE CAUSING TROUBLE
from plotting import Plot
###
from test import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends import qt_compat
use_pyside = qt_compat.QT_API == qt_compat.QT_API_PYSIDE
if use_pyside:
print ("USING PYSIDE")
from PySide.QtCore import *
from PySide.QtGui import *
else:
print("NOT USING PYSIDE")
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class AppForm(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.create_main_frame()
self.on_draw()
def create_main_frame(self):
self.main_frame = QWidget()
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.canvas.mpl_connect('key_press_event', self.on_key_press)
vbox = QVBoxLayout()
vbox.addWidget(self.canvas)
vbox.addWidget(self.mpl_toolbar)
self.main_frame.setLayout(vbox)
self.setCentralWidget(self.main_frame)
def on_draw(self):
self.fig.clear()
#Plot(self.fig)
self.canvas.draw()
def on_key_press(self, event):
key_press_handler(event, self.canvas, self.mpl_toolbar)
def main():
app = QApplication(sys.argv)
form = AppForm()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Here is a very compressed version of the other file that still causes the error.
import matplotlib.pyplot as pplt
class Plot():
def __init__(self, figure=pplt.figure()):
self.figure = figure
i had the same problems with Anaconda and Python(x,y). then i tried to install Python from scratch :
python-2.7.10.msi
PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe
VCForPython27.msi
pip install matplotlib
it doesn't solve all crashes. you could also try this :
self.setAttribute(Qt.WA_DeleteOnClose)
for example :
class AppForm(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose) # <---
self.create_main_frame()
self.on_draw()
...

Categories