PyQt crashing when trying to round variable in label - python

I'm trying to create a widget that prints into a label how far the mouse is from the center. Here is my code:
import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def mouseMoveEvent(self, event):
distance_from_center = ((event.y() - 250) + (event.x() - 500))**0.5
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(round(distance_from_center)))
def drawPoints(self, qp, x, y):
qp.setPen(Qt.red)
qp.drawPoint(x, y)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
This is where the problem lies:
def mouseMoveEvent(self, event):
distance_from_center = ((event.y() - 250) + (event.x() - 500))**0.5
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(round(distance_from_center)))
If I remove the round function around distance_from_center, it prints a value but it's not pretty. How can I make this work? Why can't I simply round?

If you want to calculate the Euclidean distance you must change:
((event.y() - 250) + (event.x() - 500))**0.5
to
((event.y() - 250)**2 + (event.x() - 500)**2)**0.5

Related

PyQt5 QPixmap scaled origin

I have needed to create a tool that can gather pixel values for the corners of a box of an image. I have decided to use PyQt5 for this as it is what I am most familiar with. I have used QPixmap for this and the scaled function to scale an image for zooming in and out with the scroll wheel. When I zoom however, the zoom always zooms to the same point and I am struggling to find a way to change the zoom origin. Thanks
import numpy
from matplotlib import pyplot as plt
from PyQt5.QtCore import QDate, Qt, QTime, QDateTime, QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import (QMainWindow, QWidget, QCalendarWidget,
QLabel, QApplication, QVBoxLayout, QTextEdit,
QFileDialog, QAction, QPushButton, QHBoxLayout,
QGridLayout, QInputDialog, QLineEdit, QTextEdit,
QPlainTextEdit, QRubberBand)
from PyQt5.QtGui import QIcon, QPixmap
import os, sys
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'BoundingBoxQT'
self.left = 1000
self.top = 100
self.width = 748
self.height = 1402
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
# Create widget
self.label = QLabel(self)
self.pixmap = QPixmap('BALMRCRTLOA_trisected.png')
self.label.setPixmap(self.pixmap)
self.label.resize(self.pixmap.width(),self.pixmap.height())
self.show()
#getting the positio of the mouse click
self.mousePressEvent = self.ClickPosition
self.mouseReleaseEvent = self.ReleasePosition
self.mouseMoveEvent = self.Move
self._band = QRubberBand(QRubberBand.Rectangle, self)
self._band.show()
self.scale = 1
self.wheelEvent = self.ScrollWheel
self.scrollcount = 0
def ClickPosition(self, event):
self.origin = event.pos()
x = event.pos().x()
y = event.pos().y()
print(x,y)
def Move(self, event):
self._band.setGeometry(QRect(self.origin, event.pos()).normalized())
def ReleasePosition(self, event):
x = event.pos().x()
y = event.pos().y()
print(x,y)
def ScrollWheel(self,event):
result = event.angleDelta().y()
x = event.pos().x()
y = event.pos().y()
if result < 0:
self.zoom_out(self.wheelEvent,x,y)
elif result > 0:
self.zoom_in(self.wheelEvent,x,y)
def zoom_in(self,event,x,y):
if self.scrollcount == 7:
return
self.scale *= 1.5
self.scrollcount += 1
self.resize(x,y)
def zoom_out(self,event,x,y):
if self.scrollcount == 0:
return
self.scale /= 1.5
self.scrollcount -= 1
self.resize(x,y)
def resize(self,x,y):
size = self.label.size() # maybe remove the self if wrong
scaled = self.pixmap.scaled(self.scale*size)
self.label.setPixmap(scaled)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

Draw a second identical polygon inside a primary one with a certain gap

