Zooming in on mouse position with QGraphicsView Scene

Solution for Zooming in on mouse position with QGraphicsView Scene
is Given Below:

I would like to enable the user to zoom in on an image in QGraphicView’s Scene, allowing him to zoom in on the mouse position.

I have tried to implent this zoom in several ways, yet unfortunately I haven’t been able to get it work as needed.

If implementing this via QGraphicsPixmapItem’s setScale and setTransformOriginpoint, the solution works fine and dandy as long as the user does not move the position of the mouse on the zoomed in image, if he does so – the displayed scaled image is incorrect. Please find a minimum reproducible example below:

import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QGraphicsView,QGraphicsPixmapItem,QGraphicsScene,QPushButton, QWidget, QVBoxLayout, QShortcut
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import QKeySequence,QShortcutEvent, QCursor
global qpixmap
global lvlofzoom
lvlofzoom =1
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setFixedSize(1200, 800)

        self.ImageDisplay = QGraphicsView()


        self.qwidget = QWidget()

        self.Zoom_inBtn = QPushButton("Zoom in (CTRL + F)")
        self.Zoom_outBtn = QPushButton("Zoom out (CTRL + G)")
        self.setimgBtn = QPushButton("Load Image (CTRL+P)")


        self.Zoom_inBtn.clicked.connect(lambda: self.zoomin())
        self.Zoom_outBtn.clicked.connect(lambda: self.zoomout())
        self.setimgBtn.clicked.connect(lambda:self.loadimg())
        self.Zoom_inBtn.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F))
        self.Zoom_outBtn.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_G))
        self.setimgBtn.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_P))

        self.vlayout = QVBoxLayout()
        self.vlayout.addWidget(self.ImageDisplay)
        self.vlayout.addWidget(self.setimgBtn)
        self.vlayout.addWidget(self.Zoom_outBtn)
        self.vlayout.addWidget(self.Zoom_inBtn)
        self.qwidget.setLayout(self.vlayout)


        self.setCentralWidget(self.qwidget)


    def zoomin(self):
        print("zoom in")
        global lvlofzoom
        lvlofzoom = lvlofzoom *1.25
        mousepos = self.ImageDisplay.mapFromGlobal(QCursor.pos())
        mousepos = self.ImageDisplay.mapToScene(mousepos)
        #mousepos = self.ImageDisplay.mapFromScene(QCursor.pos())

        global qpixmap
        pixmap_to_show = QGraphicsPixmapItem(qpixmap)
        pixmap_to_show.setTransformationMode(Qt.SmoothTransformation)
        pixmap_to_show.setTransformOriginPoint(mousepos)
        pixmap_to_show.setScale(lvlofzoom)
        pixmap_scene = QGraphicsScene(self.ImageDisplay)
        pixmap_scene.addItem(pixmap_to_show)
        #   pixmap_scene.
        self.ImageDisplay.setScene(pixmap_scene)

    def zoomout(self):
        print("zoom out")
        global lvlofzoom
        lvlofzoom = lvlofzoom * 0.8
        mousepos = self.ImageDisplay.mapFromGlobal(QCursor.pos())
        mousepos = self.ImageDisplay.mapToScene(mousepos)

        #mousepos = self.ImageDisplay.mapFromScene(QCursor.pos())
        global qpixmap
        pixmap_to_show = QGraphicsPixmapItem(qpixmap)
        pixmap_to_show.setTransformationMode(Qt.SmoothTransformation)
        pixmap_to_show.setTransformOriginPoint(mousepos)
        pixmap_to_show.setScale(lvlofzoom)
        pixmap_scene = QGraphicsScene(self.ImageDisplay)
        pixmap_scene.addItem(pixmap_to_show)
        #   pixmap_scene.
        self.ImageDisplay.setScene(pixmap_scene)

    def loadimg(self):
        global qpixmap
        print("load img")
        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(None, "Select Image", "",
                                                            "Image Files (*.png *.jpg *jpeg *.bmp)")

        if fileName:
            qpixmap = QtGui.QPixmap(fileName)
            qpixmap = qpixmap.scaledToWidth(self.ImageDisplay.width())
            qgraphicsitem = QGraphicsPixmapItem(qpixmap)
            qscene = QGraphicsScene(self.ImageDisplay)
            qscene.addItem(qgraphicsitem)
            self.ImageDisplay.setScene(qscene)
            self.ImageDisplay.setMouseTracking(True)
            #   self.ui.label_Pano.scene().
            self.ImageDisplay.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
            self.ImageDisplay.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
            self.ImageDisplay.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.ImageDisplay.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

