How to add objects scaled to the graphics view and screen? - python

I am trying to make a PyQt5 application where the user can click on a graphics view window in order to place a rectangle. I managed to scale the window using the fitInView method and that works alright but whenever the windows size is changed the relative size of the newly placed object changes as well. How can I make it so that everything is relative to window/screen/graphicsView size? I tried to use the scene width but it did weird things with scale and position when the window was resized.
Here is my the class for my main window. gv is the graphics view and Ui_MainWindow is from QtDesigner.
class Window(Ui_MainWindow, QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.scene = QGraphicsScene()
self.scene.setSceneRect(QRectF(0, 0, 100, 100))
self.gv.setScene(self.scene)
self.gv.fitInView(0, 0, 100, 100, Qt.KeepAspectRatio)
def resizeEvent(self, e):
self.gv.fitInView(0, 0, 100, 100, Qt.KeepAspectRatio)
Here is my custom graphics view class:
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtGui import QPolygonF
from PyQt5.QtCore import QPoint, QRectF
class GraphicsView(QGraphicsView):
def __init__(self, window):
super().__init__(window)
def mousePressEvent(self, QMouseEvent):
rect = self.mapToScene(QMouseEvent.x(), QMouseEvent.y(), 5, 5)
\\ rect = self.mapToScene(QMouseEvent.x(), QMouseEvent.y(), self.width()/20, self.height()/20)
self.scene().addPolygon(rect)
As a side question am I currently doing this as is recommended? Like using fitInView and mapToScene etc?
Also when I set the scene rectangle what values should I be choosing in place of 100? That was just arbitrary.
EDIT: I managed to achieve something similar to what I want by using the graphics view width as a base variable as shown in the comment in the code above. However I am now having the problem that if you resize the window it shows areas of the scene that were not previously visible. Is there a way to stretch the scene or something like that to make sure it always fills the graphics view?

Related

Overriding paintEvent for QSpinBox and drawing on top (PyQt5)

I am trying to subclass and draw on top of a QSpinBox by overriding its paintEvent. For example I try to draw an orange rect covering the entire spin box, but it only ends up going on top of some elements, and remains behind others, and moreover I discovered that passing/returning the paintEvent still draws the widget, so I am not sure I understand what is happening here. This is all on Windows btw.
So my precise question is, how do I draw a rect on top of a QSpinBox, such that it paitns on top of everythign else and is part of the subclassed widget's code (not externally painted)?
My second question is how to override the painting such that when the widget is shown it is not drawn at all?
My code:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QSpinBox, QWidget
from PyQt6.QtGui import QPaintEvent, QBrush, QColor, QPainter
class CustomSpinBox(QSpinBox):
def paintEvent(self, e: QPaintEvent) -> None:
super().paintEvent(e)
painter = QPainter(self)
painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern))
painter.drawRect(0, 0, self.width(), self.height())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
window.setGeometry(500, 200, 400, 300)
spin_box = CustomSpinBox(window)
spin_box.move(100, 100)
spin_box.setFixedSize(120, 40)
window.show()
app.exec()
This paints over the spin box arrows, but below the actual value field. I am not sure how and where this layering order is defined, I assume it is something to do with how SubControls of ComplexControls are handled, but I could not really understand how this works exactly based on the information I found.

Antialiasing images in pyqtgraph ImageView

