from PyQt6.QtCore import Qt from PyQt6.QtGui import QPainter, QPixmap, QPen, QImage from PyQt6.QtWidgets import QWidget from app.core.main_manager import MainManager import numpy as np class ImageViewer(QWidget): def __init__(self, parent = None): super().__init__(parent) self.DicomWindow = parent self.main_manager = MainManager.get_instance() self.dicom_manager = self.main_manager.get_dicom_manager() self.display_image = None self.original_image = None self.drawing = False self.brush_size = 10 self.current_stroke = [] # Points du trait en cours self.last_point = None self.setMouseTracking(True) def set_image(self, image): self.original_image = image self.display_image = QPixmap.fromImage(image) self.update() def paintEvent(self, event): _ = event if self.display_image: painter = QPainter(self) scaled = self.display_image.scaled(self.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation) x = (self.width() - scaled.width()) // 2 y = (self.height() - scaled.height()) // 2 painter.drawPixmap(x, y, scaled) # Dessiner le trait en cours en temps réel avec la même taille que le brush if self.drawing and len(self.current_stroke) > 1: pen = QPen(Qt.GlobalColor.black, self.brush_size, Qt.PenStyle.SolidLine) pen.setCapStyle(Qt.PenCapStyle.RoundCap) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) painter.setPen(pen) # Dessiner des lignes entre les points pour un rendu fluide for i in range(1, len(self.current_stroke)): painter.drawLine( int(self.current_stroke[i-1][0]), int(self.current_stroke[i-1][1]), int(self.current_stroke[i][0]), int(self.current_stroke[i][1]) ) def mousePressEvent(self, event): if event.buttons() & Qt.MouseButton.LeftButton: self.drawing = True point = (event.position().x(), event.position().y()) self.current_stroke = [point] self.last_point = point def mouseMoveEvent(self, event): if self.drawing: current_point = (event.position().x(), event.position().y()) # Interpoler avec moins de points pour plus de fluidité if self.last_point: interpolated_points = self.interpolate_points(self.last_point, current_point, max_distance=3) self.current_stroke.extend(interpolated_points) else: self.current_stroke.append(current_point) self.last_point = current_point self.update() def mouseReleaseEvent(self, event): if self.drawing: self.drawing = False mask = self.create_mask_from_stroke() if mask is not None: self.DicomWindow.apply_brush_mask(mask) self.current_stroke = [] self.last_point = None def wheelEvent(self, event): delta = event.angleDelta().y() if delta != 0: self.DicomWindow.scroll_images(1 if delta > 0 else -1) event.accept() def screen_to_image_coords(self, x, y): if not self.display_image: return None scaled = self.display_image.scaled(self.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation) x_offset = (self.width() - scaled.width()) // 2 y_offset = (self.height() - scaled.height()) // 2 # Vérifie que le point est dans l'image affichée if not (x_offset <= x <= x_offset + scaled.width()) or not (y_offset <= y <= y_offset + scaled.height()): return None # Ramène dans les coordonnées de l'image d'origine img_x = (x - x_offset) * self.original_image.width() / scaled.width() img_y = (y - y_offset) * self.original_image.height() / scaled.height() return int(img_x), int(img_y) def interpolate_points(self, start_point, end_point, max_distance=2): """Interpole les points avec une distance maximale réduite pour plus de fluidité""" x1, y1 = start_point x2, y2 = end_point # Calcule la distance entre les points distance = max(abs(x2 - x1), abs(y2 - y1)) # Si la distance est petite, pas besoin d'interpoler if distance <= max_distance: return [end_point] # Crée des points intermédiaires avec une granularité plus fine points = [] steps = max(2, int(distance / max_distance)) for i in range(1, steps + 1): t = i / steps x = x1 + (x2 - x1) * t y = y1 + (y2 - y1) * t points.append((x, y)) return points def create_mask_from_stroke(self): """Crée un mask en utilisant QPainter sur une QImage temporaire""" if not self.original_image or len(self.current_stroke) < 1: return None # Créer une QImage temporaire de même taille que l'image originale mask_image = QImage(self.original_image.width(), self.original_image.height(), QImage.Format.Format_Grayscale8) mask_image.fill(0) # Remplir de noir (0) # Calculer les paramètres d'affichage scaled = self.display_image.scaled(self.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation) x_offset = (self.width() - scaled.width()) // 2 y_offset = (self.height() - scaled.height()) // 2 scale_x = self.original_image.width() / scaled.width() scale_y = self.original_image.height() / scaled.height() # Dessiner sur le mask avec QPainter painter = QPainter(mask_image) pen = QPen(Qt.GlobalColor.white, self.brush_size * scale_x, Qt.PenStyle.SolidLine) pen.setCapStyle(Qt.PenCapStyle.RoundCap) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) painter.setPen(pen) # Convertir et dessiner les points for i in range(1, len(self.current_stroke)): # Point précédent sx1, sy1 = self.current_stroke[i-1] img_x1 = (sx1 - x_offset) * scale_x img_y1 = (sy1 - y_offset) * scale_y # Point actuel sx2, sy2 = self.current_stroke[i] img_x2 = (sx2 - x_offset) * scale_x img_y2 = (sy2 - y_offset) * scale_y # Dessiner la ligne painter.drawLine(int(img_x1), int(img_y1), int(img_x2), int(img_y2)) painter.end() # Convertir en numpy array width = mask_image.width() height = mask_image.height() ptr = mask_image.constBits() ptr.setsize(height * width) mask_array = np.frombuffer(ptr, np.uint8).reshape((height, width)).copy() return mask_array