I have a QGraphicSscene in a QGraphicsView object. In my scene you can draw ROIs, so I track the mouse position all the time. Since the objects are often not very big you can zoom in on them, I would like to move the displayed scene section when the mouse is at the edge of the displayed scene. With event.scenePos() I get the position of my mouse pointer, but how can I check if I am at the edge of the scene or not?
Zooming in and out functions in my code as follows:
def zoomIn(self):
self.view.scale(1.1, 1.1)
# some other stuff
def zoomOut(self):
# The initial zoom is always adapted to an image so that it is always larger or equal to the
# size of the GraphicsViews Object (therefore the image fills all areas).
if.self.currentZoom > 1:
self.view.scale(0.9, 0.9)
# some other stuff
To determine if a point is on the edge, you have to verify that the point is inside the rectangle of the QGraphicsView viewport but outside of a smaller rectangle displaced from the previous rectangle by some pixels on all edges:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(self.view)
self.view.viewport().setMouseTracking(True)
self.view.scene().installEventFilter(self)
def eventFilter(self, obj, event):
if (
obj is self.view.scene()
and event.type() == QtCore.QEvent.GraphicsSceneMouseMove
):
vp = self.view.mapFromScene(event.scenePos())
if self.check_if_the_point_is_on_the_edge(vp, delta=10):
print("on the border", event.scenePos())
return super().eventFilter(obj, event)
def check_if_the_point_is_on_the_edge(self, point, delta=1):
rect = self.view.viewport().rect()
internal_rect = rect.adjusted(delta, delta, -delta, -delta)
return rect.contains(point) and not internal_rect.contains(point)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
w.resize(640, 480)
sys.exit(app.exec_())
Related
I am working with OpenGL in python and trying to attach 2d images to a canvas (the images will change according to a certain frequence).
I managed to achieve that but to continue my task i need two things:
the major problem: I need to get the image position (or bounds), sorry if i don't have the correct term, i am new to this. basically i just need to have some kind of positions to know where my picture is in the canvas. i tried to look into the methods and attributes of self.view.camera I could not find anything to help.
one minor problem: i can move the image with the mouse along the canvas and i zoom it. i wonder if it is possible to only allow the zoom but not allow the right/left move [this is resolved in the comments section]
here is my code:
import sys
from PySide2 import QtWidgets, QtCore
from vispy import scene
from PySide2.QtCore import QMetaObject
from PySide2.QtWidgets import *
import numpy as np
import dog
import time
import imageio as iio
class CameraThread(QtCore.QThread):
new_image = QtCore.Signal(object)
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def run(self):
try:
while True:
frame = iio.imread(dog.getDog(filename='randog'))
self.new_image.emit(frame.data)
time.sleep(10.0)
finally:
print('end!')
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 400)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QGroupBox(self.centralwidget)
self.groupBox.setObjectName("groupBox")
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# OpenGL drawing surface
self.canvas = scene.SceneCanvas(keys='interactive')
self.canvas.create_native()
self.canvas.native.setParent(self)
self.setWindowTitle('MyApp')
self.view = self.canvas.central_widget.add_view()
self.view.bgcolor = '#ffffff' # set the canvas to a white background
self.image = scene.visuals.Image(np.zeros((1, 1)),
interpolation='nearest',
parent= self.view.scene,
cmap='grays',
clim=(0, 2 ** 8 - 1))
self.view.camera = scene.PanZoomCamera(aspect=1)
self.view.camera.flip = (0, 1, 0)
self.view.camera.set_range()
self.view.camera.zoom(1000, (0, 0))
self._camera_runner = CameraThread(parent=self)
self._camera_runner.new_image.connect(self.new_image, type=QtCore.Qt.BlockingQueuedConnection)
self._camera_runner.start()
#QtCore.Slot(object)
def new_image(self, img):
try:
self.image.set_data(img)
self.image.update()
except Exception as e:
print(f"problem sending image: {e}")
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Do you want to know the coordinates of the picture in the viewport (the window), or do you want the coordinates of the picture on the canvas? Vispy actually puts the image at (0,0) by default inside the Vispy canvas. When you move around the canvas you actually aren't moving the canvas around, you are just moving the camera which is looking at the canvas so the coordinates of the picture stay at (0,0) regardless if you move around the viewport or the camera or not. Also the coordinates of the Vispy canvas correspond one to one with the pixel length and width of your image. One pixel is one unit in Vispy. You can check this by adding this method to your MainWindow class:
def my_handler(self,event):
transform = self.image.transforms.get_transform(map_to="canvas")
img_x, img_y = transform.imap(event.pos)[:2]
print(img_x, img_y)
# optionally do the below to tell other handlers not to look at this event:
event.handled = True
and adding this to your __init__ method:
self.canvas.events.mouse_move.connect(self.my_handler)
You can see that when you hover over the top left corner of your image, it should print roughly (0,0).
def my_handler(self,event):
transform = self.image.transforms.get_transform(map_to="canvas")
img_x, img_y = transform.imap(event.pos)[:2]
print(img_x, img_y)
# optionally do the below to tell other handlers not to look at this event:
event.handled = True
I am making an image acquisition program using a webcam in PyQt. Whenever I click on the image, which is inside a label widget, i need to put an additional fixed cursor at that position (to have a reference point for e.g). I created a cursor object, set a shape and position (obtained from the clicked position). yet I dont see an additional cursor being created at the clicked position i.e. Qpoint
below is the code snippet:
def eventFilter(self, source, event):
if event.type()==QtCore.QEvent.MouseButtonPress:
self.new_cursor=QtGui.QCursor() # the additional cursori want to place
self.new_cursor.setShape(self,Qt.PointingHandCursor) # setting shape
self.cursor_clicked=event.pos() # getting position from the click
self.cursor_clicked=self.label.mapFromParent(event.pos()) #mapping to widget coords.
self.cursor_x=self.cursor_clicked.x()
self.cursor_y=self.cursor_clicked.y()
self.new_cursor.setPos(self.cursor_x,self.cursor_y)
self.setCursor(self.new_cursor)
return QtWidgets.QWidget.eventFilter(self,source,event)
QCursor is not a "static image", but is an "abstract" object related to the mouse cursor, so there's no use of it for your purpose.
What you're looking for is drawing on the existing image or the widget that shows it.
Since you probably want to leave the image unchanged, the second option is what you're looking for.
The idea is that you call the base class implementation of the paintEvent method and then draw over it.
Painting a crosshair by hand is not that hard using simple lines, but you'll need to draw an extra border around the cross using a different color to ensure its visibility even on lighter or darker backgrounds, which makes it unnecessary long; in this example I'm using the cursor image Qt uses in the CursorShape enum documentation, but you can use whatever image you want, as soon as its center is exactly at the center of it (hint: use a square image with odd sized width/height).
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
self.label = QtWidgets.QLabel()
layout.addWidget(self.label)
self.label.setPixmap(QtGui.QPixmap('myimage.png'))
self.label.installEventFilter(self)
self.cursorPos = None
# I'm using the crosshair cursor as shown at
# https://doc.qt.io/qt-5/qt.html#CursorShape-enum
self.cursorPixmap = QtGui.QPixmap('cursor-cross.png')
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
# set the current position and schedule a repaint of the label
self.cursorPos = event.pos()
self.label.update()
elif event.type() == QtCore.QEvent.Paint:
# intercept the paintEvent of the label and call the base
# implementation to actually draw its contents
self.label.paintEvent(event)
if self.cursorPos is not None:
# if the cursor position has been set, draw it
qp = QtGui.QPainter(self.label)
# translate the painter at the cursor position
qp.translate(self.cursorPos)
# paint the pixmap at an offset based on its center
qp.drawPixmap(-self.cursorPixmap.rect().center(), self.cursorPixmap)
return True
return super().eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Besides that, another approach is to use the Graphics View Framework, add the pixmap to the scene and add/move another pixmap for the cursor when the user clicks on the image. Dealing with QGraphicsViews, QGraphicsScenes and their items is a bit more complex, but if you're going to need some more advanced level of interaction with an image, it usually is the better path to take.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
self.view = QtWidgets.QGraphicsView()
layout.addWidget(self.view)
# remove any border around the view
self.view.setFrameShape(0)
self.scene = QtWidgets.QGraphicsScene()
self.view.setScene(self.scene)
pixmap = QtGui.QPixmap('myimage.png')
# adapt the view's size to that of the pixmap
self.view.setFixedSize(pixmap.size())
# add a pixmap to a scene, which returns a QGraphicsPixmapItem
self.pixmapItem = self.scene.addPixmap(pixmap)
self.crossHairItem = None
self.view.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
if not self.crossHairItem:
# as above, get a QGraphicsPixmapItem for the crosshair cursor
pixmap = QtGui.QPixmap('cursor-cross.png')
self.crossHairItem = self.scene.addPixmap(pixmap)
# set an offset of the item, so that its position is always
# based on the center of the pixmap
self.crossHairItem.setOffset(-pixmap.rect().center())
self.crossHairItem.setPos(self.view.mapToScene(event.pos()))
return super().eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Both methods behave in the same way for the user, and as you can see they look exactly identical.
How can I color my QPolygonF item? I have created triangle but don't know how to fill it with certain color.
I tried to find class in Qt library but didn't find any. Here is code where I create the triangle and add it to the scene. I tried to use setBrush() function, but QPolygonF doesn't have that class..
triangle = QtGui.QPolygonF()
triangle.append(QtCore.QPointF(0,550)) # Bottom-left
triangle.append(QtCore.QPointF(50, 550)) # Bottom-right
triangle.append(QtCore.QPointF(25, 525)) # Tip
self.scene.addPolygon(triangle)
When you use the addPolygon method this returns a QGraphicsPolygonItem, and that GraphicsPolygonItem inherits from QAbstractGraphicsShapeItem, and that class gives the ability to change the fill color using the setBrush() method and the border color with setPen():
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
triangle = QtGui.QPolygonF()
triangle.append(QtCore.QPointF(0, 550)) # Bottom-left
triangle.append(QtCore.QPointF(50, 550)) # Bottom-right
triangle.append(QtCore.QPointF(25, 525)) # Tip
triangle_item = self.scene().addPolygon(triangle)
triangle_item.setBrush(QtGui.QBrush(QtGui.QColor("salmon")))
triangle_item.setPen(QtGui.QPen(QtGui.QColor("gray")))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
w.resize(320, 240)
w.show()
sys.exit(app.exec_())
I want to get mouse position in my pyside2 application.(not desktop mouse position that QCursor gives) and I tried two way. Bellow is my code.
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class Palette(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
def mousePressEvent(self, event):
print(event.pos()) # always return (0,0)
print(QtWidgets.QWidget.mapToGlobal(QtCore.QPoint(0, 0))) #makes parameter type error
print(QtWidgets.QWidget.mapToGlobal(QtWidgets.QWidget)) # makes parameter type error
print(QtWidgets.QWidget.mapToGlobal(QtWidgets.QWidget.pos())) # makes parameter type error
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
palette = Palette(self)
view = QtWidgets.QGraphicsView(palette, self)
view.resize(500, 500)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
app.exec_()
I very wonder how i can get my mouse pos...
From what I understand you want to get the position on the window if you click anywhere in a widget.
To solve the problem, the logic is as follows:
Get the mouse position with respect to the widget
Convert that position to a global position, that is, with respect to the screen.
Convert that global position to a position relative to the window.
For the first step if mousePressEvent() is used, event.pos() returns the position relative to the widget.
For the second step you must convert that position relative to the widget to global with mapToGlobal().
And for the third step mapFromGlobal() of window is used.
def mousePressEvent(self, event):
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
super(Widget, self).mousePressEvent(event)
Update:
The QGraphicsScene is not a widget, it is not a visual element, although it is part of the representation of a visual element: the QGraphicsView. For you to understand I will explain you with an analogy, let's say that there is a cameraman recording a scene, in that example the QGraphicsScene is the scene and the QGraphicsView is what the camera records, that is, it shows a piece of the QGraphicsScene, so there could be another cameraman recording the scene from another point, so it would show the same scene from another perspective, so the position of the scene depends on the camera, so if your current question would be equivalent to saying which is the position of the point P respect to the camera i-th, and that from the scene is impossible, you should get it from the camera.
So in conclusion you should not use QGraphicsScene but QGraphicsView, the following solutions implement the same logic using 2 different methods:
1. Creating a custom class of QGraphicsView:
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class GraphicsView(QtWidgets.QGraphicsView):
def mousePressEvent(self, event):
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
super(GraphicsView, self).mousePressEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene(self)
view = GraphicsView(scene, self)
self.setCentralWidget(view)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
sys.exit(app.exec_())
2. Using eventfilter:
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(scene, self)
self.setCentralWidget(self._view)
self._view.installEventFilter(self)
def eventFilter(self, obj, event):
if obj is self._view and event.type() == QtCore.QEvent.MouseButtonPress:
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
return super(MainWindow, self).eventFilter(obj, event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
sys.exit(app.exec_())
On the other hand mapToGlobal() is a method that must be called by an instance, when you use QtWidgets.QWidget.mapToGlobal() there is no instance, my question is, what widget do you have the position? the position you have with respect to self, so you must use self.mapToGlobal() , that works for the objects belonging to a class that inherit from QWidget as QGraphicsView, but not in QGraphicsScene since it does not inherit from QWidget, it is not a widget as indicated in the lines above.
I have recently found a more universal way of getting the cursor position, if you don't want to go through sub classing and events.
# get cursor position
cursor_position = QtGui.QCursor.pos()
print cursor_position
### Returns PySide2.QtCore.QPoint(3289, 296)
I'm using Python 2.7 and PyQt4. I am trying to have a half-circle object that is a QGraphicsItem. I want to be able to move it using the mouse, by clicking and dragging. I can create the object and move it around with the mouse by setting the flag ItemIsMovable. Now the half-circle moves around freely but I want it to move just around the fixed central point. It is difficult to describe, but it should be something similar to a dial. How can I accomplish this?
you can use QGraphicsItem::mouseMoveEvent event to track item's movements within the scene and correct its position once it's moved off the restricted area. Pls, check if an example below would work for you:
import sys
from PyQt4 import QtGui, QtCore
class TestEclipseItem(QtGui.QGraphicsEllipseItem):
def __init__(self, parent=None):
QtGui.QGraphicsPixmapItem.__init__(self, parent)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
# set move restriction rect for the item
self.move_restrict_rect = QtCore.QRectF(20, 20, 200, 200)
# set item's rectangle
self.setRect(QtCore.QRectF(50, 50, 50, 50))
def mouseMoveEvent(self, event):
# check of mouse moved within the restricted area for the item
if self.move_restrict_rect.contains(event.scenePos()):
QtGui.QGraphicsEllipseItem.mouseMoveEvent(self, event)
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
scene = QtGui.QGraphicsScene(-50, -50, 600, 600)
ellipseItem = TestEclipseItem()
scene.addItem(ellipseItem)
view = QtGui.QGraphicsView()
view.setScene(scene)
view.setGeometry(QtCore.QRect(0, 0, 400, 200))
self.setCentralWidget(view)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards