PyQt program crashes QPixmap issue - python

I've a PyQt code which keeps on crashes giving me error message QPixmap::fromImage: QPixmap cannot be created without a QGuiApplication QPixmap: Must construct a QGuiApplication before a QPixmap
It's a fairly simple application in which I read frames from one class named CameraWidget and apply a function def transform_perspective(self, frame, points) to get the transform perspective of that frame. I've divided my screen in two equal haves, the right half section displays the frame as seen by the camera and the left half displays the perspective transformation of it (for which I get the coordinates from another class named Canvas).
There is one more issue: the left half doesn't occupy it's entire region. A lot of portion just appears black. Here is a pic for your reference
It's a fairly long program so below I'm including the class where I believe the problem lies.
class TopView(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TopView, self).__init__(parent)
self.original_frame = CameraWidget('Abc.ts')
# Layouts and frames
self.frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.frame)
layout.setContentsMargins(0,0,0,0)
layout.setSpacing(0)
self.setLayout(layout)
# frame left
self.frame_left = QtWidgets.QFrame()
self.get_frame_thread = Thread(target=self.transform_frame, args=())
self.get_frame_thread.daemon = True
self.get_frame_thread.start()
self.top_view_label = QtWidgets.QLabel()
self.top_view_label.setScaledContents(True)
self.layout_left = QtWidgets.QVBoxLayout()
self.layout_left.addWidget(self.top_view_label)
self.layout_left.setContentsMargins(0,0,0,0)
self.layout_left.setSpacing(0)
self.frame_left.setLayout(self.layout_left)
# frame right
self.frame_right = QtWidgets.QFrame()
self.frame_right.setStyleSheet("background-color: rgb(153, 187, 255)")
self.video_frame_1 = self.original_frame
# Create camera widgets
print('Creating Camera Widgets...')
self.layout_right = QtWidgets.QVBoxLayout()
self.layout_right.addWidget(self.video_frame_1)
self.layout_right.setContentsMargins(5,5,5,5)
self.frame_right.setLayout(self.layout_right)
self.layout_inner = QtWidgets.QHBoxLayout()
self.layout_inner.addWidget(self.frame_left, 50)
self.layout_inner.addWidget(self.frame_right, 50)
self.layout_inner.setContentsMargins(0,0,0,0)
self.layout_inner.setSpacing(0)
self.frame.setLayout(self.layout_inner)
self.setLayout(layout)
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(0)
self.screen_width = int(0.7*sizeObject.width())
self.screen_height = int(0.7*sizeObject.height())
def event(self, e):
if e.type() in (QtCore.QEvent.Show, QtCore.QEvent.Resize):
print('')
return QtWidgets.QWidget.event(self, e)
def transform_frame(self):
while True:
try:
self.top_view_frame = self.transform_perspective(self.original_frame.get_video_frame(), self.original_frame.canvas.mapped_list)
h, w, ch = self.top_view_frame.shape
bytesPerLine = ch * w
self.img = QtGui.QImage(self.top_view_frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
self.pix = QtGui.QPixmap.fromImage(self.img)
if not sip.isdeleted(self.top_view_label):
self.top_view_label.setPixmap(self.pix)
except Exception as e:
print(e)
def transform_perspective(self, frame, points):
points = np.float32(points)
p2 = np.float32([[0, 0], [600, 0], [0, 600], [600, 600]])
self.matrix = cv2.getPerspectiveTransform(points, p2)
frame = cv2.warpPerspective(frame, self.matrix, (400, 600))
return frame

Although the OP does not provide an MRE it is easy to notice that the error is that it is creating a QPixmap in a secondary thread which is prohibited. Instead you should send the QImage to the GUI thread, and in the GUI thread just convert it to QPixmap:
class ImageProcessor(QtCore.QObject):
imageChanged = QtCore.pyqtSignal(QtGui.QImage)
def process(self, video_frame, mapped_list):
thread = Thread(
target=self._execute,
args=(
video_frame,
mapped_list,
),
)
thread.daemon = True
thread.start()
def _execute(self, video_frame, mapped_list):
top_view_frame = self.transform_perspective(video_frame, mapped_list)
qimage = self.convert_np_to_qimage(top_view_frame)
self.imageChanged.emit(qimage.copy())
def transform_perspective(self, frame, points):
points = np.float32(points)
p2 = np.float32([[0, 0], [600, 0], [0, 600], [600, 600]])
matrix = cv2.getPerspectiveTransform(points, p2)
frame = cv2.warpPerspective(frame, matrix, (400, 600))
return frame
def convert_np_to_qimage(self, array):
h, w, ch = array.shape
bytesPerLine = ch * w
img = QtGui.QImage(array.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
return img.copy()
class TopView(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TopView, self).__init__(parent)
self.original_frame = CameraWidget("Abc.ts")
# Layouts and frames
self.frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.frame)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# frame left
self.frame_left = QtWidgets.QFrame()
self.top_view_label = QtWidgets.QLabel()
self.top_view_label.setScaledContents(True)
self.layout_left = QtWidgets.QVBoxLayout(self.frame_left)
self.layout_left.addWidget(self.top_view_label)
self.layout_left.setContentsMargins(0, 0, 0, 0)
self.layout_left.setSpacing(0)
# frame right
self.frame_right = QtWidgets.QFrame()
self.frame_right.setStyleSheet("background-color: rgb(153, 187, 255)")
self.video_frame_1 = self.original_frame
# Create camera widgets
print("Creating Camera Widgets...")
self.layout_right = QtWidgets.QVBoxLayout(self.frame_right)
self.layout_right.addWidget(self.video_frame_1)
self.layout_right.setContentsMargins(5, 5, 5, 5)
self.layout_inner = QtWidgets.QHBoxLayout(self.frame)
self.layout_inner.addWidget(self.frame_left, 50)
self.layout_inner.addWidget(self.frame_right, 50)
self.layout_inner.setContentsMargins(0, 0, 0, 0)
self.layout_inner.setSpacing(0)
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(0)
self.screen_width = int(0.7 * sizeObject.width())
self.screen_height = int(0.7 * sizeObject.height())
self.processor = ImageProcessor()
self.processor.imageChanged.connect(self.handle_imageChanged)
self.launch_processor()
def launch_processor(self):
self.processor.process(
self.original_frame.get_video_frame(),
self.original_frame.canvas.mapped_list,
)
#QtCore.pyqtSlot(QtGui.QImage)
def handle_imageChanged(self, qimage):
qpixmap = QtGui.QPixmap.fromImage(qimage)
self.top_view_label.setPixmap(qpixmap)
QtCore.QTimer.singleShot(0, self.launch_processor)

Related

Convert 3D CBCT dicom to 2D Panaromic View

I want to generate 2D panarmoic view from the series of 3D cbct dental images by python.
I am converting the series of 3D Cbct images to 2D panaromic view by python. The code is working fine but the output is not complete.
import sys
import vtkmodules.all as vtk
from PyQt5 import QtCore, QtWidgets
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
def create_reader(dir):
reader = vtk.vtkDICOMImageReader()
reader.SetDirectoryName(dir)
reader.Update()
return reader
def create_center(reader):
(xMin, xMax, yMin, yMax, zMin, zMax) = reader.GetExecutive().GetWholeExtent(reader.GetOutputInformation(0))
(xSpacing, ySpacing, zSpacing) = reader.GetOutput().GetSpacing()
(x0, y0, z0) = reader.GetOutput().GetOrigin()
center = [x0 + xSpacing * 0.5 * (xMin + xMax),
y0 + ySpacing * 0.5 * (yMin + yMax),
z0 + zSpacing * 0.5 * (zMin + zMax)]
return center
def create_sagittal_slice_matrix(center):
#Create Sagittal Slice Matrix
sagittal = vtk.vtkMatrix4x4()
sagittal.DeepCopy((0, 0, -1, center[0],
1, 0, 0, center[1],
0, -1, 0, center[2],
0, 0, 0, 1))
return sagittal
def create_resliced_image(reader, sagittal, frame):
# Reslice image
widget = QVTKRenderWindowInteractor(frame)
slice = vtk.vtkImageReslice()
slice.SetInputConnection(reader.GetOutputPort())
slice.SetOutputDimensionality(2)
slice.SetResliceAxes(sagittal)
slice.SetInterpolationModeToLinear()
return widget, slice
def create_display_image_actor(slice):
# Display the image
actor = vtk.vtkImageActor()
actor.GetMapper().SetInputConnection(slice.GetOutputPort())
# renderer = vtk.vtkRenderer()
return actor
def adjust_renderer_settings(renderer, widget, actor):
# Remove Renderer And Reset
renderer.RemoveAllViewProps()
renderer.ResetCamera()
widget.GetRenderWindow().Render()
renderer.AddActor(actor)
widget.GetRenderWindow().AddRenderer(renderer)
return widget
def setup_interaction(widget):
# Set up the interaction
slice_interactorStyle = vtk.vtkInteractorStyleImage()
slice_interactor = widget.GetRenderWindow().GetInteractor()
slice_interactor.SetInteractorStyle(slice_interactorStyle)
widget.GetRenderWindow().SetInteractor(slice_interactor)
widget.GetRenderWindow().Render()
return slice_interactor
# Start interaction
# slice_interactor.Start()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent = None):
QtWidgets.QMainWindow.__init__(self, parent)
self.frame = QtWidgets.QFrame()
self.vl = QtWidgets.QVBoxLayout()
reader = create_reader(r"C:\work.ateeb\2.16.840.114421.10241.9572018034.9603554034\DECOMPRESSED\One")
center = create_center(reader)
sagittal = create_sagittal_slice_matrix(center)
self.vtkWidget, slice = create_resliced_image(reader, sagittal, self.frame)
self.vl.addWidget(self.vtkWidget)
# create renderer
renderer = vtk.vtkRenderer()
# create actor
actor = create_display_image_actor(slice)
self.vtkWidget = adjust_renderer_settings(renderer, self.vtkWidget, actor)
slice_interactor = setup_interaction(self.vtkWidget)
self.show()
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
slice_interactor.Initialize()
slice_interactor.Start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
I was expecting the output like this
enter image description here
But the output the code is generating is:
enter image description here

Porting from PyOpenGL GLUT to PyQT: Rendering image in QOpenGLWidget fails

I am writing an application that shows the user's webcam video feed in a PyQT5 window. Using a QLabel and updating the label's pixmap for every frame is to slow due to the target device's performance.
I therefore tried to gain some speed by using OpenGL to display the video frames as 2D texture on a rectangle. I found this earlier question by user Arijit that contains a working example using GLUT. However, I fail to port the code from using GLUT to a QOpenGLWidget.
For the init and reshape functions in GLUT it is clear that they correspond to the initializeGL and resizeGL functions in QT. But where in the QOpenGLWidget lifecycle do the display and idle functions belong? Executing them inside paintGL shows no effect. The current code does not crash, but the widget stays black.
What is the right way to do this?
#!/usr/bin/env python
import sys, time
import cv2
import numpy as np
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class GLWidget(QOpenGLWidget):
def __init__(self, parent=None, width=1280, height=720):
self.parent = parent
self.width = width
self.height = height
QOpenGLWidget.__init__(self, parent)
def sizeHint(self):
return QSize(self.width,self.height)
def setImage(self,image):
self.image = np.flipud(image).tobytes()
self._idle()
def initializeGL(self):
version_profile = QOpenGLVersionProfile()
version_profile.setVersion(2,0)
self.gl = self.context().versionFunctions(version_profile)
self.gl.glClearColor(0.0, 0.0, 0.0, 1.0)
self.setImage(np.zeros((self.width, self.height,3)))
def _idle(self):
self.gl.glTexImage2D(self.gl.GL_TEXTURE_2D,
0,
self.gl.GL_RGB,
self.width,self.height,
0,
self.gl.GL_RGB,
self.gl.GL_UNSIGNED_BYTE,
self.image)
self.update()
def _display(self):
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
self.gl.glEnable(self.gl.GL_TEXTURE_2D)
self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MIN_FILTER, self.gl.GL_NEAREST)
self.gl.glMatrixMode(self.gl.GL_PROJECTION)
self.gl.glLoadIdentity()
self.gl.glOrtho(0, self.width, 0, self.height,-1,1)
self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
self.gl.glLoadIdentity()
self.gl.glBegin(self.gl.GL_QUADS)
self.gl.glTexCoord2f(0.0, 0.0)
self.gl.glVertex2f(0.0, 0.0)
self.gl.glTexCoord2f(1.0, 0.0)
self.gl.glVertex2f(self.width, 0.0)
self.gl.glTexCoord2f(1.0, 1.0)
self.gl.glVertex2f(self.width, self.height)
self.gl.glTexCoord2f(0.0, 1.0)
self.gl.glVertex2f(0.0, self.height)
self.gl.glEnd()
self.gl.glFlush()
def resizeGL(self, w, h):
if h == 0:
h = 1
self.gl.glViewport(0, 0, w, h)
self.gl.glMatrixMode(self.gl.GL_PROJECTION)
self.gl.glLoadIdentity()
if w <= h:
self.gl.glOrtho(-1, 1, -1*h/w, h/w, -1, 1)
else:
self.gl.glOrtho(-1*w/h, w/h, -1, 1, -1, 1)
self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
self.gl.glLoadIdentity()
def paintGL(self):
self._display()
class VideoThread(QThread):
change_image_signal = pyqtSignal(np.ndarray)
def __init__(self):
super().__init__()
self._run_flag = True
def run(self):
capture = cv2.VideoCapture(0)
capture.set(cv2.CAP_PROP_BUFFERSIZE,3)
while self._run_flag:
_, frame = capture.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.change_image_signal.emit(frame)
time.sleep(0.4)
capture.release()
def stop(self):
self._run_flag = False
self.wait()
class MainUI(QWidget):
def __init__(self):
QWidget.__init__(self)
self.video_size = QSize(394,292)
self.setup_ui()
self.setup_camera()
def setup_ui(self):
self.video_widget = GLWidget(self,self.video_size.width(),self.video_size.height())
self.main_layout = QGridLayout()
self.main_layout.addWidget(self.video_widget,0,0)
self.setLayout(self.main_layout)
def closeEvent(self, event):
self.thread.stop()
event.accept()
def setup_camera(self):
self.thread = VideoThread()
self.thread.change_image_signal.connect(self.display_video_stream)
self.thread.start()
#pyqtSlot(np.ndarray)
def display_video_stream(self,image):
self.video_widget.setImage(image)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainUI()
win.show()
sys.exit(app.exec())

