pyqtgraph ImageView and color images - python

I am trying to display an RGB numpy array in an ImageView() (or similar) within a Dock in pyqtgraph.
The general idea is something like this code:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from pyqtgraph.dockarea import Dock, DockArea
class SimDock(Dock):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#self.im1 = pg.image()
self.im1 = pg.ImageView()
self.im1.setImage(np.random.normal(size=(100, 100, 3)))
self.addWidget(self.im1, row=0, col=0)
self.im1.ui.histogram.hide()
self.im1.ui.menuBtn.hide()
self.im1.ui.roiBtn.hide()
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
area = DockArea()
win.setCentralWidget(area)
win.resize(1500, 800)
win.setWindowTitle('pyqtgraph example: dockarea')
simdock = SimDock("Similar Images", size=(500, 500))
area.addDock(simdock, 'right')
win.show()
# Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.instance().exec_()
However, when I run the one above I get:
ValueError: could not broadcast input array from shape (100,100,3) into shape (100,100)
When I switch the self.im1 to be pg.image instead of pg.ImageView then an RGB image displays in the Dock, but I get a second empty window (which I assume comes from the pg.image()).
Based on this question, ImageView can accept (M, N, 3) RGB data, but I can't seem to get it to display an RGB image in a widget without the second window popping up.

Well, I found one way of doing it that seems reasonable. There is a post that suggests to subclass pg.ImageView in order to auto add a lookup table for the color. So, in the end I have:
class ColorImageView(pg.ImageView):
"""
Wrapper around the ImageView to create a color lookup
table automatically as there seem to be issues with displaying
color images through pg.ImageView.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lut = None
def updateImage(self, autoHistogramRange=True):
super().updateImage(autoHistogramRange)
self.getImageItem().setLookupTable(self.lut)
then the call in my code becomes self.im1 = ColorImageView().
This works for me and seems reasonably simple.

Related

Hover Tool for plots in Pyqtgraph

I want to have data information shown when hovering over a line in pyqtgraph plots, but can't get my sigpointsHovered to emit any signal. Here is a simple example of what i tried to do:
from PySide6.QtWidgets import QMainWindow, QWidget, QApplication, QVBoxLayout
import pyqtgraph as pg
def hovered(self, points, ev):
print("FOOO")
x = [1,2,3,4,5,6,7,8,9]
y = [0,1,2,3,4,5,6,7,8]
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
plot_widget = pg.PlotWidget()
plot_item = plot_widget.getPlotItem()
line = plot_item.plot(x,y)
line.sigPointsHovered.connect(hovered)
layout.addWidget(plot_widget)
window.setLayout(layout)
window.show()
app.exec_()
I have already tried setting "hoverable" = True and read the docs several times, but I honestly have no clue why the sigPointsHovered is not working.
Why sigPointsHovered is not working
In short: There is no way to set "hoverable" argument for PlotDataItem class's ScatterPlotItem right now. Therefore, it is not possible to use sigPointsHovered.
You could see this in the source code of PlotDataItem class's function updateItems.
Workarounds
If you really want something like sigPointsHovered right now, instead of using a PlotWiget, use a ScatterPlotItem and set hoverable = True when you initialize it or when you use setData function. Run python -m pyqtgraph.examples and find the scatter plot example to see some example codes.
However, from your description, I think you actually want to do something when you hover over a "cruve" (instead of points). Currently, PlotCurveItem doesn't implement a hoverEvent, so you may try to make a class that inherits the PlotCurveItem and add a hoverEvent to it.
Let me show you how to do this.
In this example, when the cursor enters the curve, the color changes to blue, and turn back to white when it leave the curve.
import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui
class HoverableCurveItem(pg.PlotCurveItem):
sigCurveHovered = QtCore.Signal(object, object)
sigCurveNotHovered = QtCore.Signal(object, object)
def __init__(self, hoverable=True, *args, **kwargs):
super(HoverableCurveItem, self).__init__(*args, **kwargs)
self.hoverable = hoverable
self.setAcceptHoverEvents(True)
def hoverEvent(self, ev):
if self.hoverable:
if self.mouseShape().contains(ev.pos()):
self.sigCurveHovered.emit(self, ev)
else:
self.sigCurveNotHovered.emit(self, ev)
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.view = pg.GraphicsLayoutWidget()
self.setCentralWidget(self.view)
self.makeplot()
def makeplot(self):
x = [1, 2, 3, 4, 5, 6, 7, 8, 9]
y = [0, 1, 2, 3, 4, 5, 6, 7, 8]
plot = self.view.addPlot()
self.plotitem = HoverableCurveItem(x, y, pen=pg.mkPen('w', width=10))
self.plotitem.setClickable(True, width=10)
self.plotitem.sigCurveHovered.connect(self.hovered)
self.plotitem.sigCurveNotHovered.connect(self.leaveHovered)
plot.addItem(self.plotitem)
def hovered(self):
print("cursor entered curve")
self.plotitem.setPen(pg.mkPen('b', width=10))
def leaveHovered(self):
self.plotitem.setPen(pg.mkPen('w', width=10))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
Edit
Need to setAcceptHoverEvent to Ture
Also, in the updated example, when the cursor enters the curve, the color changes to blue, and turn back to white when it leave the curve.

QBrush transformation ignored by QPrinter/QPdfWriter

I am using PyQt 5.15.2 (Qt 5.15.2) on macOs 10.15.7.
I am trying to produce a PDF from a QGraphicsScene.
I am drawing some paths with a brush that has a pixmap texture, and a scale transformation applied (with brush.setTransform).
When I display the scene in a QGraphicsView I get exactly the desired output.
When I render the same scene to a QPrinter (set to produce a pdf) the texture is applied but ignoring the transformation.
Is this a known limitation? Is there a way around it?
Below is a minimal example showing the problem in a context similar to my use case.
Here I am creating a texture on the fly for illustration purposes.
The rendering artifacts due to antialiasing are irrelevant for my question.
The issue is the discrepancy between the scale (set at .5) of the texture in the rendered brush and the scale (always 1) in the pdf output.
[To generate the output just double click on the view, you'll see a test.pdf in the current path.]
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtPrintSupport import *
class Test(QGraphicsView):
def __init__(self):
QGraphicsView.__init__(self)
# Here I am creating a texture for testing
# it is a 50x50 black square
s = 50
img = QImage(s, s, QImage.Format_ARGB32)
img.fill(Qt.transparent)
for x in range(s):
img.setPixelColor(x,0,Qt.black)
img.setPixelColor(x,s-1,Qt.black)
img.setPixelColor(s-1,x,Qt.black)
img.setPixelColor(0,x,Qt.black)
self.scene = QGraphicsScene()
self.setScene(self.scene)
r = self.scene.addRect(0, 0, 1404, 1872)
pen = QPen()
pen.setWidth(150)
pen.setCapStyle(Qt.RoundCap)
pen.setJoinStyle(Qt.RoundJoin)
b=QBrush(img)
# Here I am transforming the brush so that the texture should be scaled 50% in both height and width
tr = QTransform()
tr.scale(.5,.5)
b.setTransform(tr)
pen.setBrush(b)
# A random path for testing, drawn using the textured brush
path = QPainterPath(QPointF(200, 200))
path.lineTo(300,300)
path.lineTo(300,1000)
path.lineTo(700,700)
self.pathItem = QGraphicsPathItem(path, r)
self.pathItem.setPen(pen)
def resizeEvent(self, event):
self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
def mouseDoubleClickEvent(self, event):
printer = QPrinter(QPrinter.HighResolution)
printer.setOutputFormat(QPrinter.PdfFormat)
# printer.setPageSize(QPrinter.A4)
printer.setOutputFileName("test.pdf")
printer.setPaperSize(QSizeF(1404,1872), QPrinter.Millimeter)
printer.setPageMargins(0,0,0,0, QPrinter.Millimeter)
p=QPainter()
p.begin(printer)
self.scene.render(p)
p.end()
if __name__ == '__main__':
app = QApplication([])
print(QT_VERSION_STR)
print(PYQT_VERSION_STR)
viewer = Test()
viewer.show()
app.exec_()
The desired output is on the left, as correctly displayed by the QGraphicsView.
On the right is the PDF rendering of the same scene, which incorrectly ignores the scaling of the brush.

QSplitter, QWidget resizing, setSizes(), setStretchFactor(), and sizeHint() - how to make it all work together?

I'm struggling with working out how to make all the stuff in the title work together in a certain situation. I'm using PyQt5 here, but feel free to respond with regular C++ Qt as I can translate pretty easily.
I'm attempting to make a UI with the following:
A main form (inherits from QWidget, could just as well use QMainWindow)
The main form should contain a QSplitter oriented vertically containing a QTextEdit at the top and containing a custom class (inheriting from QLabel) to show an image taking up the rest of the space.
The QTextEdit at the top should default to about 3 lines of text high, but this should be resizable to any reasonable extreme via the QSplitter.
The custom class should resize the image to be as big as possible given the available space while maintaining the aspect ratio.
Of course the tricky part is getting everything to resize correctly depending on how big a monitor the user has and how the move the form around. I need this to run on screens as small as about 1,000 px width and perhaps as big as 3,000+ px width.
Here is what I have so far:
# QSplitter3.py
import cv2
import numpy as np
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QSizePolicy, \
QFrame, QTabWidget, QTextEdit, QSplitter
from PyQt5.QtGui import QImage, QPixmap, QPainter
from PyQt5.Qt import Qt
from PyQt5.Qt import QPoint
def main():
app = QApplication([])
screenSize = app.primaryScreen().size()
print('screenSize = ' + str(screenSize.width()) + ', ' + str(screenSize.height()))
mainForm = MainForm(screenSize)
mainForm.show()
app.exec()
# end function
class MainForm(QWidget):
def __init__(self, screenSize):
super().__init__()
# set the title and size of the Qt QWidget window
self.setWindowTitle('Qt Window')
self.setGeometry(screenSize.width() * 0.2, screenSize.height() * 0.2,
screenSize.width() * 0.5 , screenSize.height() * 0.7)
# declare a QTextEdit to show user messages at the top, set the font size, height, and read only property
self.txtUserMessages = QTextEdit()
self.setFontSize(self.txtUserMessages, 14)
self.txtUserMessages.setReadOnly(True)
# make the min height of the text box about 2 lines of text high
self.txtUserMessages.setMinimumHeight(self.getTextEditHeightForNLines(self.txtUserMessages, 2))
# populate the user messages text box with some example text
self.txtUserMessages.append('message 1')
self.txtUserMessages.append('message 2')
self.txtUserMessages.append('message 3')
self.txtUserMessages.append('stuff here')
self.txtUserMessages.append('bla bla bla')
self.txtUserMessages.append('asdasdsadds')
# instantiate the custom ImageWidget class below to show the image
self.imageWidget = ImageWidget()
self.imageWidget.setMargin(0)
self.imageWidget.setContentsMargins(0, 0, 0, 0)
self.imageWidget.setScaledContents(True)
self.imageWidget.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.imageWidget.setAlignment(Qt.AlignCenter)
# declare the splitter, then add the user message text box and tab widget
self.splitter = QSplitter(Qt.Vertical)
self.splitter.addWidget(self.txtUserMessages)
self.splitter.addWidget(self.imageWidget)
defaultTextEditHeight = self.getTextEditHeightForNLines(self.txtUserMessages, 3)
print('defaultTextEditHeight = ' + str(defaultTextEditHeight))
# How can I use defaultTextEditHeight height here, but still allow resizing ??
# I really don't like this line, the 1000 is a guess and check that may only work with one screen size !!!
self.splitter.setSizes([defaultTextEditHeight, 1000])
# Should setStretchFactor be used here ?? This does not seem to work
# self.splitter.setStretchFactor(0, 0)
# self.splitter.setStretchFactor(1, 1)
# What about sizeHint() ?? Should that be used here, and if so, how ??
# set the main form's layout to the QGridLayout
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.splitter)
self.setLayout(self.gridLayout)
# open the two images in OpenCV format
self.openCvImage = cv2.imread('image.jpg')
if self.openCvImage is None:
print('error opening image')
return
# end if
# convert the OpenCV image to QImage
self.qtImage = openCvImageToQImage(self.openCvImage)
# show the QImage on the ImageWidget
self.imageWidget.setPixmap(QPixmap.fromImage(self.qtImage))
# end function
def setFontSize(self, widget, fontSize):
font = widget.font()
font.setPointSize(fontSize)
widget.setFont(font)
# end function
def getTextEditHeightForNLines(self, textEdit, numLines):
fontMetrics = textEdit.fontMetrics()
rowHeight = fontMetrics.lineSpacing()
rowHeight = rowHeight * 1.21
textEditHeight = int(numLines * rowHeight)
return textEditHeight
# end function
# end class
def openCvImageToQImage(openCvImage):
# get the height, width, and num channels of the OpenCV image, then compute the byte value
height, width, numChannels = openCvImage.shape
byteValue = numChannels * width
# make the QImage from the OpenCV image
qtImage = QImage(openCvImage.data, width, height, byteValue, QImage.Format_RGB888).rgbSwapped()
return qtImage
# end function
class ImageWidget(QLabel):
def __init__(self):
super(QLabel, self).__init__()
# end function
def setPixmap(self, pixmap):
self.pixmap = pixmap
# end function
def paintEvent(self, event):
size = self.size()
painter = QPainter(self)
point = QPoint(0, 0)
scaledPixmap = self.pixmap.scaled(size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
point.setX((size.width() - scaledPixmap.width()) / 2)
point.setY((size.height() - scaledPixmap.height()) / 2)
painter.drawPixmap(point, scaledPixmap)
# end function
# end class
if __name__ == '__main__':
main()
Currently I'm testing on a 2560x1440 screen and with the magic 1000 entered it works on this screen size, but I really don't like the hard-coded 1000. I suspect the area of the code where I'm missing something is this part:
# declare the splitter, then add the user message text box and tab widget
self.splitter = QSplitter(Qt.Vertical)
self.splitter.addWidget(self.txtUserMessages)
self.splitter.addWidget(self.imageWidget)
defaultTextEditHeight = self.getTextEditHeightForNLines(self.txtUserMessages, 3)
print('defaultTextEditHeight = ' + str(defaultTextEditHeight))
# How can I use defaultTextEditHeight height here, but still allow resizing ??
# I really don't like this line, the 1000 is a guess and check that may only work with one screen size !!!
self.splitter.setSizes([defaultTextEditHeight, 1000])
# Should setStretchFactor be used here ?? This does not seem to work
# self.splitter.setStretchFactor(0, 0)
# self.splitter.setStretchFactor(1, 1)
# What about sizeHint() ?? Should that be used here, and if so, how ??
# set the main form's layout to the QGridLayout
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.splitter)
With the hard coded 1000 and on this particular screen it works pretty well:
To reiterate (hopefully more clearly) I'm attempting to be able to remove the hard-coded 1000 and command Qt as follows:
Initially make the form take up about 2/3 of the screen
Initially make the text box about 3 lines of text high (min of 2 lines of text high)
Allow the user to use the QSplitter to resize the text box and image at any time and without limit
When the form is resized (or minimized or maximized), resize the text box and image proportionally per how the user had them at the time of the resize
I've tried about every combination of the stuff mentioned in the title and so far in this post but I've not been able to get this functionality, except with the hard-coded 1000 that probably won't work with a different screen size.
How can I remove the hard-coded 1000 and modify the above to achieve the intended functionality?
In my solution I will not take into account the part of opencv since it adds unnecessary complexity.
The solution is to use the setStretchFactor() method, in this case override the sizeHint() method of the QTextEdit to set the initial size and setMinimumHeight() for the minimum height. To show the image I use a QGraphicsView instead of the QLabel since the logic is easier.
from PyQt5 import QtCore, QtGui, QtWidgets
class TextEdit(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
font = self.font()
font.setPointSize(14)
self.setFont(font)
self.setMinimumHeight(self.heightForLines(2))
def heightForLines(self, n):
return (
n * self.fontMetrics().lineSpacing() + 2 * self.document().documentMargin()
)
def showEvent(self, event):
self.verticalScrollBar().setValue(self.verticalScrollBar().minimum())
def sizeHint(self):
s = super().sizeHint()
s.setHeight(self.heightForLines(3))
return s
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setFrameShape(QtWidgets.QFrame.NoFrame)
self.setBackgroundBrush(self.palette().brush(QtGui.QPalette.Window))
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self._pixmap_item = QtWidgets.QGraphicsPixmapItem()
scene.addItem(self._pixmap_item)
def setPixmap(self, pixmap):
self._pixmap_item.setPixmap(pixmap)
def resizeEvent(self, event):
self.fitInView(self._pixmap_item, QtCore.Qt.KeepAspectRatio)
self.centerOn(self._pixmap_item)
super().resizeEvent(event)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.textedit = TextEdit()
for i in range(10):
self.textedit.append("Message {}".format(i))
self.graphicsview = GraphicsView()
self.graphicsview.setPixmap(QtGui.QPixmap("image.jpg"))
splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
splitter.addWidget(self.textedit)
splitter.addWidget(self.graphicsview)
splitter.setStretchFactor(1, 1)
lay = QtWidgets.QGridLayout(self)
lay.addWidget(splitter)
screenSize = QtWidgets.QApplication.primaryScreen().size()
self.setGeometry(
screenSize.width() * 0.2,
screenSize.height() * 0.2,
screenSize.width() * 0.5,
screenSize.height() * 0.7,
)
def main():
app = QtWidgets.QApplication([])
w = Widget()
w.resize(640, 480)
w.show()
app.exec_()
if __name__ == "__main__":
main()

mapToScene requires the view being shown for correct transformations?

Primary issue: the QGraphicsView.mapToScene method returns different answers depending on whether or not the GUI is shown. Why, and can I get around it?
The context is I'm trying to write unit tests but I don't want to actually show the tools for the tests.
The small example below illustrates the behavior. I use a sub-classed view that prints mouse click event positions in scene coordinates with the origin at the lower left (it has a -1 scale vertically) by calling mapToScene. However, mapToScene does not return what I am expecting before the dialog is shown. If I run the main section at the bottom, I get the following output:
Size is (150, 200)
Putting in (50, 125) - This point should return (50.0, 75.0)
Before show(): PyQt5.QtCore.QPointF(84.0, -20.0)
After show() : PyQt5.QtCore.QPointF(50.0, 75.0)
Before show(), there is a consistent offset of 34 pixels in x and 105 in y (and in y the offset moves in reverse as if the scale is not being applied). Those offset seem rather random, I have no idea where they are coming from.
Here is the example code:
import numpy as np
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPointF, QPoint
from PyQt5.QtWidgets import (QDialog, QGraphicsView, QGraphicsScene,
QVBoxLayout, QPushButton, QApplication,
QSizePolicy)
from PyQt5.QtGui import QPixmap, QImage
class MyView(QGraphicsView):
"""View subclass that emits mouse events in the scene coordinates."""
mousedown = pyqtSignal(QPointF)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizePolicy(QSizePolicy.Fixed,
QSizePolicy.Fixed)
# This is the key thing I need
self.scale(1, -1)
def mousePressEvent(self, event):
return self.mousedown.emit(self.mapToScene(event.pos()))
class SimplePicker(QDialog):
def __init__(self, data, parent=None):
super().__init__(parent=parent)
# Get a grayscale image
bdata = ((data - data.min()) / (data.max() - data.min()) * 255).astype(np.uint8)
wid, hgt = bdata.shape
img = QImage(bdata.T.copy(), wid, hgt, wid,
QImage.Format_Indexed8)
# Construct a scene with pixmap
self.scene = QGraphicsScene(0, 0, wid, hgt, self)
self.scene.setSceneRect(0, 0, wid, hgt)
self.px = self.scene.addPixmap(QPixmap.fromImage(img))
# Construct the view and connect mouse clicks
self.view = MyView(self.scene, self)
self.view.mousedown.connect(self.mouse_click)
# End button
self.doneb = QPushButton('Done', self)
self.doneb.clicked.connect(self.accept)
# Layout
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.doneb)
#pyqtSlot(QPointF)
def mouse_click(self, xy):
print((xy.x(), xy.y()))
if __name__ == "__main__":
# Fake data
x, y = np.mgrid[0:4*np.pi:150j, 0:4*np.pi:200j]
z = np.sin(x) * np.sin(y)
qapp = QApplication.instance()
if qapp is None:
qapp = QApplication(['python'])
pick = SimplePicker(z)
print("Size is (150, 200)")
print("Putting in (50, 125) - This point should return (50.0, 75.0)")
p0 = QPoint(50, 125)
print("Before show():", pick.view.mapToScene(p0))
pick.show()
print("After show() :", pick.view.mapToScene(p0))
qapp.exec_()
This example is in PyQt5 on Windows, but PyQt4 on Linux does the same thing.
Upon diving into the C++ Qt source code, this is the Qt definition of mapToScene for a QPoint:
QPointF QGraphicsView::mapToScene(const QPoint &point) const
{
Q_D(const QGraphicsView);
QPointF p = point;
p.rx() += d->horizontalScroll();
p.ry() += d->verticalScroll();
return d->identityMatrix ? p : d->matrix.inverted().map(p);
}
The critical things there are the p.rx() += d->horizontalScroll(); and likewise vertical scroll. A QGraphicsView always contains scroll bars, even if they are always off or not shown. The offsets observed before the widget is shown are from the values of the horizontal and vertical scroll bars upon initialization, which must get modified to match the view/viewport when the widgets are shown and layouts calculated. In order for mapToScene to operate properly, the scroll bars must be set up to match the scene/view.
If I put the following lines put before the call to mapToScene in the example, then I get the appropriate transformation result without the necessity of showing the widget.
pick.view.horizontalScrollBar().setRange(0, 150)
pick.view.verticalScrollBar().setRange(-200, 0)
pick.view.horizontalScrollBar().setValue(0)
pick.view.verticalScrollBar().setValue(-200)
To do this more generally, you can pull some relevant transformations from the view.
# Use the size hint to get shape info
wid, hgt = (pick.view.sizeHint().width()-2,
pick.view.sizeHint().height()-2) # -2 removes padding ... maybe?
# Get the opposing corners through the view transformation
px = pick.view.transform().map(QPoint(wid, 0))
py = pick.view.transform().map(QPoint(0, hgt))
# Set the scroll bars accordingly
pick.view.horizontalScrollBar().setRange(px.y(), px.x())
pick.view.verticalScrollBar().setRange(py.y(), py.x())
pick.view.horizontalScrollBar().setValue(px.y())
pick.view.verticalScrollBar().setValue(py.y())
This is a hack-ish and ugly solution, so while it does work there may be a more elegant way to handle this.
have you tried implementing your own qgraphicsview and overriding your resizeEvent? When you mess around with mapTo"something" you gotta take care of your resizeEvents, have a look in this piece of code I've took from yours and modified a bit ><
from PyQt5.QtCore import QRectF
from PyQt5.QtWidgets import (QGraphicsScene, QGraphicsView, QVBoxLayout,
QApplication, QFrame, QSizePolicy)
from PyQt5.QtCore import QPoint
class GraphicsView(QGraphicsView):
def __init__(self):
super(GraphicsView, self).__init__()
# Scene and view
scene = QGraphicsScene(0, 0, 150, 200,)
scene.setSceneRect(0, 0, 150, 200)
def resizeEvent(self, QResizeEvent):
self.setSceneRect(QRectF(self.viewport().rect()))
qapp = QApplication(['python'])
# Just something to be a parent
view = GraphicsView()
# Short layout
# Make a test point
p0 = QPoint(50, 125)
# Pass in the test point before and after
print("Passing in point: ", p0)
print("Received point before show:", view.mapToScene(p0))
view.show()
print("Received point after show:", view.mapToScene(p0))
qapp.exec_()
Is that the behavior you wanted? ")

How to display PDF with python-poppler-qt4?

I have downloaded and installed python-poppler-qt4 and I am now trying out a simple Qt application to display a PDF page. I've followed what I've been able to get from the web, i.e. convert the PDF to a QImage, then to a QPixMap, but it doesn't work (all I get is a small window with no visible content).
I may have failed at some point (QImage.width() returns the width I have input, QPixMap.width() returns 0).
Here is the code:
#!/usr/bin/env python
import sys
from PyQt4 import QtGui, QtCore
import popplerqt4
class Application(QtGui.QApplication):
def __init__(self):
QtGui.QApplication.__init__(self, sys.argv)
self.main = MainWindow()
self.main.show()
class MainWindow(QtGui.QFrame):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.layout = QtGui.QVBoxLayout()
self.doc = popplerqt4.Poppler.Document.load('/home/benjamin/test.pdf')
self.page = self.doc.page(1)
# here below i entered almost random dpi, position and size, just to test really
self.image = self.page.renderToImage(150, 150, 0, 0, 210, 297)
self.pixmap = QtGui.QPixmap()
self.pixmap.fromImage(self.image)
self.label = QtGui.QLabel(self)
self.label.setPixmap(self.pixmap)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
if __name__ == "__main__":
application = Application()
sys.exit(application.exec_())
Where does it go wrong here? Thanks.
I'm not familiar with python, so this might not apply directly, but QPixmap::fromImage is a static function that returns a QPixmap. So your code should read something like:
self.pixmap = QtGui.QPixmap.fromImage(self.image)
In other words, self.pixmap.fromImage doesn't change self.pixmap, it returns a new pixmap generated from the image you give it as a parameter.
yes i know that the question has an answer.
but i faced an error when i do the same thing in PyQt5 to show a PDF file as an image.
and i found the solution of my problem.
i posted this answer to help who faced the same problem.
if you want to show a pdf file in your PyQt5 program you have 2 choices
1 - the first one is to use web engine (but it takes a lot of resources from the ram)
2 - the second it to convert the pdf into an image and show it on label
i chose the second choice
and this is my code to show a pdf file as an image and to solve the problem :
from PyQt5 import QtWidgets,QtCore,QtGui
import pypdfium2 as pdfium
the_file = "My_PDF_File.pdf"
application = QtWidgets.QApplication([])
window = QtWidgets.QWidget()
window.resize(700,600)
window_layout = QtWidgets.QGridLayout()
label_to_display_the_page = QtWidgets.QLabel()
label_to_display_the_page.setAlignment(QtCore.Qt.AlignCenter)
label_to_display_the_page_geometry = label_to_display_the_page.geometry()
pdf = pdfium.PdfDocument(the_file)
page = pdf.get_page(1)
pil_image = page.render_topil(scale=1,rotation=0,crop=(0, 0, 0, 0),greyscale=False,optimise_mode=pdfium.OptimiseMode.NONE)
image = pil_image.toqimage()
label_pixmap = QtGui.QPixmap.fromImage(image)
size = QtCore.QSize(label_to_display_the_page_geometry.width()-50,label_to_display_the_page_geometry.height()-50)
label_to_display_the_page.setPixmap(label_pixmap.scaled(size,QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
window_layout.addWidget(label_to_display_the_page)
window.setLayout(window_layout)
window.show()
application.exec()

Categories

Resources