The code below sketches a polygon on an image. I would like to draw a second identical shape inside the primary one with a gap of 0.3m. I have tried a couple of solutions but none of them worked in all use cases.
Please refer to the attached screenshot.
Context: The shape is drawn by combining a group of selected points using the QPolygon class.
import sys
from sympy import Polygon
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import QLine, Qt, QPoint, QRect
from PyQt5.QtGui import QPixmap, QPainter,QColor,QPolygon
from PyQt5 import QtCore, QtGui, QtWidgets, uic
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.window_width, self.window_height =1200,800
self.setMinimumSize(self.window_width,self.window_height)
layout= QVBoxLayout()
self.setLayout(layout)
self.pix = QPixmap('image.jpg')
self.resize(self.pix.width(),self.pix.height())
# self.pix.fill(Qt.white)
# tableau
self.point = QPoint()
self.tab =[]
def paintEvent(self,event):
painter = QPainter(self)
pen = QtGui.QPen()
pen.setColor(QtGui.QColor('red'))
pen.setWidth(3)
painter.setPen(pen)
painter.drawPixmap(QPoint(),self.pix)
if not self.point.isNull():
# rect = QRect(self.begin,self.destination)
# painter.drawRect(rect.normalized())
line = QPoint(self.point)
painter.drawPoint(line)
def mousePressEvent(self,event):
if event.buttons() & Qt.LeftButton:
self.point = event.pos()
# self.destination = self.begin
self.update()
# def mouseMoveEvent(self,event):
# if event.buttons() & Qt.LeftButton:
# self.point = event.pos()
# self.update()
def mouseReleaseEvent(self,event):
pen = QtGui.QPen()
painter = QPainter(self.pix)
if event.button() == Qt.LeftButton:
# rect = QRect(self.begin,self.destination)
line = QPoint(self.point)
pen.setColor(QtGui.QColor('red'))
pen.setWidth(3)
painter.setPen(pen)
painter.drawPoint(line)
painter.setPen(QColor(168, 34, 3))
self.tab.append(self.point)
print(self.point.x,self.point.y)
self.point = QPoint()
# w = (rect.width()*12.5)/1056
# h = (rect.height()*12.5/1056)
# a=w*h
# print(w, h,a)
self.update()
if event.button() == Qt.RightButton:
points = QPolygon(self.tab)
pen.setColor(QtGui.QColor('red'))
painter.setPen(pen)
painter.drawPolygon(points)
#print(self.tab[0])
polytab=[]
for i in self.tab:
polytab.append((i.x(),i.y()))
print(Polygon(*polytab).area*(12.5/1056)*(12.5/1056))
print((self.tab[0].x()-self.tab[1].x())*(12.5/1056))
self.tab=[]
self.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet('''QWidget{font-size:30px}''')
myAPP = MyApp()
myAPP.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
The algorithm is given a vertex so the associated edges must be translated in a parallel way and the intersection of these lines is a point of the desired polygon.
import sys
from PyQt5.QtCore import QLineF
from PyQt5.QtGui import QColor, QPainter, QPen, QPolygonF
from PyQt5.QtWidgets import QApplication, QWidget
def calculate_inner_polygon(polygon, offset):
if polygon.count() < 3:
return QPolygonF()
points = []
for i in range(polygon.count()):
pp = polygon[(i - 1 + polygon.count()) % polygon.count()]
pc = polygon[i]
pn = polygon[(i + 1 + polygon.count()) % polygon.count()]
line_0 = QLineF(pp, pc)
normal_0 = line_0.normalVector()
normal_0.setLength(offset)
line_0.translate(normal_0.dx(), normal_0.dy())
line_1 = QLineF(pc, pn)
normal_1 = line_1.normalVector()
normal_1.setLength(offset)
line_1.translate(normal_1.dx(), normal_1.dy())
t, point = line_0.intersects(line_1)
if t != QLineF.NoIntersection:
points.append(point)
return QPolygonF(points)
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(1200, 800)
self._points = list()
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), QColor("white"))
if not self._points:
return
pen_width = 3
offset = -8
pen = QPen(QColor("red"))
outer_polygon = QPolygonF(self._points)
inner_polygon = calculate_inner_polygon(outer_polygon, offset)
for polygon in (outer_polygon, inner_polygon):
pen.setWidth(pen_width)
painter.setPen(pen)
painter.drawPolygon(polygon)
pen.setWidth(2 * pen_width)
painter.setPen(pen)
for point in polygon:
painter.drawPoint(point)
def mouseReleaseEvent(self, event):
self._points.append(event.pos())
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())

create a transparent grid on top of QImage

