Embed a PyQtGraph in Qt Designer GUI - python

I have a QtGraph plot and I want to show it in a QLabel, Qwidget or Graphicsview in a GUI that I made in Qt Designer, but I can't find a direct way that shows that graph. I am streaming a video from a camera and I could easily show it in my GUI, but I am struggling with this one.
The problem is in showing.
#These are the libraries which have been used in the app.
import sys
import numpy as np
import cv2
import pyqtgraph as pg
import datetime
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication , QMainWindow
from PyQt5.uic import loadUi
from PyQt5 import QtGui
#this is the main class for this app and get the visual GUI from QtDesigner
class VideoStream(QMainWindow):
def __init__(self):
super(VideoStream , self).__init__()
loadUi('VideoStream.ui',self)
self.image= None
#self.l_values is the place that we collect the intensity values for each frame
self.l_values = []
#This is a button to set the geometry of our mask for the specific zone of lightness
self.Apply_Button.clicked.connect(self.Geometry)
#this button save the maximum values of the specific zone in a text file
#self.Save_Button.clicked.connect(self.save_data)
self.startButton.clicked.connect(self.start_webcam)
self.stopButton.clicked.connect(self.stop_webcam)
#these two variables are the height and width of our camera.
#With different cameras this part should be changed.
self.cameraHeight=480
self.cameraWidth=640
#This function shows the stream in the GUI we created by qtdesigner
def displayImage(self,img,window=1):
qformat=QtGui.QImage.Format_Grayscale8
outImage=QtGui.QImage(img, img.shape[1],img.shape[0],qformat)
self.imgLabel.setPixmap(QtGui.QPixmap.fromImage(outImage))
self.imgLabel.setScaledContents(True)
#for the seperate canvas you should click on the move button in that window once
def displayHist(self,img, window=1):
self.avr = int(np.average(self.image)*25)
self.avrage=np.array([self.avr])
if self.l<=self.bufferSize:
self.plt.setRange(xRange=[max(self.l- 100, 0), self.l])
self.data[self.l:self.l+self.n] = self.avrage
self.curve.setData(self.data)
print(self.l)
self.l = (self.l+self.n)
if self.l%100==0:
if self.l>=100:
print("y ",self.k,np.max(self.data[self.l-100:self.l]))
self.k=self.k+1
elif self.l==0:
print("- ",self.k,np.max(self.data[-(100-self.l):]))
else:
print("n ",self.k,max(np.max(self.data[-(100-self.l):]),np.max(self.data[:self.l])))
self.k=self.k+1
self.line.setValue(self.l)
if self.l>self.bufferSize:
self.plt.setRange(xRange=[max(self.bufferSize- 100, 0), self.bufferSize])
for j in range(self.bufferSize-1):
self.data[j]=self.data[j+1]
self.data[-1]=self.avrage
self.curve.setData(self.data)
self.l = (self.l+self.n)
self.line.setValue(self.bufferSize)
#this is the place that I don't have any idea what to do

Related

Adding ROI in pyqtgraph in scrolling mode

I am trying to add a ROI in real-time with a button click. Once it is added, the ROI is blocking the line plot to scroll. I tried adding the line Plot widget as a parent to the ROI, that didn't work. Is there any way to make the ROI scroll outside of the view along with line plot? Also, how do I retain the historical data and ROIs, currently, the data is replaced by the new data.
Thanks.
import signal
import time
from math import sin
from threading import Thread
from time import sleep
import pyqtgraph as pg
from pglive.kwargs import Axis
from pglive.sources.data_connector import DataConnector
from pglive.sources.live_axis import LiveAxis
from pglive.sources.live_plot import LiveLinePlot
from pglive.sources.live_plot_widget import LivePlotWidget
"""
In this example Line plot is displayed.
"""
import random
import signal
import sys
from math import sin
from time import sleep
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QApplication,QPushButton
running = True
app = QApplication(sys.argv)
button = QPushButton('Add Rect')
def addRect(button):
print('Added')
roi= pg.ROI((time.time()-3,-1.5),(2,3),pen='g',movable=False)
roi.setZValue(10)
time_axis_plot_widget.addItem(roi)
button.clicked.connect(addRect)
def stop():
"""Stop current QApplication"""
global running
running = False
app.exit(0)
# Connect SIGINT with stop function
signal.signal(signal.SIGINT, lambda sig, frame: stop())
connectors = []
layout = pg.LayoutWidget()
# Define Time plot
left_axis = LiveAxis("left", axisPen="red", textPen="red")
bottom_axis = LiveAxis("bottom", axisPen="green", textPen="green", **{Axis.TICK_FORMAT: Axis.TIME})
time_axis_plot_widget = LivePlotWidget(title="Time Line Plot # 100Hz",
axisItems={'bottom': bottom_axis, 'left': left_axis})
plot = LiveLinePlot()
time_axis_plot_widget.addItem(plot)
connectors.append(DataConnector(plot, max_points=600))
layout.addWidget(time_axis_plot_widget)
layout.addWidget(button)
layout.show()
def sin_wave_generator(*data_connectors):
"""Sinus wave generator"""
x = 0
while running:
x += 1
for data_connector in data_connectors:
data_connector.cb_append_data_point(sin(x * 0.01), time.time())
sleep(0.01)
Thread(target=sin_wave_generator, args=connectors).start()
signal.signal(signal.SIGINT, lambda sig, frame: stop())
app.exec()
stop()

How to reset/ start a new polygon with embeded matplotlib in pyqt

I am trying to create a polygon selector in my PySide2 application. Selector is working fine, but then I want to add functionality to reset/start new polygon when escape button is pressed. Something similar to this PolygonSelector example, when escape is pressed.
https://matplotlib.org/stable/gallery/widgets/polygon_selector_demo.html
I tried method .clear() but it does not seems to work for me.
import sys
import numpy as np
from PySide2 import QtWidgets, QtGui
from matplotlib.backends.backend_qtagg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
from matplotlib.widgets import PolygonSelector
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
shortcut_clear_selection = QtWidgets.QShortcut(QtGui.QKeySequence("Escape"), self._main)
shortcut_clear_selection.activated.connect(self.callback_clear_selection)
layout = QtWidgets.QVBoxLayout(self._main)
static_canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(NavigationToolbar(static_canvas, self))
layout.addWidget(static_canvas)
ax = static_canvas.figure.subplots()
t = np.linspace(0, 10, 501)
ax.plot(t, np.tan(t), ".")
self.poly = PolygonSelector(ax, self.onselect)
def onselect(self, verts):
pass
def callback_clear_selection(self):
# HERE should be the reset part
self.poly.clear()
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()
Problem is, that ESC key release event is not handled by PolygonSelector but Your callback. Therefore, to clear polygon and restart creation of polygon You have to clear polygon data and show selector again. clear method was just hiding selector, but polygon data stayed unchanged.
Change Your callback code into this:
def callback_clear_selection(self):
# HERE should be the reset
self.poly._xs, self.poly._ys = [0], [0]
self.poly._selection_completed = False
self.poly.set_visible(True)
Now, when You press ESC, polygon should be removed and You can start selection of new one.

PyQt5 application: button press event not working [duplicate]

Hopefully I am following the guidelines correctly here with my first question. I am trying to create a GUI with the MVC structure. I am having difficulty with understanding why my signals are not always being picked up by the controller. I know that there is just something simple that I'm missing. I'm attaching code from a simple calculator which I used as a guide. I removed most of the features to simplify this as much as possible. It is now only 3 of the original buttons and my own button. For debugging, I just have the value on the button printed out when you press it.
import sys
# Import QApplication and the required widgets from PyQt5.QtWidgets
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QMainWindow
from PySide2.QtWidgets import QWidget
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QGridLayout
from PySide2.QtWidgets import QLineEdit
from PySide2.QtWidgets import QPushButton
from PySide2.QtWidgets import QVBoxLayout
from functools import partial
ERROR_MSG = 'ERROR'
# Create a subclass of QMainWindow to setup the calculator's GUI
class PyCalcUi(QMainWindow):
"""PyCalc's View (GUI)."""
def __init__(self):
"""View initializer."""
super().__init__()
# Set some main window's properties
self.setWindowTitle('PyCalc')
self.setFixedSize(235, 235)
# Set the central widget and the general layout
self.generalLayout = QVBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.generalLayout)
# Create the display and the buttons
self._createDisplay()
self._createButtons()
def _createDisplay(self):
"""Create the display."""
# Create the display widget
self.display = QLineEdit()
# Set some display's properties
self.display.setFixedHeight(35)
self.display.setAlignment(Qt.AlignRight)
self.display.setReadOnly(True)
# Add the display to the general layout
self.generalLayout.addWidget(self.display)
def _createButtons(self):
"""Create the buttons."""
self.buttons = {}
buttonsLayout = QGridLayout()
# Button text | position on the QGridLayout
buttons = {'7': (0, 0),
'8': (0, 1),
'9': (0, 2),
}
# Create the buttons and add them to the grid layout
for btnText, pos in buttons.items():
self.buttons[btnText] = QPushButton(btnText)
self.buttons[btnText].setFixedSize(40, 40)
buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1])
self.mybutton = QPushButton("5")
buttonsLayout.addWidget(self.mybutton,1,0)
# Add buttonsLayout to the general layout
self.generalLayout.addLayout(buttonsLayout)
# Create a Controller class to connect the GUI and the model
class PyCalcCtrl:
"""PyCalc Controller class."""
def __init__(self, model, view):
"""Controller initializer."""
self._evaluate = model
self._view = view
# Connect signals and slots
self._connectSignals()
def _printthis(self):
print("Hi")
def _printthat(self, buttonvalue):
print(buttonvalue)
def _connectSignals(self):
"""Connect signals and slots."""
self._view.mybutton.clicked.connect(self._printthis)
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
# Create a Model to handle the calculator's operation
def evaluateExpression(expression):
"""Evaluate an expression."""
try:
result = str(eval(expression, {}, {}))
except Exception:
result = ERROR_MSG
return result
# Client code
def main():
"""Main function."""
# Create an instance of QApplication if it doesn't exist
pycalc = QApplication.instance()
if pycalc is None:
pycalc = QApplication(sys.argv)
# Show the calculator's GUI
view = PyCalcUi()
view.show()
# Create instances of the model and the controller
model = evaluateExpression
PyCalcCtrl(model=model, view=view)
# Execute the calculator's main loop
sys.exit(pycalc.exec_())
if __name__ == '__main__':
main()
This set of code works, BUT if I comment out the
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
The self._view.mybutton.clicked.connect(self._printthis) will no longer work.
What is the btn.clicked.connect(partial(self._printthat, btnText)) line doing which is allowing any other signal I put in def _connectSignals(self): to work. What aspect of that line is achieving something that the mybutton signal isn't doing?
The problem is caused because the PyCalcCtrl object is not assigned to a variable so it will be destroyed and therefore the "_printthis" method will not be accessible. On the other hand, when functools.partial is used then the object of the PyCalcCtrl class is assigned to the scope of that function, that's why it works.
The solution is to assign the PyCalcCtrl object to a variable:
ctrl = PyCalcCtrl(model=model, view=view)

Setting initial size of QTabWidget in QSplitter PyQt application

