|
| 1 | +""" |
| 2 | +Custom image viewer widget with zoom and pan capabilities |
| 3 | +""" |
| 4 | +from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem |
| 5 | +from PySide6.QtCore import Qt, Signal, QPointF |
| 6 | +from PySide6.QtGui import QPixmap, QWheelEvent, QMouseEvent |
| 7 | +import numpy as np |
| 8 | +from utils import numpy_to_qpixmap |
| 9 | + |
| 10 | +class ImageViewer(QGraphicsView): |
| 11 | + """Custom image viewer with zoom and pan""" |
| 12 | + |
| 13 | + imageClicked = Signal(QPointF) |
| 14 | + |
| 15 | + def __init__(self, parent=None): |
| 16 | + super().__init__(parent) |
| 17 | + |
| 18 | + self.scene = QGraphicsScene(self) |
| 19 | + self.setScene(self.scene) |
| 20 | + |
| 21 | + self.pixmap_item = None |
| 22 | + self.zoom_factor = 1.0 |
| 23 | + self.min_zoom = 0.1 |
| 24 | + self.max_zoom = 10.0 |
| 25 | + |
| 26 | + # Enable dragging |
| 27 | + self.setDragMode(QGraphicsView.ScrollHandDrag) |
| 28 | + |
| 29 | + # Set rendering hints |
| 30 | + self.setRenderHint(self.renderHints()) |
| 31 | + |
| 32 | + # Disable scrollbars (we'll use them when needed) |
| 33 | + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) |
| 34 | + self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) |
| 35 | + |
| 36 | + # Background |
| 37 | + self.setBackgroundBrush(Qt.darkGray) |
| 38 | + |
| 39 | + def set_image(self, image: np.ndarray): |
| 40 | + """ |
| 41 | + Set image to display |
| 42 | + |
| 43 | + Args: |
| 44 | + image: Numpy array image |
| 45 | + """ |
| 46 | + # Clear scene |
| 47 | + self.scene.clear() |
| 48 | + |
| 49 | + # Convert to pixmap |
| 50 | + pixmap = numpy_to_qpixmap(image) |
| 51 | + |
| 52 | + # Add to scene |
| 53 | + self.pixmap_item = QGraphicsPixmapItem(pixmap) |
| 54 | + self.scene.addItem(self.pixmap_item) |
| 55 | + |
| 56 | + # Reset zoom |
| 57 | + self.zoom_factor = 1.0 |
| 58 | + self.fit_in_view() |
| 59 | + |
| 60 | + def set_pixmap(self, pixmap: QPixmap): |
| 61 | + """Set QPixmap to display""" |
| 62 | + self.scene.clear() |
| 63 | + self.pixmap_item = QGraphicsPixmapItem(pixmap) |
| 64 | + self.scene.addItem(self.pixmap_item) |
| 65 | + self.zoom_factor = 1.0 |
| 66 | + self.fit_in_view() |
| 67 | + |
| 68 | + def fit_in_view(self): |
| 69 | + """Fit image in view""" |
| 70 | + if self.pixmap_item: |
| 71 | + self.fitInView(self.pixmap_item, Qt.KeepAspectRatio) |
| 72 | + self.zoom_factor = 1.0 |
| 73 | + |
| 74 | + def zoom_in(self): |
| 75 | + """Zoom in""" |
| 76 | + self.scale_view(1.25) |
| 77 | + |
| 78 | + def zoom_out(self): |
| 79 | + """Zoom out""" |
| 80 | + self.scale_view(0.8) |
| 81 | + |
| 82 | + def scale_view(self, factor: float): |
| 83 | + """Scale view by factor""" |
| 84 | + new_zoom = self.zoom_factor * factor |
| 85 | + |
| 86 | + if new_zoom < self.min_zoom or new_zoom > self.max_zoom: |
| 87 | + return |
| 88 | + |
| 89 | + self.scale(factor, factor) |
| 90 | + self.zoom_factor = new_zoom |
| 91 | + |
| 92 | + def wheelEvent(self, event: QWheelEvent): |
| 93 | + """Handle mouse wheel for zooming""" |
| 94 | + if event.angleDelta().y() > 0: |
| 95 | + self.scale_view(1.15) |
| 96 | + else: |
| 97 | + self.scale_view(0.85) |
| 98 | + |
| 99 | + def mousePressEvent(self, event: QMouseEvent): |
| 100 | + """Handle mouse press""" |
| 101 | + if event.button() == Qt.LeftButton: |
| 102 | + scene_pos = self.mapToScene(event.pos()) |
| 103 | + self.imageClicked.emit(scene_pos) |
| 104 | + super().mousePressEvent(event) |
| 105 | + |
| 106 | + def clear(self): |
| 107 | + """Clear the viewer""" |
| 108 | + self.scene.clear() |
| 109 | + self.pixmap_item = None |
| 110 | + self.zoom_factor = 1.0 |
0 commit comments