简体   繁体   中英

Pyqt5 image coordinates

I display images with Qlabel.I need image coordinates/pixel coordinates but, I use mouseclickevent its show me only Qlabel coordinates.

for examples my image is 800*753 and my Qlabel geometry is (701,451).I reads coordinates in (701,451) but I need image coordinates in (800*753)

def resimac(self):



    filename= QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')

    self.image=QtGui.QImage(filename[0])
    self.pixmap=QtGui.QPixmap.fromImage(self.image)
    self.resim1.setPixmap(self.pixmap)


    self.resim1.mousePressEvent=self.getPixel




def getPixel(self, event):


    x = event.pos().x()
    y = event.pos().y()


    print("X=",x," y= ",y)

Since you didn't provide a minimal, reproducible example , I'm going to assume that you're probably setting the scaledContents property, but that could also be not true (in case you set a maximum or fixed size for the label).

There are some other serious issues about your answer, I'll address them at the end of this answer.

The point has to be mapped to the pixmap coordinates

When setting a pixmap to a QLabel, Qt automatically resizes the label to its contents.
Well, it does it unless the label has some size constrains: a maximum/fixed size that is smaller than the pixmap, and/or the QLabel has the scaledContents property set to True as written above. Note that this also happens if any of its ancestors has some size constraints (for example, the main window has a maximum size, or it's maximized to a screen smaller than the space the window needs).
In any of those cases, the mousePressEvent will obviously give you the coordinates based on the widget, not on the pixmap.

First of all, even if it doesn't seem to be that important, you'll have to consider that every widget can have some contents margins : the widget will still receive events that happen inside the area of those margins , even if they are outside its actual contents, so you'll have to consider that aspect, and ensure that the event happens within the real geometry of the widget contents (in this case, the pixmap). If that's true, you'll have to translate the event position to that rectangle to get its position according to the pixmap.

Then, if the scaledContents property is true, the image will be scaled to the current available size of the label (which also means that its aspect ratio will not be maintained), so you'll need to scale the position.
This is just a matter of math: compute the proportion between the image size and the (contents of the) label, then multiply the value using that proportion.

    # click on the horizontal center of the widget
    mouseX = 100

    pixmapWidth = 400
    widgetWidth = 200
    xRatio = pixmapWidth / widgetWidth
    # xRatio = 2.0

    pixmapX = mouseX * xRatio
    # the resulting "x" is the horizontal center of the pixmap
    # pixmapX = 200

On the other hand, if the contents are not scaled you'll have to consider the QLabel alignment property; it is usually aligned on the left and vertically centered, but that depends on the OS, the style currently in use and the localization (consider right-to-left writing languages). This means that if the image is smaller than the available size, there will be some empty space within its margins, and you'll have to be aware of that.

In the following example I'm trying to take care about all of that (I'd have to be honest, I'm not 100% sure, as there might be some 1-pixel tolerance due to various reasons, most regarding integer-based coordinates and DPI awareness). Note that instead of overwriting mousePressEvent as you did, I'm using an event filter , I'll explain the reason for it afterwards.

from PyQt5 import QtCore, QtGui, QtWidgets

class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout(self)

        self.getImageButton = QtWidgets.QPushButton('Select')
        layout.addWidget(self.getImageButton)
        self.getImageButton.clicked.connect(self.resimac)

        self.resim1 = QtWidgets.QLabel()
        layout.addWidget(self.resim1)
        self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
        # I'm assuming the following...
        self.resim1.setScaledContents(True)
        self.resim1.setFixedSize(701,451)

        # install an event filter to "capture" mouse events (amongst others)
        self.resim1.installEventFilter(self)

    def resimac(self):
        filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
        if not filename:
            return
        self.resim1.setPixmap(QtGui.QPixmap(filename))

    def eventFilter(self, source, event):
        # if the source is our QLabel, it has a valid pixmap, and the event is
        # a left click, proceed in trying to get the event position
        if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and 
            event.type() == QtCore.QEvent.MouseButtonPress and
            event.button() == QtCore.Qt.LeftButton):
                self.getClickedPosition(event.pos())
        return super().eventFilter(source, event)

    def getClickedPosition(self, pos):
        # consider the widget contents margins
        contentsRect = QtCore.QRectF(self.resim1.contentsRect())
        if pos not in contentsRect:
            # outside widget margins, ignore!
            return

        # adjust the position to the contents margins
        pos -= contentsRect.topLeft()

        pixmapRect = self.resim1.pixmap().rect()
        if self.resim1.hasScaledContents():
            x = pos.x() * pixmapRect.width() / contentsRect.width()
            y = pos.y() * pixmapRect.height() / contentsRect.height()
            pos = QtCore.QPoint(x, y)
        else:
            align = self.resim1.alignment()
            # for historical reasons, QRect (which is based on integer values),
            # returns right() as (left+width-1) and bottom as (top+height-1),
            # and so their opposite functions set/moveRight and set/moveBottom
            # take that into consideration; using a QRectF can prevent that; see:
            # https://doc.qt.io/qt-5/qrect.html#right
            # https://doc.qt.io/qt-5/qrect.html#bottom
            pixmapRect = QtCore.QRectF(pixmapRect)

            # the pixmap is not left aligned, align it correctly
            if align & QtCore.Qt.AlignRight:
                pixmapRect.moveRight(contentsRect.x() + contentsRect.width())
            elif align & QtCore.Qt.AlignHCenter:
                pixmapRect.moveLeft(contentsRect.center().x() - pixmapRect.width() / 2)
            # the pixmap is not top aligned (note that the default for QLabel is
            # Qt.AlignVCenter, the vertical center)
            if align & QtCore.Qt.AlignBottom:
                pixmapRect.moveBottom(contentsRect.y() + contentsRect.height())
            elif align & QtCore.Qt.AlignVCenter:
                pixmapRect.moveTop(contentsRect.center().y() - pixmapRect.height() / 2)

            if not pos in pixmapRect:
                # outside image margins, ignore!
                return
            # translate coordinates to the image position and convert it back to
            # a QPoint, which is integer based
            pos = (pos - pixmapRect.topLeft()).toPoint()

        print('X={}, Y={}'.format(pos.x(), pos.y()))


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

