How to know the exact position of a camera viewbox in Qt? - python

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

Related

how to add colored lines with opencv after covnersion from grayscale to color (vispy)

I have the following problem. I need to import grayscale images and add some colored lines in the image at distinct positions (prefered colors are red or blue because they are visible)
I thought of converting the photo to rgb and then add line.
shape before the conversion is a 2d array, after conversion 3d
my problem is:
if I do this independently with a script , i get the colored line without any problems
if i integrate it to this script. however whatever I do the line is either black or white (I tried different colors) I specifically checked that when emitting the shape is still a 3d array
the point of this script is to show different images according to a frequence. when getting the image we add certain colored lines then we show them. for simplicity here i just send the same image over and over. the grayscale image should stay have the same characterstics visually. the only colored pixels are the added lines.
I keep thinking there is something wrong with the vispy when showing the image. because if we do this with only cv2 we get the right color.
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 time
import imageio as iio
import cv2
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(my_image)
print("shape before conversion: ", frame.data.shape)
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
print("shape after conversion: ", frame.data.shape)
cv2.line(frame, pt1=(100, 300), pt2=(400, 300), color=(255, 0, 0), thickness=10)
self.new_image.emit(frame.data)
time.sleep(3.0)
except Exception as e:
print(f"problem: {e}")
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, 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:
print("shape when emitting: ", img.shape)
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()
i found the answer thanks to the vispy forum. for the ones that are interested
the whole problem was in the initialization of the image
one needed to put a correct parameter np.zeros(1, 1, 3)
if it s false from the very beginning it will never update automatically.

Move the displayed PyQt5 scene

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_())

How to get movable graphical items inside a QGraphicsItemGroup?

My goal is to write a software that displays two movable disks that live inside the same QGraphicsItemGroup. I'd like to use QGraphicsItemGroup because in this way each disk can access the position of the other. To make the objects movable, I use the flag ItemIsMovable which unfortunately doesn't seem to work inside a QGraphicsItemGroup The following program exemplifies my issue:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsItem,\
QApplication, QGraphicsItemGroup, QGraphicsEllipseItem
class MyDisk(QGraphicsEllipseItem):
def __init__(self, top_left_x, top_left_y, radius, color):
super().__init__(top_left_x, top_left_y, radius, radius)
self.setBrush(color)
self.setFlag(QGraphicsItem.ItemIsMovable)
class MyGroup(QGraphicsItemGroup):
def __init__(self):
super().__init__()
self.disk1 = MyDisk(50, 50, 20, Qt.red)
self.disk2 = MyDisk(150, 150, 20, Qt.red)
self.addToGroup(self.disk1)
self.addToGroup(self.disk2)
# self.setFlag(QGraphicsItemGroup.ItemIsMovable)
class MyView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.setWindowTitle('Red disks are not movable')
self.setSceneRect(0, 0, 250, 250)
self.group = MyGroup()
self.scene.addItem(self.group)
self.scene.addItem(MyDisk(150, 50, 20, Qt.green))
if __name__ == '__main__':
app = QApplication([])
f = MyView()
f.show()
sys.exit(app.exec_())
My problem is that the green disk (not in the group) is movable but the two red disks (in the group) are not. How can I make the two red disks movable? Note that setting the flag ItemIsMovable inside MyGroup doesn't solve the problem because the red disks would then move together (you can try this by uncomment the comment in the code).

Animate using a pixmap or image sequence in Python with QT4

I have a small Python script that makes a transparent window for displaying a graphic on screen and I'd like to animate that graphic, but am entirely unsure how or where to even start. Here's what I do have at least:
import sys
from PyQt4 import QtGui, Qt, QtCore
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
self.setAutoFillBackground(True)
pixmap = QtGui.QPixmap("test1.gif")
pixmap2 = QtGui.QPixmap("test2.gif")
width = pixmap.width()
height = pixmap.height()
self.setWindowTitle("Status")
self.resize(width, height)
self.label = QtGui.QLabel(self)
def animateEvent():
imgnumber = 0
try:
if imgnumber == 1:
self.label.setPixmap(QtGui.QPixmap("test1.gif"))
self.setMask(pixmap.mask())
imgnumber = 0
else:
self.label.setPixmap(QtGui.QPixmap("test2.gif"))
self.setMask(pixmap2.mask())
imgnumber = 1
finally:
QtCore.QTimer.singleShot(1000, animateEvent)
animateEvent()
def paintEvent(self,event):
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
x = Transparent()
x.show()
app.exec_()
This feels like it has the right ingredients, but the pixmap doesn't update.
I tried QMovie, but then the area of the window that is supposed to be transparent is filled with black instead.
check out this code from www.daniweb.com and see if you can modify it to your needs:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MoviePlayer(QWidget):
def __init__(self, gif, parent=None):
super(MoviePlayer, self).__init__(parent)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle("QMovie to show animated gif")
self.movie_screen = QLabel()
self.movie_screen.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.movie_screen.setAlignment(Qt.AlignCenter)
btn_start = QPushButton("Start Animation")
btn_start.clicked.connect(self.start)
btn_stop = QPushButton("Stop Animation")
btn_stop.clicked.connect(self.stop)
main_layout = QVBoxLayout()
main_layout.addWidget(self.movie_screen)
main_layout.addWidget(btn_start)
main_layout.addWidget(btn_stop)
self.setLayout(main_layout)
self.movie = QMovie(gif, QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie_screen.setMovie(self.movie)
def start(self):
"""
Start animation
"""
self.movie.start()
def stop(self):
"""
Stop the animation
"""
self.movie.stop()
app = QApplication(sys.argv)
player = MoviePlayer("/path/to/image.gif")
player.show()
sys.exit(app.exec_())
This ended up being a simple correction of an oversight in the end.
imgnumber needed to be outside of the def as self.imgnumber and needed to be named self.imgnumber each time it was changed.
First, just make sure your animated gif really does have a proper transparent background. The following code works for me, using this fire image as a source:
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
filename = "test.gif"
size = QtGui.QImage(filename).size()
self.setWindowTitle("Status")
layout = QtGui.QVBoxLayout(self)
layout.setMargin(0)
self.movie = QtGui.QMovie(filename)
self.label = QtGui.QLabel(self)
self.label.setMovie(self.movie)
layout.addWidget(self.label)
self.resize(size)
self.movie.start()
This will create a completely transparent and frameless window, with the animated gif playing in a QMovie. There is no black being drawn behind the image. It should fully see through to what ever is underneath.
It is not so far off from your original code. You shouldn't need to set the mask, or do a paint event.

Moving a QGraphicsItem around a central point in PyQt4

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

Categories

Resources