I have a vertical splitter with a QTabWidget at the top and a QPlainTextEdit widget below (used as a logging window). In the real application, the tabs are filled with QWidgets, containing a matplotlib canvas and a QFrame with some control elements:
QSplitter
QPlainTextEdit
QVBoxLayout
QTabWidget
QWidget
QVBoxLayout
FigureCanvas (QSizePolicy.Expanding, QSizePolicy.Expanding)
QFrame (optional)
I would like the application to start with a nice vertical ratio of say 4:1 between the tabs and the logging window. However, using mysplitter.setStretchFactor(4,1) doesn't work here as the sizeHint() of the QTabWidget only is (4,4), causing the QPlainTextEdit with sizeHint() = (256,192) to gobble up nearly all available vertical space. As a workaround, I'm currently setting a fixed height for the QPlainTextWidget but I know that this widget is not the culprit.
I guess I need to fiddle around with sizePolicies or with the layout / sizes of the individual tabs but so far I haven't been successful. I've attached a MWE, the full code is available at https://github.com/chipmuenk/pyFDA/blob/master/pyfda/pyfdax.py :
# -*- coding: utf-8 -*-
from __future__ import print_function
from PyQt5.QtWidgets import (QWidget, QTabWidget, QPlainTextEdit, QSplitter,
QMainWindow, QVBoxLayout, QApplication)
from PyQt5.QtGui import QFontMetrics
from PyQt5 import QtCore
#------------------------------------------------------------------------------
class TabWidgets(QTabWidget):
def __init__(self, parent):
super(TabWidgets, self).__init__(parent)
self.wdg1 = QWidget(self)
self.wdg2 = QWidget(self)
self._construct_UI()
#------------------------------------------------------------------------------
def _construct_UI(self):
""" Initialize UI with tabbed subplots """
self.tabWidget = QTabWidget(self)
self.tabWidget.addTab(self.wdg1, 'Wdg 1')
self.tabWidget.addTab(self.wdg2, 'Wdg 2')
layVMain = QVBoxLayout()
layVMain.addWidget(self.tabWidget)
self.setLayout(layVMain)
# When user has switched the tab, call self.current_tab_redraw
self.tabWidget.currentChanged.connect(self.current_tab_redraw)
#------------------------------------------------------------------------------
def current_tab_redraw(self):
pass
#self.tabWidget.currentWidget().resize()
class MWin(QMainWindow):
"""
Main window consisting of a tabbed widget and a status window.
QMainWindow is used as it understands GUI elements like central widget
"""
def __init__(self, parent=None):
super(QMainWindow,self).__init__()
#---------------------------------------------------------------
statusWin = QPlainTextEdit(self) # status window
tabWin = TabWidgets(self) # tabbed window
print('size status win: {0}'.format(statusWin.sizeHint()))
print('size_tab win: {0}'.format(tabWin.sizeHint()))
mSize = QFontMetrics(statusWin.font())
rowHt = mSize.lineSpacing()
# fixed height for statusWin needed as the sizeHint of tabWin is very small
statusWin.setFixedHeight(4*rowHt+4)
# add status window underneath plot Tab Widgets:
spltVMain = QSplitter(QtCore.Qt.Vertical)
spltVMain.addWidget(tabWin)
spltVMain.addWidget(statusWin)
# relative initial sizes of subwidgets, this doesn't work here
spltVMain.setStretchFactor(4,1)
spltVMain.setFocus()
# make spltVMain occupy the main area of QMainWindow and set inheritance
self.setCentralWidget(spltVMain)
#----------------------------------------------------------------------------
def main():
import sys
app = QApplication(sys.argv)
mainw = MWin(None)
mainw.resize(300,400)
app.setActiveWindow(mainw)
mainw.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I've found an easy workaround: Setting the splitter in absolute units instead of a ratio does the job. Stating with the total height of the splitter widget, makes the solution work with different resolutions etc. The code snippet below shows the updated __init__() part:
def __init__(self, parent=None):
super(QMainWindow,self).__init__()
#---------------------------------------------------------------
statusWin = QPlainTextEdit(self) # status window
tabWin = TabWidgets(self) # tabbed window
print('size status win: {0}'.format(statusWin.sizeHint()))
print('size_tab win: {0}'.format(tabWin.sizeHint()))
# fixed height for statusWin no longer needed here
# mSize = QFontMetrics(statusWin.font())
# rowHt = mSize.lineSpacing()
# statusWin.setFixedHeight(4*rowHt+4)
# add status window underneath plot Tab Widgets:
spltVMain = QSplitter(QtCore.Qt.Vertical)
spltVMain.addWidget(tabWin)
spltVMain.addWidget(statusWin)
# relative initial sizes of subwidgets, this doesn't work here
# spltVMain.setStretchFactor(4,1)
# Use absolute values instead:
spltVMain.setSizes([spltVMain.size().height() * 0.8,
spltVMain.size().height() * 0.2])
spltVMain.setFocus()
# make spltVMain occupy the main area of QMainWindow and set inheritance
self.setCentralWidget(spltVMain)
#-----------------------------------------------------------------------

python VTK context menu not at mouse position

I am having problems with the context menu position in VTK with PyQt. The main GUI window has set the VTK widget as central widget:
from vtk_widget.vtk_widget import VTKWidget
class DySMainWindow(QtGui.QMainWindow):
def __init__(self):
self.vtk_widget = VTKWidget(self)
self.setCentralWidget(self.vtk_widget)
and the VTK widget is:
import vtk
from vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt4 import QtGui, QtCore, Qt
class VTKWidget(QVTKRenderWindowInteractor):
def __init__(self, MBD_system=None, parent=None):
super(VTKWidget, self).__init__(parent)
# this should show context menu
self.AddObserver("RightButtonPressEvent", self.contextMenu)
self.renderer = vtk.vtkRenderer()
self.GetRenderWindow().AddRenderer(self.renderer)
self.interactor = self.GetRenderWindow().GetInteractor()
self.interactor.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
# camera object
self.camera = self.renderer.GetActiveCamera()
if self.projection == 'perspective':
self.camera.ParallelProjectionOff()
else:
self.camera.ParallelProjectionOn()
self.renderer.SetActiveCamera(self.camera)
self.renderer.ResetCamera()
self.renderer.SetBackground(0, 0, 0)
self.interactor.Initialize()
def contextMenu(self, caller, event):
pos = self.interactor.GetEventPosition()
menu = QtGui.QMenu(parent=self)
menu.addAction(self.tr("Edit object"))
menu.exec_(self.mapToGlobal(QtCore.QPoint(pos[0], pos[1])))
Any help solving this would be appreciated.
The contextmeny event method takes a point as an input. If we assume that your menu is called qMenuVTK and you have a parent window, the following should work:
In your rightbuttonpressevent add the following:
self.parent.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.parent.customContextMenuRequested.connect(self.onContextMenu)
And the method event will look like:
def onContextMenu(self, point):
self.qMenuVTK.exec_(self.parent.mapToGlobal(point))

Categories