I'm new to GUI-programming and need help with a QThread application.
I designed a GUI-Programm which records a Signal from the microphone and plots it in a Figure at the same time.
Now I want to evaluate the signal in another thread, so it still records and plots in the GUI.
The streaming and plotting works fine but everytime I start the thread the GUI freezes and then exits.
Does somebody know what I did wrong in my code, I don't have that much Programming-experience?
# Imports ----------------------------
import sys
import time
import numpy as np
import pyaudio
from PyQt5 import QtGui, QtWidgets, QtCore
import matplotlib
from matplotlib.mlab import find
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
matplotlib.use('Qt5Agg')
class Window(QtWidgets.QMainWindow):
def __init__(self): # template for rest of GUI,
super(Window, self).__init__()
self.setGeometry(50, 50, 1500, 900)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.channels = 2 # StereoSignal
self.fs = 44100 # Samplingrate
self.Chunks = 4096 # Buffersize
self.streamstart = False
self.audiodata = [] # to buffer streaming-values in
self.tapeLength = 4 # seconds
self.tape = np.empty(self.fs * self.tapeLength) * np.nan # tape to store signal-chunks
self.home()
def home(self):
btn = QtWidgets.QPushButton("Stream and Plot", self) # Button to start streaming
btn.clicked.connect(self.plot)
btn.move(100, 100)
btn = QtWidgets.QPushButton("Stop", self) # Button to stop streaming
btn.clicked.connect(self.stop_signal)
btn.move(200, 100)
btn = QtWidgets.QPushButton("Evaluate", self) # Button for the Evaluation
btn.clicked.connect(self.evaluation)
btn.move(100, 140)
self.textEdit = QtWidgets.QTextEdit(self) # Show text of evaluation
self.textEdit.move(250, 170)
self.textEdit.resize(200, 200)
self.scrollArea = QtWidgets.QScrollArea(self) # Scroll-Area to plot signal (Figure) in
self.scrollArea.move(75, 400)
self.scrollArea.resize(600, 300)
self.scrollArea.setWidgetResizable(False)
self.figure = Figure((15, 2.8), dpi=100) # figure instance (to plot on) F(width, height, ...)
self.canvas = FigureCanvas(self.figure)
self.scrollArea.setWidget(self.canvas)
self.gs = gridspec.GridSpec(1, 1)
self.ax = self.figure.add_subplot(self.gs[0])
self.figure.subplots_adjust(left=0.05)
def start_stream(self, start=True):
"""start a Signal-Stream with pyAudio, with callback (to also play immediately)"""
if start is True:
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input=True,
output=True, frames_per_buffer=self.Chunks, stream_callback=self.callback)
self.streamstart = True
self.stream.start_stream()
print("Recording...")
def callback(self, in_data, frame_count, time_info, flag):
"""Callback-Function which stores the streaming data in a list"""
data = np.fromstring(np.array(in_data).flatten(), dtype=np.float32)
self.audiodata = data
print("appending...")
return data, pyaudio.paContinue
def tape_add(self):
"""add chunks from (callback)-list to tapes for left and right Signalparts"""
if self.streamstart:
self.tape[:-self.Chunks] = self.tape[self.Chunks:]
self.taper = self.tape # tape for right signal
self.tapel = self.tape # tape for left signal
self.tapel[-self.Chunks:] = self.audiodata[::2]
self.taper[-self.Chunks:] = self.audiodata[1::2]
print("taping...")
else:
print("No streaming values found")
def plot(self):
"""Start the streaming an plot the signal"""
print("(Stereo-)Signal streaming & plotting...")
if self.streamstart:
pass
else:
self.start_stream(start=True)
self.t1 = time.time()
time.sleep(0.5)
while self.streamstart:
QtWidgets.QApplication.processEvents() # does this still work with threads?
print("Plotting...")
self.tape_add()
self.timeArray = np.arange(self.taper.size)
self.timeArray = (self.timeArray / self.fs) * 1000 # scale to milliseconds
self.ax.clear()
self.ax.plot(self.timeArray, (self.taper / np.max(np.abs(self.taper))), '-b')
self.ax.grid()
self.ax.set_ylabel("Amplitude")
self.ax.set_xlabel("Samples")
self.canvas.draw()
def stop_signal(self):
print("Stopping Signal.")
if self.streamstart:
print("Stop Recording")
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
self.streamstart = False
else:
pass
def evaluation(self):
""" Start the evaluation in another Thread"""
threader = WorkerThread(self.taper, self.tapel)
thread = QtCore.QThread()
# threader.threadDone.connect(self.thread_done) # doesn't work yet
thread.started.connect(threader.run)
thread.start() # start thread
class WorkerThread(QtCore.QObject):
def __init__(self, taper, tapel): # take the tape-parts from the original thread
# super().__init__() # do I need this or next?
QtCore.QThread.__init__(self)
self.__taper = taper
self.__tapel = tapel
def run(self):
"""Do evaluation, later mor, for now just some calculations"""
print("Evaluating Signal")
self.tpr = self.__taper.astype(np.float32, order='C') / 32768 # here the GUI freezes and then exits
self.tpl = self.__tapel.astype(np.float32, order='C') / 32768
# cut nan-values if there are some
self.r = self.tpr[~np.isnan(self.tpr)]
self.l = self.tpl[~np.isnan(self.tpl)]
# normalize signals
self.left2 = (self.l / np.max(np.abs(self.l)))
self.right2 = (self.r / np.max(np.abs(self.r)))
self.norm_audio2 = np.array((self.left2, self.right2)) # like channels (in de_interlace)
# do some calculations
self.databew = """ Mute, Loudness and PSNR/MOS...
Dominant fundamental frequencies etc.
"""
print(self.databew)
# self.textEdit.append(self.databew) # would this work?
# self.threadDone.emit('Thread-Bewertung Done.') # later implemented
def main():
app = QtWidgets.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
main()
So the streaming parts work, maybe someone can tell me what's wrong with the Threading-Part, where I want to do some simple calculations with the recorded signal?
The thread doesn't work with the Signal still recording, but also not, when I stop the recording and plotting and have the Signal in a buffer.
I'm sorry I couldn't get a simpler programm working with similar values, where the same problem occurs.
Hope someone can still help me?
thx, Julia
After a little trying out different things I found a sulition. So the Problem was indeed the QApplication.ProcessEvents-part. This is for completing loops in PyQt, but mine is a endless loop, only stopped after a button-klick. This is why the GUI froze everytime I used it.
The solution now, was to put the plotting part also in a new Thread, which can access the GUI-window.
Here is the new code, which works fine and reasonable fast:
# Imports ----------------------------
import sys
import time
import numpy as np
import pyaudio
from PyQt5 import QtGui, QtWidgets, QtCore
import matplotlib
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
matplotlib.use('Qt5Agg')
class Window(QtWidgets.QMainWindow):
def __init__(self): # template for rest of GUI,
super(Window, self).__init__()
self.setGeometry(50, 50, 1500, 900)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.channels = 2 # StereoSignal
self.fs = 44100 # Samplingrate
self.Chunks = 4096 # Buffersize
self.streamstart = False
self.audiodata = [] # to buffer streaming-values in
self.tapeLength = 4 # seconds
self.tape = np.empty(self.fs * self.tapeLength) * np.nan # tape to store signal-chunks
self.home()
def home(self):
btn = QtWidgets.QPushButton("Stream and Plot", self) # Button to start streaming
btn.clicked.connect(self.plot)
btn.move(100, 100)
btn = QtWidgets.QPushButton("Stop", self) # Button to stop streaming
btn.clicked.connect(self.stop_signal)
btn.move(200, 100)
btn = QtWidgets.QPushButton("Evaluate", self) # Button for the Evaluation
btn.clicked.connect(self.evaluation)
btn.move(100, 140)
self.textEdit = QtWidgets.QTextEdit(self) # Show text of evaluation
self.textEdit.move(250, 170)
self.textEdit.resize(200, 200)
self.scrollArea = QtWidgets.QScrollArea(self) # Scroll-Area to plot signal (Figure) in
self.scrollArea.move(75, 400)
self.scrollArea.resize(600, 300)
self.scrollArea.setWidgetResizable(False)
self.figure = Figure((15, 2.8), dpi=100) # figure instance (to plot on) F(width, height, ...)
self.canvas = FigureCanvas(self.figure)
self.scrollArea.setWidget(self.canvas)
self.gs = gridspec.GridSpec(1, 1)
self.ax = self.figure.add_subplot(self.gs[0])
self.figure.subplots_adjust(left=0.05)
def start_stream(self, start=True):
"""start a Signal-Stream with pyAudio, with callback (to also play immediately)"""
if start is True:
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input=True,
output=True, frames_per_buffer=self.Chunks, stream_callback=self.callback)
self.streamstart = True
self.stream.start_stream()
print("Recording...")
def callback(self, in_data, frame_count, time_info, flag):
"""Callback-Function which stores the streaming data in a list"""
data = np.fromstring(np.array(in_data).flatten(), dtype=np.float32)
self.audiodata = data
print("appending...")
return data, pyaudio.paContinue
def tape_add(self):
"""add chunks from (callback)-list to tapes for left and right Signalparts"""
if self.streamstart:
self.tape[:-self.Chunks] = self.tape[self.Chunks:]
self.taper = self.tape # tape for right signal
self.tapel = self.tape # tape for left signal
self.tapel[-self.Chunks:] = self.audiodata[::2]
self.taper[-self.Chunks:] = self.audiodata[1::2]
print("taping...")
else:
print("No streaming values found")
def plot(self):
"""Start the streaming an plot the signal"""
print("(Stereo-)Signal streaming & plotting...")
self.plot_thread = PlotThead(self)
self.plot_thread.start()
def stop_signal(self):
print("Stopping Signal.")
if self.streamstart:
print("Stop Recording")
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
self.streamstart = False
self.plot_thread.stop()
else:
pass
def evaluation(self):
""" Start the evaluation in another Thread"""
self.thread = WorkerThread(self, self.taper, self.tapel)
self.thread.start() # start thread
class PlotThead(QtCore.QThread):
def __init__(self, window):
QtCore.QThread.__init__(self)
self.deamon = True
self.__is_running = True
self.window = window
def stop(self):
self.__is_running = False
def run(self):
if self.window.streamstart:
pass
else:
self.window.start_stream(start=True)
self.window.t1 = time.time()
time.sleep(0.5)
while self.window.streamstart and self.__is_running:
print("Plotting...")
self.window.tape_add()
self.window.timeArray = np.arange(self.window.taper.size)
self.window.timeArray = (self.window.timeArray / self.window.fs) * 1000 # scale to milliseconds
self.window.ax.clear()
self.window.ax.plot(self.window.timeArray, (self.window.taper / np.max(np.abs(self.window.taper))), '-b')
self.window.ax.grid()
self.window.ax.set_ylabel("Amplitude")
self.window.ax.set_xlabel("Samples")
self.window.canvas.draw()
class WorkerThread(QtCore.QThread):
def __init__(self, window, taper, tapel): # take the tape-parts from the original thread
QtCore.QThread.__init__(self)
self.__taper = taper
self.__tapel = tapel
self.deamon = True
self.window = window
def run(self):
"""Do evaluation, later mor, for now just some calculations"""
print("Evaluating Signal")
self.tpr = self.__taper.astype(np.float32, order='C') / 32768 # here the GUI freezes and then exits
self.tpl = self.__tapel.astype(np.float32, order='C') / 32768
# cut nan-values if there are some
self.r = self.tpr[~np.isnan(self.tpr)]
self.l = self.tpl[~np.isnan(self.tpl)]
# normalize signals
self.left2 = (self.l / np.max(np.abs(self.l)))
self.right2 = (self.r / np.max(np.abs(self.r)))
self.norm_audio2 = np.array((self.left2, self.right2)) # like channels (in de_interlace)
# do some calculations
self.databew = """ Mute, Loudness and PSNR/MOS...
Dominant fundamental frequencies etc.
"""
print(self.databew)
self.window.textEdit.append(self.databew) # would this work?
def main():
app = QtWidgets.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
main()
Related
I am writing a WebCam Gui, which is supposed to take pictures and manipulate with WebCam parameters. After the video stream is activated on the main GUI window, an additional window can be opened to change the WebCamera parameters Screenshot 1, Screenshot 2.
I am using Qthread to stream on QLabel. Also, I was able to set the initial camera parameters on the camera properties' changing window. My problem is changing the Exposure parameter by using a slider on the sub-window and seeing results in real-time on the main window.
Please see the code.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import cv2
class MainFrame(QMainWindow):
def __init__(self):
super(MainFrame, self).__init__()
# Loading UI
uic.loadUi("MainFrame.ui", self)
# Remove maximize button to prevent crushing
self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint)
# Define Widgets
self.Video_Feed = self.findChild(QLabel, 'Video_Feed')
self.Case_Name = self.findChild(QLineEdit, "Case_Name")
self.Pictures_List = self.findChild(QListWidget, "Pictures_List")
self.Start_Video = self.findChild(QAction, 'actionStart_Video')
self.Start_Video.setShortcut('Shift+S')
self.Stop_Video = self.findChild(QAction, 'actionStop_Video')
self.Stop_Video.setShortcut('Shift+F')
self.Take_a_Picture = self.findChild(QAction, 'actionTake_a_picture')
self.Take_a_Timed_Picture = self.findChild(QAction, 'actionTake_a_timed_picture')
self.Camera_Properties = self.findChild(QAction, 'actionProperties')
# Initializing Video
self.Start_Video.triggered.connect(self.Start_Video_Clicked)
self.Stop_Video.triggered.connect(self.Stop_Video_Clicked)
self.Camera_Properties.triggered.connect(self.Camera_Properties_Clicked)
def Video_Feed_Update(self, Image):
self.Video_Feed.setPixmap(QPixmap.fromImage(Image))
def Start_Video_Clicked(self):
self.Video_Feed_is_Running = True
self.thread = QThread()
self.Video_Thread = Worker()
self.Video_Thread.moveToThread(self.thread)
self.Video_Thread.ImageUpdate.connect(self.Video_Feed_Update)
self.thread.started.connect(self.Video_Thread.run)
self.thread.start()
def Stop_Video_Clicked(self):
self.Video_Thread.stop_video()
self.Video_Feed.setText("Your video starts here")
def Camera_Properties_Clicked(self):
self.CP = CameraParameters()
Initial_Exposure = self.Video_Thread.Camera_Initial_Parameters()
self.CP.Setup_Exposure(int(Initial_Exposure))
self.CP.Exposure_Calibration.connect(self.Video_Thread.Exposure_update)
self.CP.show()
class Worker(QObject):
ImageUpdate = pyqtSignal(QImage)
def run(self):
self.ThreadActive = True
self.Capture = cv2.VideoCapture(1, cv2.CAP_DSHOW)
self.Capture.set(3, 1920)
self.Capture.set(4, 1080)
while self.ThreadActive:
ret, frame = self.Capture.read()
if ret:
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Converting Video into QT5 readable format
qt_video_format = QImage(image.data, image.shape[1], image.shape[0], QImage.Format_RGB888)
qt_picture = qt_video_format.scaled(1280, 720, Qt.KeepAspectRatio)
self.ImageUpdate.emit(qt_picture)
def stop_video(self):
self.ThreadActive = False
self.Capture.release()
def Camera_Initial_Parameters(self):
return self.Capture.get(cv2.CAP_PROP_EXPOSURE)
def Exposure_update(self, value):
self.Capture.set(cv2.CAP_PROP_EXPOSURE, value)
class CameraParameters(QDialog):
Exposure_Calibration = pyqtSignal(int)
def __init__(self):
super().__init__()
uic.loadUi('Cam_Parameters.ui', self)
# Sliders
self.Exposure_Slider = self.findChild(QSlider, 'ExposureSlider')
self.Exposure_Slider.setRange(-10, 10)
self.White_Balance_Slider = self.findChild(QSlider, 'WBSlider')
self.White_Balance_Slider.setMinimum(-10)
self.White_Balance_Slider.setMaximum(10)
self.Brightness_Slider = self.findChild(QSlider, 'BrightnessSlider')
self.Brightness_Slider.setMinimum(0)
self.Brightness_Slider.setMaximum(300)
self.Saturation_Slider = self.findChild(QSlider, 'SaturationSlider')
self.Saturation_Slider.setMinimum(0)
self.Saturation_Slider.setMaximum(300)
self.Contrast_Slider = self.findChild(QSlider, 'ContrastSlider')
self.Contrast_Slider.setMinimum(-10)
self.Contrast_Slider.setMaximum(10)
self.Gamma_Slider = self.findChild(QSlider, 'GammaSlider')
self.Gamma_Slider.setMinimum(-10)
self.Gamma_Slider.setMaximum(10)
self.Sharpness_Slider = self.findChild(QSlider, 'SharpnessSlider')
self.Sharpness_Slider.setMinimum(0)
self.Sharpness_Slider.setMaximum(100)
# Sliders values
self.Exposure_Value = self.findChild(QLabel, 'Exposure_Value')
self.White_Balance_Value = self.findChild(QLabel, 'WB_value')
self.Brightness_Value = self.findChild(QLabel, 'Brightness_value')
self.Saturation_Value = self.findChild(QLabel, 'Saturation_value')
self.Contrast_Value = self.findChild(QLabel, 'Contrast_value')
self.Gamma_Value = self.findChild(QLabel, 'Gamma_value')
self.Sharpness_Value = self.findChild(QLabel, 'Sharpness_value')
# Connections
self.Exposure_Slider.valueChanged.connect(self.Exposure_sliding)
def Setup_Exposure(self, value):
self.Exposure_Slider.setValue(value)
self.Exposure_Value.setText(str(value))
def Exposure_sliding(self, value):
self.Exposure_Value.setText(str(value))
self.Exposure_Calibration.emit(value)
if __name__ == "__main__":
App = QApplication(sys.argv)
Root = MainFrame()
Root.show()
sys.exit(App.exec())
Cam_Parameters.ui, MainFrame.ui for the GUI
I admit I don't know why this worked, but changing the connect to a lambda function did the trick.
self.CP.Exposure_Calibration.connect(lambda x: self.Video_Thread.Exposure_update(x))
I have written an application in PyQt5. I am basically displaying a camera feed (in this case my web cam), but the problem is that the frame size keeps on increasing at run time and ultimately goes out of my laptop screen. I'm unable to figure out what the problem is.
Can anyone please explain what I'm doing wrong here?
Below is the code snippet.
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
from collections import deque
from datetime import datetime
import time
import sys
import cv2
import imutils
class CameraWidget(QtWidgets.QWidget):
"""Independent camera feed
Uses threading to grab IP camera frames in the background
#param width - Width of the video frame
#param height - Height of the video frame
#param stream_link - IP/RTSP/Webcam link
#param aspect_ratio - Whether to maintain frame aspect ratio or force into fraame
"""
def __init__(self, width=0, height=0, aspect_ratio=False, parent=None, deque_size=1):
super(CameraWidget, self).__init__(parent)
# Initialize deque used to store frames read from the stream
self.deque = deque(maxlen=deque_size)
self.maintain_aspect_ratio = aspect_ratio
self.camera_stream_link = 0
# Flag to check if camera is valid/working
self.online = False
self.capture = None
self.video_frame = QtWidgets.QLabel()
self.load_network_stream()
# Start background frame grabbing
self.get_frame_thread = Thread(target=self.get_frame, args=())
self.get_frame_thread.daemon = True
self.get_frame_thread.start()
# Periodically set video frame to display
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.set_frame)
self.timer.start(.5)
print('Started camera: {}'.format(self.camera_stream_link))
def load_network_stream(self):
"""Verifies stream link and open new stream if valid"""
def load_network_stream_thread():
if self.verify_network_stream(self.camera_stream_link):
self.capture = cv2.VideoCapture(self.camera_stream_link)
self.online = True
self.load_stream_thread = Thread(target=load_network_stream_thread, args=())
self.load_stream_thread.daemon = True
self.load_stream_thread.start()
def verify_network_stream(self, link):
"""Attempts to receive a frame from given link"""
cap = cv2.VideoCapture(link)
if not cap.isOpened():
return False
cap.release()
return True
def get_frame(self):
# time.sleep(5)
"""Reads frame, resizes, and converts image to pixmap"""
while True:
try:
if self.capture.isOpened() and self.online:
# Read next frame from stream and insert into deque
status, frame = self.capture.read()
if status:
self.deque.append(frame)
else:
self.capture.release()
self.online = False
else:
# Attempt to reconnect
print('attempting to reconnect', self.camera_stream_link)
self.load_network_stream()
self.spin(2)
self.spin(.001)
except AttributeError:
pass
def spin(self, seconds):
"""Pause for set amount of seconds, replaces time.sleep so program doesnt stall"""
time_end = time.time() + seconds
while time.time() < time_end:
QtWidgets.QApplication.processEvents()
def set_frame(self):
"""Sets pixmap image to video frame"""
if not self.online:
self.spin(1)
return
if self.deque and self.online:
# Grab latest frame
frame = self.deque[-1]
# Keep frame aspect ratio
if self.maintain_aspect_ratio:
self.frame = imutils.resize(frame, width=self.screen_width)
# Force resize
else:
self.frame = cv2.resize(frame, (self.screen_width, self.screen_height))
self.frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = self.frame.shape
bytesPerLine = ch * w
# Convert to pixmap and set to video frame
self.img = QtGui.QImage(self.frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
self.pix = QtGui.QPixmap.fromImage(self.img)
self.video_frame.setPixmap(self.pix)
def set_frame_params(self, width, height):
self.screen_width = width
self.screen_height = height
def get_video_frame(self):
self.video_frame.setScaledContents(True)
return self.video_frame
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Middle frame
self.mid_frame = QtWidgets.QFrame()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.camera = CameraWidget()
# Create camera widgets
print('Creating Camera Widgets...')
self.video_frame = self.camera.get_video_frame()
self.mid_layout = QtWidgets.QHBoxLayout()
self.mid_layout.addWidget(self.video_frame)
self.mid_frame.setLayout(self.mid_layout)
self.widget = QtWidgets.QWidget()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.mid_frame)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
def event(self, e):
if e.type() in (QtCore.QEvent.Show, QtCore.QEvent.Resize):
print("resize ", self.mid_frame.width(), self.mid_frame.height())
self.camera.set_frame_params(self.mid_frame.width()-10, self.mid_frame.height()-10)
return QtWidgets.QMainWindow.event(self, e)
if __name__ == '__main__':
# Create main application window
app = QtWidgets.QApplication([])
app.setStyle(QtWidgets.QStyleFactory.create("Cleanlooks"))
w = MainWindow()
w.showMaximized()
sys.exit(app.exec_())
I am trying to retrieve the value of x i.e is a counter from class ShowVideo and to display the counter in a button in ImageViewer class. Video is running perfectly, but I m not getting the logic on how to show counter when video starts running. All the function should work simultaneously, here i as a variable is the counter name declared in inside function startVideo() that comes under showVideo class. I want to update the value on button i.e button_in(class ImageViewer, method InitUI()) as a counter.
'''
import cv2
import numpy as np
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal,QRect,QThread
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap
filenameOpen =0
class ShowVideo(QtCore.QObject):# its only running a videooo
camera = cv2.VideoCapture(filenameOpen)
VideoSignal = QtCore.pyqtSignal(QtGui.QImage)
#############################################
# newValue=QtCore.pyqtSignal(int)
# stopped= pyqtSignal()
#################################################
def __init__(self, parent = None):
# super(ShowVideo, self).__init__(parent)
super().__init__()
#QtCore.pyqtSlot()
def startVideo(self):
run_video = True
# self.counterThread.startVideo()QImage
x=0
while run_video:
# ret, image = self.camera.read()
ret, image = self.camera.read()
height, width, channels = image.shape
frame=image.copy()
frameClone = frame.copy()
# frame = cv2.resize(frame, (1920, 1080))
color_swapped_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width, _ = color_swapped_image.shape
qt_image = QtGui.QImage(color_swapped_image.data,
width,
height,
color_swapped_image.strides[0],
QtGui.QImage.Format_RGB888)
self.VideoSignal.emit(qt_image)
# return x
# print(x)
# x=x+1
class ImageViewer(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super().__init__()
left=0
top=0
width=1920
height=1080
iconName="icon.png"
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left,top,width,height)
self.image = QtGui.QImage()
self.initUI()
self.show()
def initUI(self):
self.setWindowTitle('Emotion Analysis')
button_video = QPushButton(self)
button_file = QPushButton(self)
button_play = QPushButton(self)
self.button_in=QPushButton(self)
button_out=QPushButton(self)
button_total=QPushButton(self)
self.button_stop = QPushButton(self)
self.label_image=QLabel(self)
label_image_blank=QLabel(self)
label_in=QLabel(self)
label_out=QLabel(self)
label_total=QLabel(self)
# button definations>>>>>>>>>>>>>>>>>>>>
button_video.setGeometry((QRect(10,30,90,65)))# syntax(x,y,<>,^)
button_video.setIcon(QtGui.QIcon("securitycamera.png"))
button_video.setIconSize(QtCore.QSize(50,50))
# button.setToolTip("This is Click Me Button")
button_video.setToolTip("<h4>Live Stream<h4>")
button_video.clicked.connect(vid.startVideo)
button_file.setGeometry((QRect(110,30,90,65)))# syntax(x,y,<>,^)
button_file.setIcon(QtGui.QIcon("file.png"))
button_file.setIconSize(QtCore.QSize(50,50))
button_file.setToolTip("<h4>Add new connection<h4>")
button_file.clicked.connect(QtWidgets.qApp.quit) # this line is also working condition, the quit() method is defined above
button_play.setGeometry((QRect(1710,30,90,65)))# syntax(x,y,<>,^)
# button_play.setGeometry((QRect(1710,300,90,65)))# syntax(x,y,<>,^)
button_play.setIcon(QtGui.QIcon("play_red.png"))
button_play.setIconSize(QtCore.QSize(50,50))
button_play.setToolTip("<h4>Play video<h4>")
button_play.clicked.connect(vid.startVideo)
self.button_stop.setGeometry((QRect(1820,30,90,65)))# syntax(x,y,<>,^)
self.button_stop.setIcon(QtGui.QIcon("stop.png"))
self.button_stop.setIconSize(QtCore.QSize(50,50))
self.button_stop.setToolTip("<h4>Stop Video<h4>")
self.button_stop.clicked.connect(QApplication.instance().quit)
#############################################################
self.button_in.setGeometry((QRect(1710,500,90,45)))# syntax(x,y,<>,^)
self.button_in.setText("0")# it should be updated while counter runs
# self.button_in.setText(x)
self.button_in.setFont(QtGui.QFont("Sanserif",20))
# self.counterThread=QThread()
# self.counter=ShowVideo()
# self.counter.moveToThread(self.counterThread)
# self.button_in.clicked.connect(self.startCounting)
# self.vid.newValue.connect(self.button_in.setText)
# self.counterThread.started.connect(self.counter.startVideo)
#####################################################################
button_out.setGeometry((QRect(1710,550,90,45)))# syntax(x,y,<>,^)
button_out.setText("0")
button_out.setFont(QtGui.QFont("Sanserif",20))
button_total.setGeometry((QRect(1710,600,90,45)))# syntax(x,y,<>,^)
button_total.setText("0")
button_total.setFont(QtGui.QFont("Sanserif",20))
# label definations>>>>>>>>>>>>>>>>>>>>
self.label_image.setGeometry((QRect(10,110,1500,900))) # syntax(x,y,<>,^)
self.label_image.setPixmap(QPixmap("black.jpg"))
self.label_image.setScaledContents(True)
label_in.setGeometry((QRect(1600,500,100,50))) # syntax(x,y,<>,^)
label_in.setText("In")
label_in.setFont(QtGui.QFont("Sanserif",20))
label_out.setGeometry((QRect(1600,550,100,50))) # syntax(x,y,<>,^)
label_out.setText("Out")
label_out.setFont(QtGui.QFont("Sanserif",20))
label_total.setGeometry((QRect(1600,600,100,50))) # syntax(x,y,<>,^)
label_total.setText("Total")
label_total.setFont(QtGui.QFont("Sanserif",20))
#QtCore.pyqtSlot(QtGui.QImage)
def setImage(self, image):
self.label_image.setPixmap(QPixmap.fromImage(image))
# self.vid.newValue.connect(self.button_in.setText)
# #QtCore.pyqtSlot(int)
# def startCounting(self,x):
# # # if not self.counterThread.isRunning():
# # # self.counterThread.startVideo()
# # # pass
# self.vid.newValue.connect(self.button_in.setText)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
thread = QtCore.QThread() #thread declareation
thread.start()
vid = ShowVideo() # calling 1st class
vid.moveToThread(thread)
image_viewer = ImageViewer() #calling second class
vid.VideoSignal.connect(image_viewer.setImage)
# vid.VideoSignal.connect(image_viewer.startCounting)
sys.exit(app.exec_()) '''
Probably the easiest is to emit the frame number with the image when emitting ShowVideo.VideoSignal in ShowVideo.startVedio and connecting vid.VideoSignal to image_viewer.setText, i.e.
class ShowVideo(QtCore.QObject):
VideoSignal = QtCore.pyqtSignal(QtGui.QImage, int)
....
#QtCore.pyqtSlot()
def startVideo(self):
....
x += 1
self.VideoSignal.emit(qt_image, x)
....
if __name__ == '__main__':
....
vid.VideoSignal.connect(lambda img, frame: image_viewer.button_in.setText(str(frame)) )
I have a simple project that takes the serial reading from my potentiometer and Arduino. After I get the reading that is converted and is supposed to change the color of a blank window.
For some reason I can't get the window to update and I am REALLY unfamiliar with Tkinter. Thanks for any input.
import Tkinter
import serial
ser = serial.Serial("/dev/ttyACM0", 9600, timeout = 2)
def update():
while 1:
line = ser.readline()
color = "#%02x%02x%02x" %(000, 000, int(line))
return color
root = Tkinter.Tk()
root.geometry("500x500")
root.configure(background = update())
root.mainloop()
I recently did just this to monitor input from a serial device. You need to be able to receive serial data when it is ready and also keep the Tk event-loop running. You can either use a worker thread to read the serial data and post the inputs to the UI thread for display or use the Twisted framework instead. In my case I used twisted to avoid any blocking reads from the serial port and plot the values only a simple line graph using a canvas line.
#!/usr/bin/python
import sys
from Tkinter import *
from ttk import *
from twisted.internet import tksupport, reactor
from twisted.internet.serialport import SerialPort
from twisted.protocols import basic
class DeviceProtocol(basic.LineReceiver):
#delimiter = "\r\n" # default delimiter is crlf
def __init__(self, callback):
self.callback = callback
def lineReceived(self, data):
self.callback.OnDataReceived(data)
class MainWindow(Frame):
def __init__(self, parent, title):
self.data = [(n,1) for n in range(100)]
self.sample = len(self.data)
Frame.__init__(self, parent)
parent.wm_withdraw()
parent.wm_title(title)
self.CreateUI()
self.grid(sticky = (N,S,W,E))
parent.wm_protocol("WM_DELETE_WINDOW", self.onDestroy)
parent.grid_rowconfigure(0, weight = 1)
parent.grid_columnconfigure(0, weight = 1)
parent.wm_deiconify()
def CreateUI(self):
canvas = Canvas(self, background="white")
canvas.create_line((0, 0, 1, 1), tag='Plot', fill='red', smooth=True)
canvas.grid(sticky = (N,S,W,E))
self.canvas = canvas
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def onDestroy(self):
reactor.stop() # required to cleanly shutdown twisted event loop
def Monitor(self, port, baudrate = 9600):
self.serial = SerialPort(DeviceProtocol(self),
port, reactor, baudrate=baudrate)
def OnDataReceived(self, data):
print data
for v in data.split():
self.data.append((self.sample, int(v)))
self.sample += 1
if len(self.data) > 100:
self.data = self.data[-100:]
self.after_idle(self.OnUpdatePlot)
def OnUpdatePlot(self):
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
xS,yS = self.data[0]
xE,yE = self.data[-1]
coords = []
fx = width / (xE - xS)
fy = height / 100 # y values are 0 < y < 100
for (x,y) in self.data:
coords.append(fx * (x - xS))
coords.append(fy * (100 - y))
self.canvas.coords('Plot', *coords)
def main(argv = None):
if argv is None:
argv = sys.argv
if len(argv) != 2:
print "usage: plot_serial <serialport>"
return 1
root = Tk()
tksupport.install(root) # register the Tk event loop with twisted
main = MainWindow(root, "Plot serial data")
main.Monitor(argv[1])
reactor.run() # use twisted instead of root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main())
First, I am new to Python. I am a long-time MatLab user (engineer, not computer scientist) and I am beginning the process of attempting to work Python, NumPy, SciPy, etc. into my workflow. So, please excuse my obvious ignorance of what is a wonderful programming language!
As my first endeavor, I decided to build an application to interact with a sensor that I am developing. The sensor has microsecond resolution (data from 512 high and 512 low energy "pixels" every 500 microseconds), but the I/O will be blocking. Since I will continually poll the device, I know threading will be important to keep the GUI responsive (the GUI will ultimately also integrate serial communication with another device, and have an image processing subroutine that operates on the sensor data). I created a threaded instance of MatPlotLib to plot these "real-time" data from the sensor. Though I've built the module that communicates with the sensor independently and verified I know how to do that in Python, I am starting here simply with a "simulation" of the data by generating 512 random numbers between 8 and 12 for the low energy "pixels", and 512 random numbers between 90 and 110 for the high energy "pixels". That is what is threaded. Working from many examples here, I also learned to use blitting to get a fast enough screen update with MatPlotLib -- BUT, the problem is that unless I use put the threaded process to sleep for 20ms using time.sleep(0.02), the GUI is unresponsive. This can be verified because the interactive X,Y data point feedback from MatPlotLib doesn't work and the 'STOP' button cannot be used to interrupt the process. Anything longer than time.sleep(0.02) makes the GUI operated even smoother, but at the expense of "data rate". Anything slower than time.sleep(0.02) makes the GUI unresponsive. I'm not sure I understand why. I was going to go off and try to use GUIqwt instead, but thought I would ask here before abandoning MatPlotLib since I'm not sure that is even the problem. I am concerned that putting the thread to sleep for 20ms will mean that I miss at least 40 potential lines of data from the sensor array (40 lines * 500us/line = 20ms).
Here is the current code:
import time, random, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QMainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.thread = Worker()
self.create_main_frame()
self.create_status_bar()
self.connect(self.thread, SIGNAL("finished()"), self.update_UI)
self.connect(self.thread, SIGNAL("terminated()"), self.update_UI)
self.connect(self.startButton, SIGNAL("clicked()"), self.start_acquisition)
self.connect(self.stopButton, SIGNAL("clicked()"), self.stop_acquisition)
self.thread.pixel_list.connect(self.update_figure)
def create_main_frame(self):
self.main_frame = QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QPushButton(self.tr("&Start"))
self.stopButton = QPushButton(self.tr("&Stop"))
layout = QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.thread.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.thread.render()
def stop_acquisition(self):
self.thread.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QThread):
pixel_list = pyqtSignal(list, list)
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def render(self):
self.start()
def run(self):
# simulate I/O
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.02)
n -= 1
def main():
app = QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Perhaps my problem isn't even with MatPlotLib or PyQT4, but the way I implemented threading. As I noted, I am new to this and learning. And, I'm not even sure GUIqwt will address any of these issues -- but I do know I've seen many recommendations here to use something faster than MatPlotLib for "real time" plotting in a GUI. Thanks for the help on this!
[edited because QThread is confusing/confused]
There seems to be two ways to use it, either sub-classing it (as your example and the documentation says) or creating a worker object and then moving it to a thread (See this blog post). I then get more confused when you mix signal/slots in. As Avaris says, this change may not be your problem.
I re-worked your Worker class as a a sub-class of QObject (because this is the style I understand).
A problem is that if you do not put a sleep in your fake data system, then you generate all the call backs to the main window in < 1s. The main thread is then essentially blocked as it clears out the signal queue. If you set the delay to your specified delay, (0.0005s), then it cranks through generating the data far faster than it can be displayed, which seems to suggest that this may not be suitable for your problem (being pragmatic, you also can't see that fast, and it seems to do ok at 30 - 40 fps).
import time, random, sys
#from PySide.QtCore import *
#from PySide.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.create_status_bar()
self.startButton.clicked.connect(self.start_acquisition)
self.stopButton.clicked.connect(self.stop_acquisition)
self.worker.pixel_list.connect(self.update_figure)
self.worker.done.connect(self.update_UI)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QtGui.QPushButton(self.tr("&Start"))
self.stopButton = QtGui.QPushButton(self.tr("&Stop"))
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QtGui.QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.worker.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
def stop_acquisition(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QtCore.QObject):
pixel_list = QtCore.pyqtSignal(list, list)
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
#QtCore.pyqtSlot()
def get_data(self):
# simulate I/O
print 'data_start'
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.05)
n -= 1
print 'n: ', n
self.done.emit()