I am using PyQtGraph and am really enjoying it, but have hit upon an issue that may force me to move to something else.
I am displaying medical images (CT/MRI etc.) as numpy 2D or 3D arrays in the ImageView which gives the nice slider view for volume data. The problem is theses images are often low res (256x256) and when viewed on large monitors or just zoomed-in they look blocky and horrible.
How can I show these images antialiased? This seems to be possible as mentioned here:
How can anti-aliasing be enabled in a pyqtgraph ImageView?
and a few other places suggesting all you need to do is:
import pyqtgraph as pg
pg.setConfigOptions(antialias=True)
and enable antialiasing in the graphics view, which I assume would be this:
myImageViewWidget = pg.ImageView(parent=None)
myImageViewWidget.ui.graphicsView.setAntialiasing(True)
But this doesn't seem to do anything different in my code. What am I doing wrong?
I'm using Windows 10 (but need it to work on MacOs - Darwin), Python 3.7, PySide 2 (5.15.12) and PyQtGraph 0.12.3
'Minimum' code to reproduce the issue (not quite but I want to keep ImageView subclassed as that's how I have it in my code):
import sys
from PySide2.QtWidgets import (
QApplication,
QHBoxLayout,
QMainWindow,
QWidget,
)
import pyqtgraph as pg
import numpy as np
pg.setConfigOptions(antialias=True)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.cw = QWidget(self)
self.cw.setAutoFillBackground(True)
self.setCentralWidget(self.cw)
self.layout = QHBoxLayout()
self.cw.setLayout(self.layout)
self.ImgWidget = MyImageWidget(parent=self)
self.layout.addWidget(self.ImgWidget)
self.show()
class MyImageWidget(pg.ImageView):
def __init__(self, parent=None):
super().__init__(parent)
self.ui.histogram.hide()
self.ui.roiBtn.hide()
self.ui.menuBtn.hide()
self.ui.graphicsView.setAntialiasing(True)
# 5 frames of 50x50 random noise
img = (1000 * np.random.normal(size=(5, 50, 50))) - 500
self.setImage(img)
def main():
app = QApplication()
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What you're referring to is not antialiasing.
Antialiasing "smoothens" the portions of an image that cannot "fit" precisely a single physical pixel.
What you are seeing is in fact the opposite, as each source pixel is actually large enough to be shown as it is: a square that possibly occupies more physical pixels.
What you probably want is a blur effect, which can be achieved through a QGraphicsBlurEffect set on the self.imageItem of the view:
class MyImageWidget(pg.ImageView):
def __init__(self, parent=None):
# ...
self.blurEffect = QGraphicsBlurEffect(blurRadius=1.1)
self.imageItem.setGraphicsEffect(self.blurEffect)
Note that since the image item is always scaled and the blur effect is proportional, you might need to adjust the blur radius to even smaller values depending on the shown resolution (but still bigger than 1.0).

PyQt5: how to open one window in each connected screen?

I'm trying to create a screenshot utility for linux using python. Right now I'm stuck at trying to implement a function that lets the user select a region from a live screen and screenshot it. After much pondering, I reached the conclusion to create a full-screen window on each screen to get the mouse's click and drag coordinates.
How can I have my program create a full-screen window (without the toolbar icon) for each screen connected to the system?
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
class InvisWindow(qtw.QWidget):
def __init__(self, screens):
super().__init__()
self.setWindowFlags(qtc.Qt.Tool | qtc.Qt.FramelessWindowHint)
self.show()
self.showFullScreen()
self.windowHandle().setScreen(screens[0])
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = InvisWindow(app.screens())
sys.exit(app.exec_())
I found this code searching for a way to do it, but no matter which screen I pass to setScreen() it always appears on a single screen, i.e. changing the argument doesn't change which screen it appears in.
There are two problems:
as the documentation explains:
If the screen is part of a virtual desktop of multiple screens, the window will not move automatically to newScreen.
on Linux, there's some amount of time and system events between the call to show and when the window is actually mapped on the screen the first time (see Initial Geometry), which can be overridden by the window manager if no geometry is explicitly set;
That said, there should be no need to use the QWindow for this, as using move is usually be enough, you only must do it before any call to show() or related functions:
class InvisWindow(qtw.QWidget):
def __init__(self, screens):
super().__init__()
self.setWindowFlags(qtc.Qt.Tool | qtc.Qt.FramelessWindowHint)
self.move(screens[0].geometry().topLeft())
self.showFullScreen()
Note that there's no use in calling show() before showFullScreen(), since it implicitly calls setVisible(True).
If what you want is to show a single window on top of everything, then you could try the following:
class InvisWindow(qtw.QWidget):
mapped = False
def __init__(self, screens):
super().__init__()
self.setWindowFlags(
qtc.Qt.WindowStaysOnTopHint |
qtc.Qt.Tool |
qtc.Qt.FramelessWindowHint
)
self.show()
def moveEvent(self, event):
if not self.mapped:
geometry = qtc.QRect()
for screen in qtw.QApplication.screens():
geometry |= screen.geometry()
if self.pos() != geometry.topLeft():
self.setGeometry(geometry)
self.mapped = True
Please consider the last lines, as they are very important, because trying to do geometry changes in a geometry change event (moveEvent and resizeEvent) can cause recursion.

How to add scrollbars in PyQt4 WITHOUT with absolute positioning

I'm creating an interface in PyQt4, and I am in need of scrollbars. My interface uses absolute positioning. I have looked a other treads for adding scrollbars, but the only answers given are to those interfaces without a layout (such as VBoxLayout, Grid Layout, etc).
Please take a look at my code. How could I add a scrollbars (horizontal and vertical) to this interface?
The full code wouldn't format properly on here, so I'll link to this pastebin
http://pastebin.com/hEH4R534
Here is the base of the interface (a 1500px by 1000px window)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(100,100,1500,1000)
def main():
import sys
app = QtGui.QApplication(sys.argv)
window = Example()
window.show()
sys.exit(app.exec_())
main()
The question is... How would I modify the code above so I have horizontal and vertical scrollbars?
Only widgets that inherit from QAbstractScrollArea can have scroll bars. You could place one of those widgets inside your widget, and then place other widgets inside that widget. Or, just have your widget inherit from QScrollArea instead of QWidget.
By default, scroll bars will only appear when necessary to display hidden child widgets. You can force scroll bars to appear by setting the scroll bar policy for the widget.
from PyQt4.QtCore import Qt
...
def initUI(self):
self.setGeometry(100, 100, 1500, 1000)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

How to detect mouse click on images displayed in GUI created using PySide

Firstly, I'm new to Python, Qt and PySide so forgive me if this question seems too simple.
What I'm trying to do is to display a bunch of photos in a grid in a GUI constructed using PySide API. Further, when a user clicks on a photo, I want to be able to display the information corresponding to that photo. Additionally, I would like the container/widget used for displaying the photo to allow for the photo to be changed e.g. I should be able to replace any photo in the grid without causing the entire grid of photos to be created from scratch again.
Initially I tried to use QLabel to display a QPixmap but I realized (whether mistakenly or not) that I have no way to detect mouse clicks on the label. After some searching, I got the impression that I should subclass QLabel (or some other relevant class) and somehow override QWidget's(QLabel's parent class) mousePressEvent() to enable mouse click detection. Problem is I'm not sure how to do that or whether there is any alternative widget I can use to contain my photos other than the QLabel without having to go through subclass customization.
Can anyone suggest a more suitable container other than QLabel to display photos while allowing me to detect mouse clicks on the photo or provide some code snippet for subclassing QLabel to enable it to detect mouse clicks?
Thanks in advance for any replies.
I've added an example of how to emit a signal and connect to another slot. Also the docs are very helpful
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
picture = PictureLabel("pic.png", self)
picture.pictureClicked.connect(self.anotherSlot)
layout.addWidget(picture)
layout.addWidget(QLabel("click on the picture"))
def anotherSlot(self, passed):
print passed
print "now I'm in Main.anotherSlot"
class PictureLabel(QLabel):
pictureClicked = Signal(str) # can be other types (list, dict, object...)
def __init__(self, image, parent=None):
super(PictureLabel, self).__init__(parent)
self.setPixmap(image)
def mousePressEvent(self, event):
print "from PictureLabel.mousePressEvent"
self.pictureClicked.emit("emit the signal")
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
Even if the question has been answered, i want to provide an other way that can be used in different situations (see below) :
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
picture = QLabel()
picture.setPixmap("pic.png")
layout.addWidget(picture)
layout.addWidget(QLabel("click on the picture"))
makeClickable(picture)
QObject.connect(picture, SIGNAL("clicked()"), self.anotherSlot)
def anotherSlot(self):
print("AnotherSlot has been called")
def makeClickable(widget):
def SendClickSignal(widget, evnt):
widget.emit(SIGNAL('clicked()'))
widget.mousePressEvent = lambda evnt: SendClickSignal(widget, evnt)
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This way doesn't imply subclassing QLabel so it can be used to add logic to a widget made with QtDeigner.
Pros :
Can be used over QTdesigner compiled files
Can be applied to any kind of widget (you might need to include a super call to the overrided function to ensure widget's normal behavior)
The same logic can be used to send other signals
Cons :
You have to use the QObject syntax to connect signals and slots

Categories