Now. A couple of suggestions.

Don't overwrite existing child object methods with [other] object's instance attributes

There are various reasons for which this is not a good idea, and, while dealing with Qt, the most important of them is that Qt uses function caching for virtual functions ; this means that as soon as a virtual is called the first time, that function will always be called in the future. While your approach could work in simple cases (especially if the overwriting happens within the parent's __init__ ), it's usually prone to unexpected behavior that's difficult to debug if you're not very careful.

And that's exactly your case: I suppose that resimac is not called upon parent instantiation and until after some other event (possibly a clicked button) happens. But if the user, for some reason, clicks on the label before a new pixmap is loaded, your supposedly overwritten method will never get called: at that time, you've not overwritten it yet, so the user clicks the label, Qt calls the QLabel's base class mousePressEvent implementation, and then that method will always be called from that point on, no matter if you try to overwrite it.

To work around that, you have at least 3 options:

  1. use an event filter (as the example above); an event filter is something that "captures" events of a widgets and allows you to observe (and interact) with it; you can also decide to propagate that event to the widget's parent or not (that's mostly the case of key/mouse events: if a widget isn't "interested" about one of those events, it "tells" its parent to care about it); this is the simplest method, but it can become hard to implement and debug for complex cases;
  2. subclass the widget and manually add it to your GUI within your code;
  3. subclass it and "promote" the widget if you're using Qt's Designer;

You don't need to use a QImage for a QLabel.

This is not that an issue, it's just a suggestion: QPixmap already uses ( sort of ) fromImage within its C++ code when constructing it with a path as an argument, so there's no need for that.

Always, always provide usable, Minimal Reproducible Example code.

See:

It could take time, even hours to get an "MRE", but it's worth it: there'll always somebody that could answer you, but doesn't want to or couldn't dig into your code for various reasons (mostly because it's incomplete, vague, inusable, lacking context, or even too expanded). If, for any reason, there'll be just that one user, you'll be losing your occasion to solve your problem. Be patient, carefully prepare your questions, and you'll probably get plenty of interactions and useful insight from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM