guidoasc4064 | 2020-05-16 12:43:57 UTC | #1
Hi all! I'm very new to PyQt5, so bare with me if the question has an obvious answer. Some context: I'm trying to develop a GUI that will let users open an image (using opencv; the image then goes onto a QLabel) that features a person. The GUI then allows the user to digitize (and save) the positions of the person's joints (ankles, knees, elbows, etc). Ideally, the GUI would function like this. First, the user opens an image by pressing CTRL+o. Then, the image gets displayed, and the user has to select one of the pushButtons with the joint names (these appear below the QLabel with the image), each corresponding to a different joint. When a joint button is selected, if the user clicks on a point on the image the coordinates of that point are saved in a dictionary under the name of the joint that was selected, and a dot should appear on that pixel, to indicate that that's the pixel where the joint was digitised (so the user can quickly see if he/she made a mistake). The part I got stuck on is this: when the user clicks on a joint, I want a green (if it was a left mouse click, representing a joint that is visible) or yellow (if it was a right mouse click, representing a joint that was occluded) dot to appear where the user clicked. I followed the code given in the brilliantly explained tutorial at https://www.pythonguis.com/courses/custom-widgets/bitmap-graphics/, but it did not work for me: it doesn't give any errors, and the draw_something method does get called whenever the user clicks (I checked this using print statements), but no dots appear. My hunch is that the dots are indeed being drawn, only they are being drawn UNDER the image. Any thoughts? Below is my code: I apologise if it's a bit long and articulated, I hope you can follow my line of thought. Thanks!
import cv2
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QImage, QPixmap, QFont, QColor
from PyQt5.QtWidgets import QDialog, QApplication, QFileDialog, QMainWindow
from PyQt5.uic import loadUi
import json as serializer
import csv
# track movements of the mouse to display a (x,y) label next to it
class MouseTracker(QtCore.QObject):
positionChanged = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.setMouseTracking(True)
self.widget.installEventFilter(self)
@property
def widget(self):
return self._widget
def eventFilter(self, o, e):
if o is self.widget and e.type() == QtCore.QEvent.MouseMove:
self.positionChanged.emit(e.pos())
return super().eventFilter(o, e)
# main window
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
loadUi('load_and_save2.ui',self)
self.image=None
self.actionOpen.triggered.connect(self.loadTriggered)
self.actionExit.triggered.connect(self.close)
self.imgLabel.resize(1920, 844) # specific resolution (DO NOT CHANGE)
self.joint_selected = None
self.buttonLeft_ankle.clicked.connect(lambda: self.jointSelected('Left_ankle'))
self.buttonRight_ankle.clicked.connect(lambda: self.jointSelected('Right_ankle'))
self.buttonLeft_knee.clicked.connect(lambda: self.jointSelected('Left_knee'))
self.buttonRight_knee.clicked.connect(lambda: self.jointSelected('Right_knee'))
self.buttonLeft_hip.clicked.connect(lambda: self.jointSelected('Left_hip'))
self.buttonRight_hip.clicked.connect(lambda: self.jointSelected('Right_hip'))
self.buttonLeft_shoulder.clicked.connect(lambda: self.jointSelected('Left_shoulder'))
self.buttonRight_shoulder.clicked.connect(lambda: self.jointSelected('Right_shoulder'))
self.buttonLeft_elbow.clicked.connect(lambda: self.jointSelected('Left_elbow'))
self.buttonRight_elbow.clicked.connect(lambda: self.jointSelected('Right_elbow'))
self.buttonLeft_wrist.clicked.connect(lambda: self.jointSelected('Left_wrist'))
self.buttonRight_wrist.clicked.connect(lambda: self.jointSelected('Right_wrist'))
self.joints = {}
tracker = MouseTracker(self.imgLabel)
tracker.positionChanged.connect(self.on_positionChanged)
self.label_position = QtWidgets.QLabel(
self.imgLabel, alignment=QtCore.Qt.AlignCenter
)
def jointSelected(self, joint):
self.joint_selected = joint
# detects if left or right mouse was clicked
def mousePressEvent(self, event):
# on left mouse click, it prints to screen the coordinates and 0
if event.button() == QtCore.Qt.LeftButton:
self.lastLeftPoint = self.imgLabel.mapFromParent(event.pos())
if self.joint_selected is not None:
self.joints[self.joint_selected] = [self.lastLeftPoint.x(), self.lastLeftPoint.y(), 1]
# turn green the background of corresponding joint button
self.changeButtonBackground(joint_selected=self.joint_selected,
visible=1)
self.draw_something(self.lastLeftPoint.x(),
self.lastLeftPoint.y(),
visible=1)
# on right mouse click, it prints to screen the coordinates and 1
elif event.button() == QtCore.Qt.RightButton:
self.lastRightPoint = self.imgLabel.mapFromParent(event.pos())
if self.joint_selected is not None:
self.joints[self.joint_selected] = [self.lastLeftPoint.x(), self.lastLeftPoint.y(), 0]
# turn yellow the background of corresponding joint button
self.changeButtonBackground(joint_selected=self.joint_selected,
visible=0)
self.draw_something(self.lastLeftPoint.x(),
self.lastLeftPoint.y(),
visible=0)
def changeButtonBackground(self, joint_selected, visible):
button_name = 'button' + joint_selected
if visible:
getattr(self, button_name).setStyleSheet("background-color:green")
elif not visible:
getattr(self, button_name).setStyleSheet("background-color:yellow")
def draw_something(self, x, y, visible):
print(f'About to draw a dot (visible={visible})')
painter = QtGui.QPainter(self.imgLabel.pixmap())
pen = QtGui.QPen()
pen.setWidth(40)
if visible:
pen.setColor(QtGui.QColor('green'))
else:
pen.setColor(QtGui.QColor('yellow'))
painter.setPen(pen)
painter.drawPoint(x, y)
painter.end()
print('Dot drawn')
# reposition the (x,y) label as the mouse moves
# make the font of the label bold and green
@QtCore.pyqtSlot(QtCore.QPoint)
def on_positionChanged(self, pos):
delta = QtCore.QPoint(30, -15)
self.label_position.show()
self.label_position.move(pos + delta)
self.label_position.setText(f'{self.joint_selected}')
myFont=QtGui.QFont('Arial', 12)
myFont.setBold(True)
self.label_position.setFont(myFont)
self.label_position.adjustSize()
color = QtGui.QColor(0,128,0)
values = "{r}, {g}, {b}".format(r = color.red(),
g = color.green(),
b = color.blue()
)
self.label_position.setStyleSheet("QLabel {color:rgb("+values+")}")
#self.label_position.setBackground(QtGui.QColor('sea green'))
# user wants to open an image -> open it from a user-selected folder
def loadTriggered(self):
fname,filter = QFileDialog.getOpenFileName(self, 'Open File','C:\\',"Image Files (*.jpg)")
if fname:
self.loadImage(fname)
else:
print('Invalid Image')
# load the selected image, using opencv
def loadImage(self,fname):
self.image=cv2.imread(fname,cv2.IMREAD_COLOR)
self.displayImage()
# display loaded image on main window
def displayImage(self):
qformat=QImage.Format_Indexed8
if len(self.image.shape)==3: # rows[0],cols[1],channels[2]
if(self.image.shape[2])==4:
qformat=QImage.Format_RGBA8888
else:
qformat=QImage.Format_RGB888
img=QImage(self.image,self.image.shape[1],self.image.shape[0],self.image.strides[0],qformat)
# BGR > RGB
img= img.rgbSwapped()
self.imgLabel.setPixmap(QPixmap.fromImage(img))
self.imgLabel.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter)
if __name__=='__main__':
import sys
app=QApplication(sys.argv)
window=MyMainWindow()
window.setWindowTitle('Load and Save Images')
window.showMaximized()
sys.exit(app.exec_())
martin | 2020-05-17 09:01:12 UTC | #2
Hey @guidoasc4064 welcome to the forum :slight_smile:
I can't be 100% sure without testing it (can you also attach your ui file?) but looking at the code I think the issue is that you're drawing to the wrong pixmap. In draw_something
you have e.g.
painter = QtGui.QPainter(self.imgLabel.pixmap())
But if I understand you right you want to draw to the actual image, which is in self.image
? If that's right the line should be --
painter = QtGui.QPainter(self.image.pixmap())
Hope that helps!
Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.