pyqtgraph cross widget to plot not working? - python-3.x

I am trying to add GraphicsLayoutWidget and LinearRegionItem, that two ratio is 4:1 or m:n, but I don't found any layout in pygtgraph.So I try to the code like below, but unfortunately it don't work and cast out traceback error info.
code
import pyqtgraph as pg
import numpy as np
from PyQt5.QtWidgets import *
class Chart(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.data = np.linspace(0, 2*np.pi, 100)
self.data = np.sin(self.data)
self.p = self.addPlot(y=self.data)
class LR(pg.GraphicsLayoutWidget):
def __init__(self, target):
super().__init__()
self.p = self.addPlot() # type:pg.PlotItem
self.target = target # type: pg.PlotItem
rg = (target.data[0], target.data[-1])
self.p.setXRange(rg[0], rg[1], padding=0)
self.lr = pg.LinearRegionItem([rg[0], rg[1]])
self.lr.sigRegionChanged.connect(self.onLRChanged)
self.p.addItem(self.lr)
def onLRChanged(self):
print(self.target, self.lr.getRegion())
self.target.setXRange(*self.lr.getRegion())
class Win(QWidget):
def __init__(self):
super().__init__()
lay = QVBoxLayout()
self.chart = Chart()
self.lr = LR(self.chart)
lay.addWidget(self.chart)
lay.addWidget(self.lr)
lay.setStretch(0, 4)
lay.setStretch(1, 1)
self.setLayout(lay)
app = QApplication([])
win = Win()
win.show()
app.exec()
image
error
The traceback info will show when user drag LinearRegionItem line in both side of the bottom graph(plot)
environment
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32
Name: pyqtgraph Version: 0.12.2
Name: numpy Version: 1.20.3
Name: PyQt5 Version: 5.15.4

The problem has nothing to do with the stretch factor but setting the range to the GraphicsLayoutWidget that expects a QRect, instead of the PlotItem that expects a tuple causing that exception. On the other hand you can improve how to place the initial range based on the range of the data, also it is better to use signals.
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget
import pyqtgraph as pg
import numpy as np
class Chart(pg.GraphicsLayoutWidget):
def __init__(self, xdata, ydata):
super().__init__()
self.p = self.addPlot(x=xdata, y=ydata)
class LR(pg.GraphicsLayoutWidget):
range_changed = pyqtSignal(float, float)
def __init__(self, xmin, xmax):
super().__init__()
self.lr = pg.LinearRegionItem(values=(xmin, xmax))
self.p = self.addPlot()
self.p.addItem(self.lr)
self.lr.sigRegionChanged.connect(self.handle_region_changed)
def handle_region_changed(self, item):
self.range_changed.emit(*item.getRegion())
class Win(QWidget):
def __init__(self):
super().__init__()
xdata = np.linspace(0, 2 * np.pi, 100)
ydata = np.sin(xdata)
self.chart = Chart(xdata, ydata)
self.lr = LR(xdata.min(), xdata.max())
self.lr.range_changed.connect(self.chart.p.setXRange)
lay = QVBoxLayout(self)
lay.addWidget(self.chart, stretch=4)
lay.addWidget(self.lr, stretch=1)
def main():
app = QApplication([])
win = Win()
win.show()
app.exec()
if __name__ == "__main__":
main()

Related

PyQt5 shows black window without widgets while moviepy is concatenating videos

When concatenating two video files, the PyQt window is completely black with no widgets. The background color changes to grey and the widget shows up only after the videos have been concatenated.
I would like to see a grey window and my widget while the videos are being concatenated.
Here is the script:
import os
import sys
from PyQt5.QtWidgets import *
from moviepy.editor import VideoFileClip, concatenate_videoclips
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Using Labels")
self.setGeometry(50,50,350,350)
self.UI()
def UI(self):
text1=QLabel("Hello Python",self)
text1.move(50,50)
self.show()
def make_movie():
sep = os.sep
directory_stub = '.' + sep + 'src' + sep + "assets" + sep
clip1 = VideoFileClip(directory_stub + 'introoutro' + sep + "intro.mp4")
clip2 = VideoFileClip(directory_stub + 'introoutro' + sep + "outro.mp4")
final_clip = concatenate_videoclips([clip1,clip2])
final_clip.write_videofile("my_concatenation.mp4")
def main():
App = QApplication(sys.argv)
window=Window()
make_movie()
sys.exit(App.exec_())
if __name__ == '__main__':
main()
While the videos are being put together I see this:
I want to see this during the video processing but I only see it 1. after the processing is done or 2. if I edit the code to never call make_movie():
Thanks for any help.
Concatenation is a very time consuming task so it should not be run on the main thread as it can freeze the GUI:
import os
import sys
import threading
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication, QLabel, QWidget
from moviepy.editor import VideoFileClip, concatenate_videoclips
class QMovie(QObject):
started = pyqtSignal()
finished = pyqtSignal()
def concatenate(self, inputs, output):
threading.Thread(
target=self._concatenate, args=(inputs, output), daemon=True
).start()
def _concatenate(self, inputs, output):
self.started.emit()
clips = []
for input_ in inputs:
clip = VideoFileClip(input_)
clips.append(clip)
output_clip = concatenate_videoclips(clips)
output_clip.write_videofile(output)
self.finished.emit()
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Using Labels")
self.setGeometry(50, 50, 350, 350)
self.UI()
def UI(self):
text1 = QLabel("Hello Python", self)
text1.move(50, 50)
self.show()
def main():
App = QApplication(sys.argv)
window = Window()
directory_stub = os.path.join(".", "src", "assets")
in1 = os.path.join(directory_stub, "introoutro", "intro.mp4")
in2 = os.path.join(directory_stub, "introoutro", "outro.mp4")
qmovie = QMovie()
qmovie.concatenate([in1, in2], "my_concatenation.mp4")
sys.exit(App.exec_())
if __name__ == "__main__":
main()

How to record the video from a webcam in a pyqt5 gui using OpenCV and QThread?

I'm trying to make pyqt5 Gui that shows a webcam live feed, records the feed at the same time, and saves it locally when closed. I managed to acheieve this using Timer(QTimer) in pyqt gui but When I try to implement it using Qthread (Which I really require) only the live feed is working.
Whenever I add Code required for recording video and run the program it says Python has Stopped Working and closes. Here is my Code:
import cv2
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap
class Thread(QThread):
changePixmap = pyqtSignal(QImage)
def run(self):
self.cap = cv2.VideoCapture(0)
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.codec = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
self.writer = cv2.VideoWriter('output.avi', self.codec, 30.0, (self.width, self.height))
while self.cap.isOpened():
ret, self.frame = self.cap.read()
if ret:
self.frame = cv2.flip(self.frame, 1)
rgbimage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbimage.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(rgbimage.data, w, h, bytesPerLine, QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
self.changePixmap.emit(p)
class MyApp(QWidget):
def __init__(self):
super(MyApp, self).__init__()
self.title = 'Camera'
self.initUI()
def initUI(self):
self.label = QLabel(self)
lay = QVBoxLayout()
lay.addWidget(self.label)
self.setLayout(lay)
self.th = Thread()
self.th.changePixmap.connect(self.setImage)
self.th.start()
self.show()
#pyqtSlot(QImage)
def setImage(self, image):
self.label.setPixmap(QPixmap.fromImage(image))
self.th.writer.write(image)
def main():
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I tried placing the .write() inside the run() of Thread class as well which is showing the same error. Can you guys point out What I'm doing wrong and how to make it work. I'm new to python and pyqt.
Thanks in Advance.
You need to separate threads. First thread is for signal, second is for the record and the main thread is for GUI. Try the following code. There is a button to start/stop the record.
import sys
import cv2
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, pyqtSlot
from PyQt5 import QtWidgets, QtCore, QtGui
#https://ru.stackoverflow.com/a/1150993/396441
class Thread1(QThread):
changePixmap = pyqtSignal(QImage)
def __init__(self, *args, **kwargs):
super().__init__()
def run(self):
self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
self.cap1.set(3,480)
self.cap1.set(4,640)
self.cap1.set(5,30)
while True:
ret1, image1 = self.cap1.read()
if ret1:
im1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
height1, width1, channel1 = im1.shape
step1 = channel1 * width1
qImg1 = QImage(im1.data, width1, height1, step1, QImage.Format_RGB888)
self.changePixmap.emit(qImg1)
class Thread2(QThread):
def __init__(self, *args, **kwargs):
super().__init__()
self.active = True
def run(self):
if self.active:
self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
self.out1 = cv2.VideoWriter('output.avi', self.fourcc, 30, (640,480))
self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
self.cap1.set(3, 480)
self.cap1.set(4, 640)
self.cap1.set(5, 30)
while self.active:
ret1, image1 = self.cap1.read()
if ret1:
self.out1.write(image1)
self.msleep(10)
def stop(self):
self.out1.release()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.resize(660, 520)
self.control_bt = QPushButton('START')
self.control_bt.clicked.connect(self.controlTimer)
self.image_label = QLabel()
self.saveTimer = QTimer()
self.th1 = Thread1(self)
self.th1.changePixmap.connect(self.setImage)
self.th1.start()
vlayout = QVBoxLayout(self)
vlayout.addWidget(self.image_label)
vlayout.addWidget(self.control_bt)
#QtCore.pyqtSlot(QImage)
def setImage(self, qImg1):
self.image_label.setPixmap(QPixmap.fromImage(qImg1))
def controlTimer(self):
if not self.saveTimer.isActive():
# write video
self.saveTimer.start()
self.th2 = Thread2(self)
self.th2.active = True
self.th2.start()
# update control_bt text
self.control_bt.setText("STOP")
else:
# stop writing
self.saveTimer.stop()
self.th2.active = False
self.th2.stop()
self.th2.terminate()
# update control_bt text
self.control_bt.setText("START")
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
you placed the .write() inside the run() of Thread class is right way.
like:
...
while self.cap.isOpened():
ret, self.frame = self.cap.read()
if ret:
self.frame = cv2.flip(self.frame, 1)
rgbimage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbimage.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(
rgbimage.data, w, h, bytesPerLine, QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
# put your writer() here, make sure your param is frame
# not converted to QtImage format
self.writer.write(rgbimage)
self.changePixmap.emit(p)
...

pyqtgraph dynamic plot: add line to open GUI

I am trying to add new lines to an existing and open Plot.
I wrote a watchdog watching a folder with measurement data. Every few secs there will be a new data file. The Application I try to generate should read the file when triggered by the watchdog and add the data to the plot.
Dynamic plots using QTimer and stuff is easy when updating existing data, but I don't get a hook for new lines.
Also, do I have to use multithreading when I want to run a script while having a plot on _exec()?
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pq
import sys
import numpy as np
import time
class Plot2d(object):
def __init__(self):
self.traces = dict()
self.num = 0
pq.setConfigOptions(antialias=True)
self.app = QtGui.QApplication(sys.argv)
self.win = pq.GraphicsWindow(title='examples')
self.win.resize(1000, 600)
self.win.setWindowTitle('Windowtitle')
self.canvas = self.win.addPlot(title='Plot')
def starter(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
def trace(self, name, dataset_x, dataset_y):
if name in self.traces:
self.traces[name].setData(dataset_x, dataset_y)
else:
self.traces[name] = self.canvas.plot(pen='y')
def update(self, i):
x_data = np.arange(0, 3.0, 0.01)
y_data = x_data*i
self.trace(str(i), x_data, y_data)
self.trace(str(i), x_data, y_data)
if __name__ == '__main__':
p = Plot2d()
p.update(1)
p.starter()
time.sleep(1)
p.update(2)
This is what I tried. The update function should be called by the watchdog when new data is available in the dir.
import time as time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
if __name__ == "__main__":
patterns = "*"
ignore_patterns = ""
ignore_directories = False
case_sensitive = True
go_recursively = False
my_event_handler = PatternMatchingEventHandler(patterns, ignore_patterns, ignore_directories, case_sensitive)
def on_created(event):
print(f" {event.src_path} has been created!") #this function should call the plot update
my_event_handler.on_created = on_created
path = (r'C:\Users\...') #path from GUI at some point
my_observer = Observer()
my_observer.schedule(my_event_handler, path, recursive=go_recursively)
my_observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
my_observer.stop()
my_observer.join()
The exec_() method is blocking, so starter() will be too, and will be unlocked when the window is closed, which implies that all the code after starter will only be executed after closing the window. On the other hand you should not use time.sleep in the GUI thread as it blocks the execution of the GUI event loop.
Depending on the technology used, ways of updating elements are offered, in the case of Qt the appropriate way is to use signals so that all the logic of the watchdog will be encapsulated in a QObject that will transmit the information to the GUI through a signal, Then the GUI takes that information to plot.
import sys
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pq
import numpy as np
class QObserver(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(object)
def __init__(self, parent=None):
super(QObserver, self).__init__(parent)
patterns = "*"
ignore_patterns = ""
ignore_directories = False
case_sensitive = True
go_recursively = False
path = r"C:\Users\..." # path from GUI at some point
event_handler = PatternMatchingEventHandler(
patterns, ignore_patterns, ignore_directories, case_sensitive
)
event_handler.on_created = self.on_created
self.observer = Observer()
self.observer.schedule(event_handler, path, recursive=go_recursively)
self.observer.start()
def on_created(self, event):
print(
f" {event.src_path} has been created!"
) # this function should call the plot update
self.dataChanged.emit(np.random.randint(1, 5))
class Plot2d(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.traces = dict()
self.num = 0
pq.setConfigOptions(antialias=True)
self.app = QtGui.QApplication(sys.argv)
self.win = pq.GraphicsWindow(title="examples")
self.win.resize(1000, 600)
self.win.setWindowTitle("Windowtitle")
self.canvas = self.win.addPlot(title="Plot")
def starter(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()
def trace(self, name, dataset_x, dataset_y):
if name in self.traces:
self.traces[name].setData(dataset_x, dataset_y)
else:
self.traces[name] = self.canvas.plot(pen="y")
#QtCore.pyqtSlot(object)
def update(self, i):
x_data = np.arange(0, 3.0, 0.01)
y_data = x_data * i
self.trace(str(i), x_data, y_data)
self.trace(str(i), x_data, y_data)
if __name__ == "__main__":
p = Plot2d()
qobserver = QObserver()
qobserver.dataChanged.connect(p.update)
p.starter()

PyQt5 gui with PyQtGraph plot: Display y axis on the right

I am creating a PyQt5 Gui and I am using PyQtGraph to plot some data. Here is a minimal, complete, verifiable example script that closely resembles the structure that I have.
import sys
from PyQt5.QtWidgets import (QWidget, QGridLayout, QApplication)
import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui
class CustomPlot(pg.GraphicsObject):
def __init__(self, data):
pg.GraphicsObject.__init__(self)
self.data = data
print(self.data)
self.generatePicture()
def generatePicture(self):
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('w', width=1/2.))
for (t, v) in self.data:
p.drawLine(QtCore.QPointF(t, v-2), QtCore.QPointF(t, v+2))
p.end()
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QtCore.QRectF(self.picture.boundingRect())
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.simpleplot()
def initUI(self):
self.guiplot = pg.PlotWidget()
layout = QGridLayout(self)
layout.addWidget(self.guiplot, 0,0)
def simpleplot(self):
data = [
(1., 10),
(2., 13),
(3., 17),
(4., 14),
(5., 13),
(6., 15),
(7., 11),
(8., 16)
]
pgcustom = CustomPlot(data)
self.guiplot.addItem(pgcustom)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This generates a graph that looks like
The y-axis is on the left of the plot but I would like to move it to the right. I have tried a number of things but I cannot find the object (QtGui.QPainter, GraphicObject, etc.) that has the option or method to achieve this.
It can be set by methods of the PlotItem class.
def initUI(self):
self.guiplot = pg.PlotWidget()
plotItem = self.guiplot.getPlotItem()
plotItem.showAxis('right')
plotItem.hideAxis('left')
If you haven't read the section on Organization of Plotting Classes, take look at it. In particular at the relation between the PlotWidet and PlotItem classes.

opencv pyqt video normal frame rate

I’m creating a special purpose video player in Python 3.6 using OpenCV3 and ffmepg for handling the images and using PyQt5 for the Windows environment. I chose this combination of packages because ffmpeg handles a wider variety of codecs than QtMultimedia.
I’ve run into one snag. My player does not play at regular speed – it plays at roughly ¾ of normal speed. I use QTimer.timer to loop my display engine (nextFrameSlot) at a speed of 1/framerate.
Any suggestions on how to get the video to play at regular speed? Here is an abbreviated set of code that demonstrates my problem.
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QFormLayout, QPushButton, QMainWindow
from PyQt5.QtWidgets import QAction, QMessageBox, QApplication, QFileDialog
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer
import cv2
class VideoCapture(QWidget):
def __init__(self, filename, parent):
super(QWidget, self).__init__()
self.cap = cv2.VideoCapture(str(filename[0]))
self.length = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
self.frame_rate = self.cap.get(cv2.CAP_PROP_FPS)
#self.codec = self.cap.get(cv2.CAP_PROP_FOURCC)
self.video_frame = QLabel()
parent.layout.addWidget(self.video_frame)
def nextFrameSlot(self):
ret, frame = self.cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
pix = QPixmap.fromImage(img)
self.video_frame.setPixmap(pix)
def start(self):
self.timer = QTimer()
self.timer.timeout.connect(self.nextFrameSlot)
self.timer.start(1000.0/self.frame_rate)
def pause(self):
self.timer.stop()
def deleteLater(self):
self.cap.release()
super(QWidget, self).deleteLater()
class VideoDisplayWidget(QWidget):
def __init__(self,parent):
super(VideoDisplayWidget, self).__init__(parent)
self.layout = QFormLayout(self)
self.startButton = QPushButton('Start', parent)
self.startButton.clicked.connect(parent.startCapture)
self.startButton.setFixedWidth(50)
self.pauseButton = QPushButton('Pause', parent)
self.pauseButton.setFixedWidth(50)
self.layout.addRow(self.startButton, self.pauseButton)
self.setLayout(self.layout)
class ControlWindow(QMainWindow):
def __init__(self):
super(ControlWindow, self).__init__()
self.setGeometry(50, 50, 800, 600)
self.setWindowTitle("PyTrack")
self.capture = None
self.isVideoFileLoaded = False
self.quitAction = QAction("&Exit", self)
self.quitAction.setShortcut("Ctrl+Q")
self.quitAction.triggered.connect(self.closeApplication)
self.openVideoFile = QAction("&Open Video File", self)
self.openVideoFile.setShortcut("Ctrl+Shift+V")
self.openVideoFile.triggered.connect(self.loadVideoFile)
self.mainMenu = self.menuBar()
self.fileMenu = self.mainMenu.addMenu('&File')
self.fileMenu.addAction(self.openVideoFile)
self.fileMenu.addAction(self.quitAction)
self.videoDisplayWidget = VideoDisplayWidget(self)
self.setCentralWidget(self.videoDisplayWidget)
def startCapture(self):
if not self.capture and self.isVideoFileLoaded:
self.capture = VideoCapture(self.videoFileName, self.videoDisplayWidget)
self.videoDisplayWidget.pauseButton.clicked.connect(self.capture.pause)
self.capture.start()
def endCapture(self):
self.capture.deleteLater()
self.capture = None
def loadVideoFile(self):
try:
self.videoFileName = QFileDialog.getOpenFileName(self, 'Select a Video File')
self.isVideoFileLoaded = True
except:
print ("Please Select a Video File")
def closeApplication(self):
choice = QMessageBox.question(self, 'Message','Do you really want to exit?',QMessageBox.Yes | QMessageBox.No)
if choice == QMessageBox.Yes:
print("Closing....")
sys.exit()
else:
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
window = ControlWindow()
window.show()
sys.exit(app.exec_())
Solved - I needed to specify self.timer.setTimerType(Qt.PreciseTimer) after the statement self.timer = QTimer() in the function start(self). By default, QTimer() uses a coarse timer. For Windows, the coarse time is 15.6 msec intervals.

Resources