I understand that this is due to the fact that I am taking mouse position from global, and scaling the full image with the mouse’s global position as the transformation originpoint. However, even after mapping it to scene, it does not work as needed.

Ideally, I would like to solve the issue in a different manner, implementing the zoom function via scaling the pixmap itself. This is the preferred option, as it requires me to rewrite the least amount code (part of a larger “pet project”, and other functions are interacting with zoom – ex: drawing on the image, whilst zoomed). 
Please find the reimplantation of zoomin & loadimg below (the rest stays the same, except zoomout which is partially irrelevant):

 def zoomin(self):
    print("zoom in")
    global lvlofzoom
    lvlofzoom = lvlofzoom *1.25
    global qpixmap
    mousepos = self.ImageDisplay.mapFromGlobal(QCursor.pos())

    pixmap = qpixmap.scaledToWidth(int(self.ImageDisplay.width() * lvlofzoom))
    pixmap_to_show = QGraphicsPixmapItem(pixmap)
    pixmap_scene = QGraphicsScene(self.ImageDisplay)
    pixmap_scene.addItem(pixmap_to_show)
    self.ImageDisplay.setScene(pixmap_scene)


    
def loadimg(self):
    global qpixmap
    print("load img")
    fileName, _ = QtWidgets.QFileDialog.getOpenFileName(None, "Select Image", "",
                                                        "Image Files (*.png *.jpg *jpeg *.bmp)")

    if fileName:
        qpixmap = QtGui.QPixmap(fileName)
        qpixmap2 = qpixmap.scaledToWidth(self.ImageDisplay.width())
        qgraphicsitem = QGraphicsPixmapItem(qpixmap2)
        qscene = QGraphicsScene(self.ImageDisplay)
        qscene.addItem(qgraphicsitem)
        self.ImageDisplay.setScene(qscene)
        self.ImageDisplay.setMouseTracking(True)
        self.ImageDisplay.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.ImageDisplay.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.ImageDisplay.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)           
        self.ImageDisplay.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

I would ideally like to add a mouse anchor to the scaledtoWidth and multiply the self.ImageDisplay.width with the lvlofzoom OR scale ImageDisplay, with Transformation mode of Qt.SmoothTransformation.

Solved by the use of setSceneRect(), as guided by another answer by eyllanesc (PyQt5: I can’t understand QGraphicsScene’s setSceneRect(x, y, w, h))

def zoomin(self):
    print("zoom in")
    global lvlofzoom
    lvlofzoom = lvlofzoom *1.25
    global qpixmap
    mousepos = self.ImageDisplay.mapFromGlobal(QCursor.pos())

    pixmap = qpixmap.scaledToWidth(int(self.ImageDisplay.width() * lvlofzoom))
    pixmap_to_show = QGraphicsPixmapItem(pixmap)
    pixmap_scene = QGraphicsScene(self.ImageDisplay)
    pixmap_scene.addItem(pixmap_to_show)
    p = self.ImageDisplay.mapToScene(QCursor.pos())#p0)
    r = self.ImageDisplay.sceneRect() 
                        r.moveCenter(p)
    self.ImageDisplay.setScene(pixmap_scene)
    self.ImageDisplay.setSceneRect(r)