Source code for canns.pipeline.asa_gui.views.widgets.image_viewer

"""Image viewer widget with zoom and pan."""

from __future__ import annotations

from pathlib import Path

from PySide6.QtCore import QPointF, QRectF, Qt
from PySide6.QtGui import QColor, QPainter, QPixmap
from PySide6.QtWidgets import QGraphicsPixmapItem, QGraphicsScene, QGraphicsTextItem, QGraphicsView


[docs] class ImageViewer(QGraphicsView): """Image viewer with fit-to-view, zoom (wheel), and pan (drag).""" def __init__(self, parent=None) -> None: super().__init__(parent) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setMinimumHeight(200) self._scene = QGraphicsScene(self) self.setScene(self._scene) self._pixmap_item = QGraphicsPixmapItem() self._scene.addItem(self._pixmap_item) self._placeholder = QGraphicsTextItem("No image") self._placeholder.setDefaultTextColor(QColor("#888")) self._scene.addItem(self._placeholder) self._has_image = False self._auto_fit = True
[docs] def set_image(self, path: str | Path | None) -> None: if path is None: self._set_placeholder("No image") return path = Path(path) if not path.exists(): self._set_placeholder(f"Missing: {path}") return pixmap = QPixmap(str(path)) if pixmap.isNull(): self._set_placeholder("Failed to load image") return self._has_image = True self._auto_fit = True self._pixmap_item.setPixmap(pixmap) self._pixmap_item.setVisible(True) self._placeholder.setVisible(False) self._scene.setSceneRect(QRectF(pixmap.rect())) self.resetTransform() self.fitInView(self._pixmap_item, Qt.KeepAspectRatio)
[docs] def wheelEvent(self, event) -> None: # noqa: N802 - Qt naming if not self._has_image: return delta = event.angleDelta().y() if delta == 0: return factor = 1.2 if delta > 0 else 1 / 1.2 self._auto_fit = False self.scale(factor, factor)
[docs] def mouseDoubleClickEvent(self, event) -> None: # noqa: N802 - Qt naming if self._has_image: self._auto_fit = True self.resetTransform() self.fitInView(self._pixmap_item, Qt.KeepAspectRatio) super().mouseDoubleClickEvent(event)
[docs] def resizeEvent(self, event) -> None: # noqa: N802 - Qt naming super().resizeEvent(event) if self._has_image and self._auto_fit: self.fitInView(self._pixmap_item, Qt.KeepAspectRatio) self._center_placeholder()
def _set_placeholder(self, text: str) -> None: self._has_image = False self._auto_fit = True self._pixmap_item.setPixmap(QPixmap()) self._pixmap_item.setVisible(False) self._placeholder.setPlainText(text) self._placeholder.setVisible(True) self._scene.setSceneRect(QRectF(0, 0, self.viewport().width(), self.viewport().height())) self._center_placeholder() def _center_placeholder(self) -> None: if not self._placeholder.isVisible(): return view_rect = self.viewport().rect() center_scene = self.mapToScene(view_rect.center()) br = self._placeholder.boundingRect() self._placeholder.setPos( QPointF(center_scene.x() - br.width() / 2, center_scene.y() - br.height() / 2) )