/
Nemo_499
/
DecodingX-rayImages
Обзор
Документация
Войти
/
Nemo_499
/
DecodingX-rayImages
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CImageLabel.py
308 строк
12 KB
Вдовин Денис
vgg16
19 май 2026, 18:42
19 май 2026, 18:42
73ddac7
Код
Авторство
О чём код?
from PyQt6.QtWidgets import ( QLabel, QScrollArea ) from PyQt6.QtCore import ( Qt, QPoint, QPointF, QRectF, QRect, pyqtSignal ) from PyQt6.QtGui import ( QPixmap, QPainter, QPen, QBrush, QColor, QWheelEvent, QMouseEvent, ) class ImageLabel(QLabel): """Класс для отображения изображений с bounding boxes, поддержкой масштабирования""" # Сигнал для обновления скролла в родительском QScrollArea scaleChanged = pyqtSignal(float) def __init__(self, parent=None): super().__init__(parent) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setMouseTracking(True) # Данные изображения self._pixmap_original = None self._scale = 1.0 self._min_scale = 0.1 self._max_scale = 10.0 # Данные для bounding boxes self.bboxes = [] self.highlight_index = None # Для отслеживания позиции мыши при масштабировании self._last_mouse_pos = QPoint() # Для панорамирования self._is_panning = False self._pan_start_pos = QPoint() # --- Базовые методы --- def set_image(self, pixmap): """Установка нового изображения""" if pixmap and not pixmap.isNull(): self._pixmap_original = pixmap self._scale = 1.0 self._update_pixmap() else: self.clear() self._pixmap_original = None def set_bboxes(self, bboxes): """Установка bounding boxes""" self.bboxes = bboxes self.highlight_index = None self._update_pixmap() def highlight_bbox(self, index): """Подсветка выделеного bounding box""" if index != self.highlight_index: self.highlight_index = index self._update_pixmap() # --- Обработка событий мыши --- def wheelEvent(self, event: QWheelEvent): """Обработка прокрутки колесика мыши для масштабирования""" if self._pixmap_original is None: return # Получение позиции курсора мыши относительно виджета mouse_pos = event.position().toPoint() # Сохранение текущей позиции курсора в координатах изображения old_pos_in_image = self._widget_to_image_coords(mouse_pos) # Изменение масштаба delta = event.angleDelta().y() if delta > 0: # Увеличение new_scale = self._scale * 1.2 else: # Уменьшение new_scale = self._scale * 0.8 # Ограничение масштаба new_scale = max(self._min_scale, min(new_scale, self._max_scale)) self._scale = new_scale self._update_pixmap() # Вычисление смещения для сохранения позиции курсора if old_pos_in_image: # Находим, где эта точка изображения находится сейчас на виджете new_mouse_pos = self._image_to_widget_coords(old_pos_in_image) if new_mouse_pos: dx = new_mouse_pos.x() - mouse_pos.x() dy = new_mouse_pos.y() - mouse_pos.y() # Если родитель - QScrollArea, корректируем скролл scroll_area = self._find_scroll_area() if scroll_area: scroll_area.horizontalScrollBar().setValue( scroll_area.horizontalScrollBar().value() + int(dx) ) scroll_area.verticalScrollBar().setValue( scroll_area.verticalScrollBar().value() + int(dy) ) event.accept() def mousePressEvent(self, event: QMouseEvent): """Обработка нажатия кнопки мыши для панорамирования""" if event.button() == Qt.MouseButton.LeftButton: self._is_panning = True self._pan_start_pos = event.pos() self.setCursor(Qt.CursorShape.ClosedHandCursor) super().mousePressEvent(event) def mouseMoveEvent(self, event: QMouseEvent): """Обработка движения мыши""" if self._is_panning: # Панорамирование self._pan_current_pos = event.pos() delta = self._pan_current_pos - self._pan_start_pos scroll_area = self._find_scroll_area() if scroll_area: scroll_area.horizontalScrollBar().setValue( scroll_area.horizontalScrollBar().value() - delta.x() ) scroll_area.verticalScrollBar().setValue( scroll_area.verticalScrollBar().value() - delta.y() ) self._pan_start_pos = event.pos() super().mouseMoveEvent(event) def mouseReleaseEvent(self, event: QMouseEvent): """Обработка отпускания кнопки мыши""" if event.button() == Qt.MouseButton.LeftButton: self._is_panning = False self.setCursor(Qt.CursorShape.ArrowCursor) super().mouseReleaseEvent(event) def _find_scroll_area(self): """Находит родительский QScrollArea""" parent = self.parent() while parent: if isinstance(parent, QScrollArea): return parent parent = parent.parent() return None def _get_paint_offset(self, scaled_pixmap_size): """Вычисляет смещение для центрирования изображения""" label_rect = self.rect() if scaled_pixmap_size.width() < label_rect.width(): offset_x = (label_rect.width() - scaled_pixmap_size.width()) // 2 else: offset_x = 0 if scaled_pixmap_size.height() < label_rect.height(): offset_y = (label_rect.height() - scaled_pixmap_size.height()) // 2 else: offset_y = 0 return offset_x, offset_y def _widget_to_image_coords(self, widget_pos: QPoint): """Конвертирует координаты из виджета в координаты оригинального изображения""" if not self._pixmap_original or self.pixmap().isNull(): return None current_pixmap = self.pixmap() offset_x, offset_y = self._get_paint_offset(current_pixmap.size()) # Корректировка позиции мыши adjusted_pos = QPointF( widget_pos.x() - offset_x, widget_pos.y() - offset_y ) # Проверка, что курсор над изображением if (0 <= adjusted_pos.x() < current_pixmap.width() and 0 <= adjusted_pos.y() < current_pixmap.height()): # Конвертация в координаты оригинального изображения image_x = adjusted_pos.x() / self._scale image_y = adjusted_pos.y() / self._scale return QPointF(image_x, image_y) return None def _image_to_widget_coords(self, image_pos: QPointF): """Конвертирует координаты оригинального изображения в координаты виджета""" if not self._pixmap_original or self.pixmap().isNull(): return None # Масштабирование координат scaled_x = image_pos.x() * self._scale scaled_y = image_pos.y() * self._scale # Добавление смещения центрирования current_pixmap = self.pixmap() offset_x, offset_y = self._get_paint_offset(current_pixmap.size()) return QPointF(scaled_x + offset_x, scaled_y + offset_y) # --- Отрисовка --- def _update_pixmap(self): """Обновляет отображаемое изображение с bounding boxes""" if self._pixmap_original is None: self.clear() return # Масштабирование оригинального изображения scaled_size = self._pixmap_original.size() * self._scale scaled_pixmap = self._pixmap_original.scaled( scaled_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) # Создание временного pixmap для рисования temp_pixmap = QPixmap(scaled_pixmap.size()) temp_pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(temp_pixmap) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # Рисование масштабированного изображения painter.drawPixmap(0, 0, scaled_pixmap) scale_x = scaled_pixmap.width() / self._pixmap_original.width() scale_y = scaled_pixmap.height() / self._pixmap_original.height() # Рисование bounding boxes результатов if self.bboxes: for i, bbox_data in enumerate(self.bboxes): if "bbox" not in bbox_data: continue x1, y1, x2, y2 = bbox_data["bbox"] x1_s, y1_s = int(x1 * scale_x), int(y1 * scale_y) x2_s, y2_s = int(x2 * scale_x), int(y2 * scale_y) w, h = x2_s - x1_s, y2_s - y1_s if w <= 0 or h <= 0: continue if i == self.highlight_index: pen = QPen(QColor(255, 255, 0), 3) # Желтый brush = QBrush(QColor(255, 255, 0, 30)) painter.setPen(pen) painter.setBrush(brush) painter.drawRect(x1_s, y1_s, w, h) # Текст painter.setPen(QPen(Qt.GlobalColor.black, 1)) font = painter.font() font.setPointSize(max(8, int(10 * self._scale))) painter.setFont(font) label = bbox_data.get('label', 'N/A') score = bbox_data.get('score', 0.0) text = f"#{i+1}: {label} ({score:.2f})" text_rect = painter.boundingRect(QRectF(x1_s, y1_s - 25 * self._scale, w, 20 * self._scale),Qt.AlignmentFlag.AlignLeft, text) painter.fillRect(text_rect, QBrush(QColor(255, 255, 255, 200))) painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft, text) else: pen = QPen(QColor(255, 0, 0), max(1, int(2 * self._scale))) # Красный painter.setPen(pen) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawRect(x1_s, y1_s, w, h) painter.end() self.setPixmap(temp_pixmap) self.adjustSize() self.scaleChanged.emit(self._scale) def clear(self): """Очистка изображения, bounding boxes и ROI""" super().clear() self._pixmap_original = None self.bboxes = [] self.highlight_index = None self._scale = 1.0 self._drawing_mode = False self._has_roi = False self.setCursor(Qt.CursorShape.ArrowCursor) def clear_boxes(self): """Очистка bounding boxes""" self.bboxes = [] self._update_pixmap()