Scaled, centered image

I’m working on a face recognition app. I have a requirement to display a scaled image as large as possible within the client area of the parent widget. I also need to be able to convert between image coordinates and screen coordinates in order to outline the detected faces and detect mouse clicks on the faces displayed.

I was not able to figure out how to meet these various requirements using Qt6 so I reused the following code from another project in .net. Class ScalingImage contains the original image, scales it on demand, paints the scaled image using a supplied QPainter and calculates the transformations between screen and image coordinates.

It works as well as I need it to but I wonder how it might be implemented using Qt.

Any comments gratefully received. Thanks in advance.

from deepface import DeepFace
from PyQt6.QtCore import QPoint, QRect, Qt, pyqtSignal
from PyQt6.QtGui import QColor, QPainter, QPen, QPixmap
from PyQt6.QtWidgets import QApplication, QLabel, QMessageBox, QWidget
import sys

class ImageFile:

def __init__(self, path):
    self._path = path
    self._faces = [
        Face(i, embedding['facial_area']['x'], embedding['facial_area']['y'], embedding['facial_area']['w'], embedding['facial_area']['h'], embedding['face_confidence']) 
        for i, embedding in enumerate(DeepFace.represent(img_path=self._path, model_name='Facenet', detector_backend='opencv', enforce_detection=False))
        if embedding['face_confidence'] >= 0.90
    ]
    
def getPath(self):
    return self._path
    
def getFaces(self):
    return self._faces 

class Face:

def __init__(self, id, x, y, w, h, confidence):
    self._id = id
    self._x = x
    self._y = y
    self._w = w
    self._h = h
    self._confidence = confidence
    
def getBoundingRect(self):
    return QRect(self._x, self._y, self._w, self._h)

class ScalingImage:

def __init__(self, image):
    self._original_image = image

def resize(self, screenSize):
    
    # Calculate scaling.
    scaleX = screenSize.width() / self._original_image.width()
    scaleY = screenSize.height() / self._original_image.height()
    self._scale = scaleX if (scaleX < scaleY) else scaleY
    scaledImageWidth = int(self._scale * self._original_image.width())
    scaledImageHeight = int(self._scale * self._original_image.height())

    self._scaledImage = self._original_image.scaled(scaledImageWidth, scaledImageHeight, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio, transformMode=Qt.TransformationMode.SmoothTransformation)

    # Calculate offsets.
    self._xofs = int((screenSize.width() - scaledImageWidth) / 2) if screenSize.width() > scaledImageWidth else 0
    self._yofs = int((screenSize.height() - scaledImageHeight) / 2) if screenSize.height() > scaledImageHeight else 0

def drawPixmap(self, painter: QPainter):
    painter.drawPixmap(self._xofs, self._yofs, self._scaledImage)

def screenToImage(self, p):
    return QPoint(int((p.x() - self._xofs) / self._scale), int((p.y() - self._yofs) / self._scale))

def imageToScreen(self, p):
    return QPoint(int((p.x() * self._scale) + self._xofs), int((p.y() * self._scale) + self._yofs))

def imageToScreen(self, r):
    return QRect(int((r.x() * self._scale) + self._xofs), int((r.y() * self._scale) + self._yofs), int(r.width() * self._scale), int(r.height() * self._scale))

class ScalingImageWidget(QWidget):

FaceClicked = pyqtSignal(Face)

def __init__(self):
    super().__init__()
    self._scalingImage = None

def setImageFile(self, imgf):
    self._imgf = imgf
    self._scalingImage = ScalingImage(QPixmap(self._imgf.getPath()))
    self.update()

def resizeEvent(self, e):
    super().resizeEvent(e)
    if self._scalingImage is not None:
        self._scalingImage.resize(e.size())

def paintEvent(self, e):

    super().paintEvent(e)

    if self._scalingImage is not None:

        painter = QPainter(self)
    
        # Draw centered, scaled image.
        self._scalingImage.drawPixmap(painter)

        # Outline faces
        whitePen = QPen()
        whitePen.setColor(QColor('white'))
        whitePen.setWidth(2)
        painter.setPen(whitePen)

        for face in self._imgf.getFaces():
            # need to convert image coordinates to screen coordinates to draw bounding rectangles.
            boundingRect = self._scalingImage.imageToScreen(face.getBoundingRect())
            painter.drawRect(boundingRect)
            
        painter.end()

def mousePressEvent(self, e):
    if self._scalingImage is not None:
        # need to convert screen coordinates to image coordinates to detect which face (if any) was clicked.
        pos = self._scalingImage.screenToImage(e.pos())
        for face in self._imgf.getFaces():
            if face.getBoundingRect().contains(pos.x(), pos.y()):
                QMessageBox.information(self, None, f'Face #{face._id} clicked.')
                break

imgf_path = r’D:\FaceDb\Friends\images\friends (1).jpg’
imgf = ImageFile(imgf_path)

app = QApplication(sys.argv)

label = ScalingImageWidget()
label.setImageFile(imgf)
label.show()

app.exec()