I am trying to build a paint app that has a grid, but I don't want to grid to be included in the saved photo.
I tried to draw on a transparent image, but I got a black background instead!
Here is the full code:
import sys
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import QPoint, Qt
from PyQt5.QtGui import QBrush, QGuiApplication, QImage, QPainter, QPen, QIcon, QColor
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTextEdit, QAction, QFileDialog,
QGraphicsScene, QGraphicsProxyWidget, QGraphicsView
import pyautogui
import matplotlib.pyplot as plt
class Drawer(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._drawing = False
self.last_point = QPoint()
self.image_layer = QImage(self.size(), QImage.Format_RGB32)
self.image_layer.fill(Qt.gray)
self.brushSize = 2
self.brushColor = Qt.black
#paint = QPainter(self.image_layer)
#paint.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
# paint.drawLine(0,0,self.size().width(),0)
# paint.drawLine(0,10,200,10)
#paint.drawLine(0,0,0,200)
#paint.drawLine(10,0,10,200)
self.update()
def mousePressEvent(self, event):
self._drawing = True
self.last_point = event.pos()
def mouseMoveEvent(self, event):
if self._drawing and event.buttons() & Qt.LeftButton:
painter = QPainter(self.image_layer)
painter.setPen(
QPen(
self.brushColor,
self.brushSize,
Qt.SolidLine,
Qt.RoundCap,
Qt.RoundJoin,
)
)
painter.drawLine(self.last_point, event.pos())
self.last_point = event.pos()
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(QPoint(), self.image_layer)
painter.end()
def resizeEvent(self, event):
if (
self.size().width() > self.image_layer.width()
or self.size().height() > self.image_layer.height()
):
qimg = QImage(
max(self.size().width(), self.image_layer.width()),
max(self.size().height(), self.image_layer.height()),
QImage.Format_RGB32,
)
qimg.fill(Qt.gray)
painter = QPainter(qimg)
painter.drawImage(QPoint(), self.image_layer)
painter.drawLine(0, 0, qimg.size().width(), 0)
painter.drawLine(0, 10, qimg.size().width(), 10)
painter.drawLine(0, 600, qimg.size().width(), 600)
print(qimg.size().height())
painter.end()
self.image_layer = qimg
self.update()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
[x, y] = pyautogui.size()
self.setGeometry(0, 0, x, y)
self.drawer = Drawer()
textbox = QTextEdit("Converted text will show here")
central_widget = QWidget()
self.setCentralWidget(central_widget)
vlay = QVBoxLayout(central_widget)
vlay.addWidget(textbox)
vlay.addWidget(self.drawer, stretch=1)
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu("File")
saveAction = QAction(QIcon("icons/save.png"), "Save", self)
saveAction.setShortcut("Ctrl+S")
fileMenu.addAction(saveAction)
saveAction.triggered.connect(self.save)
def save(self):
filePath, _ = QFileDialog.getSaveFileName(self.drawer, "Save Image", "",
"PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")
if filePath == "":
return
self.drawer.image_layer.save(filePath)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
Note: there are only two lines that mimics the grid, I can draw it later, but for now I want the saved image to not include the grind lines.
You should paint the grid in the paintEvent, instead of continuously painting it onto the image.
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(QPoint(), self.image_layer)
gridSize = 10
x = y = 0
width = self.width()
height = self.height()
while y <= height:
# draw horizontal lines
painter.drawLine(0, y, width, y)
y += gridSize
while x <= width:
# draw vertical lines
painter.drawLine(x, 0, x, height)
x += gridSize

how to upgrade from pyqt4 to pyqt5 in python

I want to upgrade or convert this code from pyqt4 to pyqt5 as this code is not compatible with latest pyqt5.
So can someone tell me what major changes i can make in this code to run it in pyqt5.
import sys
from PyQt4.QtCore import Qt
from PyQt4.QtCore import QRectF
from PyQt4.QtWidgets import QApplication
from PyQt4.QtGui import QColor
from PyQt4.QtGui import QFont
from PyQt4.QtGui import QPainter
from PyQt4.QtGui import QPixmap
from PyQt4.QtGui import QTextOption
from PyQt4.QtGui import QToolTip
from PyQt4.QtGui import QWidget
this are all the imported libraries for this code
class AreaSelector(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, None, Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowState(Qt.WindowFullScreen)
self.setAutoFillBackground(False)
self.parent = parent
self.start_x = 0
self.start_y = 0
self.end_x = 0
self.end_y = 0
self.current_x = 0
self.current_y = 0
def showEvent(self, event):
self.bg = QPixmap.grabWindow(QApplication.desktop().winId())
self.screen_geometry = QApplication.desktop().screenGeometry(self)
def mousePressEvent(self, event):
self.start_x = event.globalX()
self.start_y = event.globalY()
def mouseReleaseEvent(self, event):
self.end_x = event.globalX()
self.end_y = event.globalY()
please view the full code here full code
Translating a PyQt4 code to PyQt5 is not a trivial task:
PyQt4 and PyQt5 are wrappers of Qt4 and Qt5, respectively, so both are affected by the changes of that transition, and one of the transitions is that the QtGui sub-module of Qt4 was divided into the QtGui and QtWidgets sub-modules of Qt5.
Some classes and methods are deprecated so you will have to find an equivalent if it exists.
In this case both things happen, the solution for the first case is simple: You must look in the Qt docs and check to which sub-module it belongs, for example QToolTip, at the top there is a table:
And the part of QT += widgets that indicates that it belongs to the QtWidgets sub-module is observed.
But the second case is somewhat more complicated since it involves looking for an equivalent that may or may not be in the same class, in this case it happens with the QPixmap.grabWindow() method which is deprecates (see here for more information). After doing a search you can replace that code with QApplication.primaryScreen().grabWindow(0).
Considering all of the above, the translation is:
import sys
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap, QTextOption, QScreen
from PyQt5.QtWidgets import QApplication, QToolTip, QWidget
class AreaSelector(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, None, Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowState(Qt.WindowFullScreen)
self.setAutoFillBackground(False)
self.parent = parent
self.start_x = 0
self.start_y = 0
self.end_x = 0
self.end_y = 0
self.current_x = 0
self.current_y = 0
def showEvent(self, event):
self.bg = QApplication.primaryScreen().grabWindow(0)
self.screen_geometry = QApplication.primaryScreen().geometry()
def mousePressEvent(self, event):
self.start_x = event.globalX()
self.start_y = event.globalY()
def mouseReleaseEvent(self, event):
self.end_x = event.globalX()
self.end_y = event.globalY()
def mouseMoveEvent(self, event):
self.current_x = event.globalX()
self.current_y = event.globalY()
self.repaint()
text = "Start: %sx%s \nEnd: %sx%s" % (
self.start_x,
self.start_y,
self.current_x,
self.current_y,
)
QToolTip.showText(event.pos(), text)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
self._acceptSelection()
elif event.key() == Qt.Key_Escape:
self.close()
def _acceptSelection(self):
if self.parent is not None:
self.parent.areaSelectEvent(
self.start_x, self.start_y, self.end_x, self.end_y
)
self.close()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.fillRect(self.screen_geometry, QColor(10, 10, 10, 125))
self._paint_selection(painter)
self._paint_usage_text(painter)
painter.end()
def _paint_usage_text(self, painter):
font = QFont("Helvetica [Cronyx]", 26, QFont.Bold)
painter.setFont(font)
painter.setPen(QColor(255, 255, 255, 255))
screen_width = self.screen_geometry.width()
text_width = 800
text_start_x = screen_width / 2 - text_width / 2
screen_height = self.screen_geometry.height()
text_height = 200
text_start_y = screen_height / 2 - text_height / 2
textoption = QTextOption(Qt.AlignCenter)
textbox = QRectF(text_start_x, text_start_y, text_width, text_height)
painter.drawText(
textbox,
"Click & Drag to select an area\n" "ENTER to confirm or ESC to cancel",
textoption,
)
painter.drawRoundedRect(textbox, 20, 20)
def _paint_selection(self, painter):
"""Draws the current user selection"""
rectangle = QRectF()
if self.start_x > self.current_x:
rectangle.setLeft(self.current_x)
rectangle.setRight(self.start_x)
else:
rectangle.setLeft(self.start_x)
rectangle.setRight(self.current_x)
if self.start_y > self.current_y:
rectangle.setTop(self.current_y)
rectangle.setBottom(self.start_y)
else:
rectangle.setTop(self.start_y)
rectangle.setBottom(self.current_y)
painter.drawPixmap(rectangle, self.bg, rectangle)
painter.drawRect(rectangle)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = AreaSelector()
main.show()
sys.exit(app.exec_())

How to draw a line from mouse to a point in PyQt5?

Here is my code:
import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(event.x(), event.y(), 250, 500)
q.end()
def drawPoints(self, qp, x, y):
qp.setPen(Qt.red)
qp.drawPoint(x, y)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
What I'm trying to do is paint a simple line from the position of the mouse to the middle of the widget using this:
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(event.x(), event.y(), 250, 500)
q.end()
But when I run it, no line is visible. What do I need to do?
You must implement the function QPaintEvent, in this function you must draw the line, in addition you must call the function update() to update the drawing.
import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
self.pos = None
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
self.pos = event.pos()
self.update()
def paintEvent(self, event):
if self.pos:
q = QPainter(self)
q.drawLine(self.pos.x(), self.pos.y(), 500, 250)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
Output:
You can only use a QPainter inside the paintEvent method. So one way to fix is to record the x and y coordinates inside the class and update the root widget. This then calls paintEvent and you will see the line.
example
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QWidget
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
self.x = -1
self.y = -1
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def paintEvent(self, e):
if not (self.x == -1 or self.y == -1):
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(self.x, self.y, 250, 500)
q.end()
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
self.x = event.x()
self.y = event.y()
self.update()
def drawPoints(self, qp, x, y):
qp.setPen(Qt.red)
qp.drawPoint(x, y)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
I wasn't sure about how self.x and self.y would be set initially. The -1 and the check in the paintEvent feels a bit hacky, but at least it paints.
For the previous answer, I tried it under Python3.7 and PyQt5. The result was program crash
'Process finished with exit code -1073740791 (0xC0000409)'.
And in the comments, there were someone else also mentioned the crash.
I find the solution to this crash:
self.x and self.y must be initialized before the calling of self.show()
So I simply modified the code to as follows:
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.x = 100
self.y = 100
self.show()

Categories