HoDA_Radio/app/ui/widgets/image_viewer.py
2025-09-26 17:51:43 +02:00

176 lines
7.0 KiB
Python

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