How to crop QPixmap that is set on a Scene with a rotated QGraphicsRectItem?

I cannot figure out how to crop a QPixmap that is set on a scene with a rotated QGraphicsRectItem also placed in the same scene.
Here is the code for my QGraphicsScene and QPixmap.
class crop_pattern(QGraphicsView):
img_is_cropped = QtCore.Signal(object)
def __init__(self, path, scale):
super().__init__()
# Connect graphics scene with graphics view
self.setFixedSize(500, 500)
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.roi_scale = scale
# Display image
self.set_image(path)
def set_image(self, path):
pixmap = QtGui.QPixmap(path)
if pixmap:
pixmap = pixmap.scaledToHeight(self.roi_scale * pixmap.height())
self.scene.clear()
self.scene.addPixmap(pixmap)
self.setAlignment(QtCore.Qt.AlignCenter)
def wheelEvent(self, event):
zoomInFactor = 1.05
zoomOutFactor = 1 / zoomInFactor
oldPos = self.mapToScene(event.pos())
if event.angleDelta().y() > 0:
zoomFactor = zoomInFactor
else:
zoomFactor = zoomOutFactor
self.scale(zoomFactor, zoomFactor)
newPos = self.mapToScene(event.pos())
delta = newPos - oldPos
self.translate(delta.x(), delta.y())
(Credits to QGraphicsView Zooming in and out under mouse position using mouse wheel for the wheelEvent function)
Here is the QGraphicsItem that is generated when the user clicks a certain button.
QtCore.Slot(bool)
def create_shape(self):
sender = self.sender()
if sender.text().lower() == "circle":
self.shape = ellipse_shape(0, 0, 100, 100)
elif sender.text().lower() == "rectangle":
self.shape = rect_shape(0, 0, 100, 100)
self.shape.setZValue(1)
self.shape.setTransformOriginPoint(50, 50)
self.crop_pattern.scene.addItem(self.shape) # added item to the same scene, which is crop_pattern.
Here is the GUI as the question suggested. (QGraphicsRectItem has been resized)
How can I crop the pixels inside the rectangle? Thanks!
One possible solution is to create a hidden QGraphicsView and use the render() method to save the image section. The objective of the hidden QGraphicsView is not to modify the existing view since the image must be rotated in addition to not being affected by the scaling.
from PyQt5 import QtCore, QtGui, QtWidgets
def crop_rect(rect_item, scene):
is_visible = rect_item.isVisible()
rect_item.hide()
hide_view = QtWidgets.QGraphicsView(scene)
hide_view.rotate(-rect_item.rotation())
polygon = rect_item.mapToScene(rect_item.rect())
pixmap = QtGui.QPixmap(rect_item.rect().size().toSize())
pixmap.fill(QtCore.Qt.transparent)
source_rect = hide_view.mapFromScene(polygon).boundingRect()
painter = QtGui.QPainter(pixmap)
hide_view.render(
painter,
target=QtCore.QRectF(pixmap.rect()),
source=source_rect,
)
painter.end()
rect_item.setVisible(is_visible)
return pixmap
def main():
app = QtWidgets.QApplication([])
scene = QtWidgets.QGraphicsScene()
view = QtWidgets.QGraphicsView(alignment=QtCore.Qt.AlignCenter)
view.setScene(scene)
# emulate wheel
view.scale(0.8, 0.8)
# create pixmap
pixmap = QtGui.QPixmap(500, 500)
pixmap.fill(QtGui.QColor("green"))
painter = QtGui.QPainter(pixmap)
painter.setBrush(QtGui.QColor("salmon"))
painter.drawEllipse(pixmap.rect().adjusted(100, 90, -80, -100))
painter.end()
pixmap_item = scene.addPixmap(pixmap)
rect = QtCore.QRectF(0, 0, 200, 300)
rect_item = scene.addRect(rect)
rect_item.setPen(QtGui.QPen(QtGui.QColor("red"), 4))
rect_item.setPos(100, 100)
rect_item.setTransformOriginPoint(50, 50)
rect_item.setRotation(10)
view.resize(640, 480)
view.show()
qpixmap = crop_rect(rect_item, scene)
label = QtWidgets.QLabel()
label.setPixmap(qpixmap)
label.show()
app.exec_()
if __name__ == "__main__":
main()
Input:
Output:

RuntimeError: wrapped C/C++ object of type QLabel has been deleted

I am trying to run the following piece of code but it gives me an error RuntimeError: wrapped C/C++ object of type QLabel has been deleted. I'm unable to figure out why is it happening.
I searched other answers but they all said it has something to do with setCentralWidget. But I'm not using it. So, why's the error occuring in my code?
from PyQt4 import QtGui, QtCore
from threading import Thread
from collections import deque
from datetime import datetime
import time
import sys
import cv2
import imutils
import os
class CameraWidget(QtGui.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, height, stream_link=0, btn_text=None, 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.screen_width = width - 8
self.screen_height = height - 8
self.maintain_aspect_ratio = aspect_ratio
self.camera_stream_link = stream_link
# Flag to check if camera is valid/working
self.online = False
self.capture = None
self.video_display_frame = QtGui.QFrame()
self.video_layout = QtGui.QVBoxLayout()
self.video_btn = QtGui.QPushButton(btn_text)
self.video_btn.setStyleSheet("background-color: rgb(128, 159, 255)")
self.video_btn.clicked.connect(self.message)
self.video_frame = QtGui.QLabel()
self.video_layout.addWidget(self.video_btn)
self.video_layout.addWidget(self.video_frame)
self.video_layout.setContentsMargins(0,0,0,0)
self.video_layout.setSpacing(0)
self.video_display_frame.setLayout(self.video_layout)
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):
"""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:
QtGui.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))
# Convert to pixmap and set to video frame
self.img = QtGui.QImage(self.frame, self.frame.shape[1], self.frame.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
self.pix = QtGui.QPixmap.fromImage(self.img)
self.video_frame.setPixmap(self.pix) ### error comes in this line.
def get_video_frame(self):
return self.video_frame
def get_video_display_frame(self):
return self.video_display_frame
def message(self):
self.zone_config = ZoneConfig(self.camera_stream_link)
self.zone_config.show()
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.setWindowTitle("VIDS")
titleBar_logo = os.path.join(dir, 'logos/logo.png')
self.setWindowIcon(QtGui.QIcon(titleBar_logo))
self.showMaximized()
self.screen_width = self.width()
self.screen_height = self.height()
# Layouts & frames
self.layout = QtGui.QVBoxLayout()
self.top_frame = QtGui.QFrame()
self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.mid_frame = QtGui.QFrame()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.btm_frame = QtGui.QFrame()
self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.layout.addWidget(self.top_frame, 2)
self.layout.addWidget(self.mid_frame, 20)
self.layout.addWidget(self.btm_frame, 1)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
# Top frame
label = QtGui.QLabel('VIDS Dashboard')
label.setFont(QtGui.QFont('Arial', 20))
label.setAlignment(QtCore.Qt.AlignCenter)
self.top_layout = QtGui.QHBoxLayout()
self.top_layout.addWidget(label)
self.top_frame.setLayout(self.top_layout)
# Middle frame
self.mid_layout = QtGui.QStackedLayout()
# Create camera widgets
print('Creating Camera Widgets...')
self.one = CameraWidget(int(self.screen_width), int(self.screen_height), camera1, 'Camera 1')
self.two = CameraWidget(int(self.screen_width), int(self.screen_height), camera2, 'Camera 2')
cam_widget = Cam2(self.one, self.two, self)
self.mid_layout.addWidget(cam_widget)
self.mid_layout.setCurrentWidget(cam_widget)
self.mid_frame.setLayout(self.mid_layout)
# Bottom frame
btn1 = QtGui.QPushButton('1 Cam')
btn1.clicked.connect(self.button1)
btn2 = QtGui.QPushButton('2 Cams')
btn2.clicked.connect(self.button2)
self.btm_layout = QtGui.QHBoxLayout()
self.btm_layout.addStretch()
self.btm_layout.addWidget(btn1)
self.btm_layout.addWidget(btn2)
self.btm_frame.setLayout(self.btm_layout)
self.show()
def button1(self):
cam1_widget = Cam1(self.one, self)
self.mid_layout.addWidget(cam1_widget)
self.mid_layout.setCurrentWidget(cam1_widget)
def button2(self):
cam2_widget = Cam2(self.one, self.two, self)
self.mid_layout.addWidget(cam2_widget)
self.mid_layout.setCurrentWidget(cam2_widget)
class Cam1(QtGui.QWidget):
def __init__(self, one, parent=None):
super(Cam1, self).__init__(parent)
# Add widgets to layout
print('Adding widgets to layout...')
layout = QtGui.QGridLayout()
layout.addWidget(one.get_video_display_frame(),0,0,1,1)
self.setLayout(layout)
class Cam2(QtGui.QWidget):
def __init__(self, one, two, parent=None):
super(Cam2, self).__init__(parent)
# Add widgets to layout
print('Adding widgets to layout...')
layout = QtGui.QGridLayout()
layout.addWidget(one.get_video_display_frame(),0,0,1,1)
layout.addWidget(two.get_video_display_frame(),0,1,1,1)
self.setLayout(layout)
class ZoneConfig(QtGui.QWidget):
def __init__(self, camera, parent=None):
super(ZoneConfig, self).__init__(parent)
self.initUI(camera)
def initUI(self, camera):
self.setWindowTitle("VIDS")
titleBar_logo = os.path.join(dir, 'logos/logo.png')
self.setWindowIcon(QtGui.QIcon(titleBar_logo))
self.showMaximized()
self.screen_width = self.width()
self.screen_height = self.height()
self.camera = camera
# Layouts & frames
self.layout = QtGui.QVBoxLayout()
self.top_frame = QtGui.QFrame()
self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.mid_frame = QtGui.QFrame()
# self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.btm_frame = QtGui.QFrame()
self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.layout.addWidget(self.top_frame, 2)
self.layout.addWidget(self.mid_frame, 20)
self.layout.addWidget(self.btm_frame, 1)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
# Top frame
self.label = QtGui.QLabel('VIDS - Zone Configuration')
self.label.setFont(QtGui.QFont('Arial', 20))
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.top_layout = QtGui.QHBoxLayout()
self.top_layout.addWidget(self.label)
self.top_frame.setLayout(self.top_layout)
# Middle frame
self.mid_layout = QtGui.QVBoxLayout()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
# Create camera widgets
print('Creating Camera Widgets...')
self.cam = CameraWidget(self.screen_width, self.screen_height, self.camera)
self.mid_layout = QtGui.QVBoxLayout()
self.mid_layout.addWidget(self.cam.get_video_frame())
self.mid_frame.setLayout(self.mid_layout)
self.mid_layout.setContentsMargins(0,0,0,0)
self.mid_layout.setSpacing(0)
self.mid_frame.setLayout(self.mid_layout)
# Bottom frame
btn = QtGui.QPushButton('Dashboard')
btn.clicked.connect(self.goMainWindow)
self.btm_layout = QtGui.QHBoxLayout()
self.btm_layout.addStretch()
self.btm_layout.addWidget(btn)
self.btm_frame.setLayout(self.btm_layout)
def goMainWindow(self):
self.close()
dir = os.path.dirname(__file__)
window = None
camera1 = 'streams/Fog.avi'
camera2 = 'streams/Overspeed.avi'
if __name__ == '__main__':
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("plastique")) # applies to entire window
window = MainWindow()
# window.show()
sys.exit(app.exec_())
I am using python 3, pyqt 4 and windows 10.
It seems that in the handling of threads it does not synchronize the elimination of the objects correctly, so a possible solution is to use sip to verify if the object was eliminated or not:
from PyQt4 import QtGui, QtCore
import sip
# ...
self.pix = QtGui.QPixmap.fromImage(self.img)
if not sip.isdeleted(self.video_frame):
self.video_frame.setPixmap(self.pix)

QGraphicsAnchorLayout not anchoring in QGraphicsScene

I am currently having issues understanding the behavior of QGraphicsAnchorLayout within a QGraphicsScene. I create 4 boxes and anchor the corners of each, but no anchors appear to be applied properly, or at least the way I believed they would be.
The yellow box should be on the top left of the QGraphicsScene at all times, even when the GraphicsView is expanded. The blue box is anchored to appear adjacent to the yellow box on the right, with its top the coinciding with the top of the QGraphicsScene/viewport.
The top left corner of the green box is anchored to the bottom right of the blue box and likewise for the red box to the green box. But this is what I am getting:
I expect the yellow box to be at the top of the graphics scene/viewport at all times. And I would like for it always to remain visible even when scrolled right, but I believe that probably would be a separate issue. However, when I expand the window vertically, all the boxes are centered, including the yellow box which I expected to remain at top.
The blue, green and red boxes seem to bear no resemblance to the anchors I applied.
Following is the code I used to generate this. How do these anchors work and what can I do to correct this?
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from debug_utils import *
from PyQt5.QtWidgets import QGraphicsAnchorLayout, QGraphicsWidget, QGraphicsLayoutItem
def qp(p):
return "({}, {})".format(p.x(), p.y())
class box(QtWidgets.QGraphicsWidget):
pressed = QtCore.pyqtSignal()
def __init__(self, rect, color, parent=None):
super(box, self).__init__(parent)
self.raw_rect = rect
self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
self.color = color
def boundingRect(self):
pen_adj = 0
return self.rect.normalized().adjusted(-pen_adj, -pen_adj, pen_adj, pen_adj)
def paint(self, painter, option, widget):
r = self.boundingRect()
brush = QtGui.QBrush()
brush.setColor(QtGui.QColor(self.color))
brush.setStyle(Qt.SolidPattern)
#rect = QtCore.QRect(0, 0, painter.device().width(), painter.device().height())
painter.fillRect(self.boundingRect(), brush)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(Qt.darkGray)
painter.drawRect(self.boundingRect())
#painter.drawRect(0, 0, max_time*char_spacing, self.bar_height)
def mousePressEvent(self, ev):
self.pressed.emit()
self.update()
def mouseReleaseEvent(self, ev):
self.update()
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self.numbers = []
self.setMouseTracking(True)
l = QGraphicsAnchorLayout()
l.setSpacing(0)
w = QGraphicsWidget()
#painter = QtGui.QPainter(self)
w.setPos(0, 0)
w.setLayout(l)
scene.addItem(w)
self.main_widget = w
self.main_layout = l
self.makeBoxs()
def makeBoxs(self):
rect = [0, 0, 600, 250]
blue_box = box(rect, QtGui.QColor(0, 0, 255, 128))
green_box = box(rect, QtGui.QColor(0, 255, 0, 128))
red_box = box([0, 0, 200, 50], QtGui.QColor(255, 0, 0, 128))
yellow_box_left = box([0, 0, 75, 600], QtGui.QColor(255, 255, 0, 128))
#self.scene().setSceneRect(blue_box.rect)
#self.scene().setSceneRect(bar_green.rect)
# Adding anchors adds the item to the layout which is part of the scene
self.main_layout.addCornerAnchors(yellow_box_left, Qt.TopLeftCorner, self.main_layout, Qt.TopLeftCorner)
self.main_layout.addCornerAnchors(blue_box, Qt.TopLeftCorner, yellow_box_left, Qt.TopRightCorner)
self.main_layout.addCornerAnchors(green_box, Qt.TopLeftCorner, blue_box, Qt.BottomRightCorner)
self.main_layout.addCornerAnchors(red_box, Qt.TopLeftCorner, green_box, Qt.BottomRightCorner)
#self.main_layout.addAnchor(bar_green, Qt.AnchorTop, blue_box, Qt.AnchorBottom)
#self.main_layout.addAnchor(bar_green, Qt.AnchorLeft, blue_box, Qt.AnchorRight)
def printStatus(self, pos):
msg = "Viewport Position: " + str(qp(pos))
v = self.mapToScene(pos)
v = QtCore.QPoint(v.x(), v.y())
msg = msg + ", Mapped to Scene: " + str(qp(v))
v = self.mapToScene(self.viewport().rect()).boundingRect()
msg = msg + ", viewport Mapped to Scene: " + str(qp(v))
v2 = self.mapToScene(QtCore.QPoint(0, 0))
msg = msg + ", (0, 0) to scene: " + qp(v2)
self.parent().statusBar().showMessage(msg)
def mouseMoveEvent(self, event):
pos = event.pos()
self.printStatus(pos)
super(GraphicsView, self).mouseMoveEvent(event)
def resizeEvent(self, event):
self.printStatus(QtGui.QCursor().pos())
h = self.mapToScene(self.viewport().rect()).boundingRect().height()
r = self.sceneRect()
r.setHeight(h)
height = self.viewport().height()
for item in self.items():
item_height = item.boundingRect().height()
super(GraphicsView, self).resizeEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
gv = GraphicsView()
self.setCentralWidget(gv)
self.setGeometry(475, 250, 600, 480)
scene = self.scene = gv.scene()
sb = self.statusBar()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
EDIT: Adding expected output
Based on how the anchors are defined, I expect the output to look something like the following. Since I can't yet actually create what I need, I have created this in PowerPoint. But, of course, in addition to getting this to work, I'm hoping to understand how to use anchors in a more general sense as well.
EDIT 2:
Thank you again for updating. It's not quite what I was expecting, and there is a strange artifact when I scroll. Just to clarify,
I expect the yellow widget to be visible at all times, top left of viewport with the highest z-order. I think you provided that.
All the other widgets should scroll, maintaining their relative anchors. I removed the blue box's anchor to the yellow box, but then all boxes align left.
In your current implementation, the non-yellow boxes do not scroll, but when I resize or move the scroll bar right and back left I see this artifact:
You have 2 errors:
Your Box class is poorly built, instead of override boundingRect it only sets the size since the position will be handled by the layout.
The position of a layout is always relative to the widget where it is set. And in your case you want the top-left of the main_layout to match the top-left of the viewport so you must modify the top-left of the main_widget to match.
Considering the above, the solution is:
from PyQt5 import QtCore, QtGui, QtWidgets
def qp(p):
return "({}, {})".format(p.x(), p.y())
class Box(QtWidgets.QGraphicsWidget):
pressed = QtCore.pyqtSignal()
def __init__(self, size, color, parent=None):
super(Box, self).__init__(parent)
self.setMinimumSize(size)
self.setMaximumSize(size)
self.color = color
def paint(self, painter, option, widget):
brush = QtGui.QBrush()
brush.setColor(QtGui.QColor(self.color))
brush.setStyle(QtCore.Qt.SolidPattern)
painter.fillRect(self.boundingRect(), brush)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.darkGray)
painter.drawRect(self.boundingRect())
def mousePressEvent(self, event):
self.pressed.emit()
super().mousePressEvent(event)
class GraphicsView(QtWidgets.QGraphicsView):
messageChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setMouseTracking(True)
self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
l = QtWidgets.QGraphicsAnchorLayout()
l.setSpacing(0)
w = QtWidgets.QGraphicsWidget()
self.scene().sceneRectChanged.connect(self.update_widget)
self.horizontalScrollBar().valueChanged.connect(self.update_widget)
self.verticalScrollBar().valueChanged.connect(self.update_widget)
w.setLayout(l)
scene.addItem(w)
self.main_widget = w
self.main_layout = l
self.makeBoxs()
def makeBoxs(self):
blue_box = Box(QtCore.QSizeF(300, 125), QtGui.QColor(0, 0, 255, 128))
green_box = Box(QtCore.QSizeF(300, 125), QtGui.QColor(0, 255, 0, 128))
red_box = Box(QtCore.QSizeF(100, 25), QtGui.QColor(255, 0, 0, 128))
yellow_box = Box(QtCore.QSizeF(37.5, 300), QtGui.QColor(255, 255, 0, 128))
# yellow_box_left top-left
self.main_layout.addCornerAnchors(
yellow_box,
QtCore.Qt.TopLeftCorner,
self.main_layout,
QtCore.Qt.TopLeftCorner,
)
self.main_layout.addCornerAnchors(
blue_box, QtCore.Qt.TopLeftCorner, yellow_box, QtCore.Qt.TopRightCorner
)
self.main_layout.addCornerAnchors(
green_box, QtCore.Qt.TopLeftCorner, blue_box, QtCore.Qt.BottomRightCorner
)
self.main_layout.addCornerAnchors(
red_box, QtCore.Qt.TopLeftCorner, green_box, QtCore.Qt.BottomRightCorner
)
# self.setSceneRect(self.scene().itemsBoundingRect())
def update_widget(self):
vp = self.viewport().mapFromParent(QtCore.QPoint())
tl = self.mapToScene(vp)
geo = self.main_widget.geometry()
geo.setTopLeft(tl)
self.main_widget.setGeometry(geo)
def resizeEvent(self, event):
self.update_widget()
super().resizeEvent(event)
def mouseMoveEvent(self, event):
pos = event.pos()
self.printStatus(pos)
super(GraphicsView, self).mouseMoveEvent(event)
def printStatus(self, pos):
msg = "Viewport Position: " + str(qp(pos))
v = self.mapToScene(pos)
v = QtCore.QPoint(v.x(), v.y())
msg = msg + ", Mapped to Scene: " + str(qp(v))
v = self.mapToScene(self.viewport().rect()).boundingRect()
msg = msg + ", viewport Mapped to Scene: " + str(qp(v))
v2 = self.mapToScene(QtCore.QPoint(0, 0))
msg = msg + ", (0, 0) to scene: " + qp(v2)
self.messageChanged.emit(msg)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
gv = GraphicsView()
self.setCentralWidget(gv)
self.setGeometry(475, 250, 600, 480)
gv.messageChanged.connect(self.statusBar().showMessage)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Categories