Skip to content
Snippets Groups Projects
Commit dd79c02e authored by Christian's avatar Christian
Browse files

Created a draft for the GUI

parent dbe23314
No related branches found
No related tags found
No related merge requests found
import sys
import numpy as np
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QGraphicsView, QGraphicsScene,
QGraphicsEllipseItem, QGraphicsPixmapItem, QPushButton,
QHBoxLayout, QVBoxLayout, QWidget, QFileDialog
)
from PyQt5.QtGui import QPixmap, QPen, QBrush
from PyQt5.QtCore import Qt, QRectF
class ImageGraphicsView(QGraphicsView):
"""
Custom class inheriting from QGraphicsView for displaying an image and placing red dots.
"""
def __init__(self, parent=None):
super().__init__(parent)
# Create scene and add it to the view
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
# Zoom around mouse pointer
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
# Image item
self.image_item = QGraphicsPixmapItem()
self.scene.addItem(self.image_item)
# Points and dot items
self.points = []
self.point_items = []
self.editor_mode = False
self.dot_radius = 4
# Enable built-in panning around image, but force arrow cursor initially
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.viewport().setCursor(Qt.ArrowCursor)
# Track clicking vs. dragging
self._mouse_pressed = False
self._press_view_pos = None
self._drag_threshold = 5
self._was_dragging = False
def load_image(self, image_path):
"""Load an image and fit it in the view."""
pixmap = QPixmap(image_path)
if not pixmap.isNull():
self.image_item.setPixmap(pixmap)
# Avoid TypeError by converting to QRectF
self.setSceneRect(QRectF(pixmap.rect()))
# Clear existing dots from previous image
self.points.clear()
self._clear_point_items()
# Reset transform then fit image in view
self.resetTransform()
self.fitInView(self.image_item, Qt.KeepAspectRatio)
def set_editor_mode(self, mode: bool):
"""If True: place/remove dots; if False: do nothing on click."""
self.editor_mode = mode
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._mouse_pressed = True
self._was_dragging = False
self._press_view_pos = event.pos()
# Switch to closed-hand cursor while left mouse is down
self.viewport().setCursor(Qt.ClosedHandCursor)
elif event.button() == Qt.RightButton:
# If Editor Mode is on remove the nearest dot
if self.editor_mode:
self._remove_point(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
"""
If movement > _drag_threshold: consider it a drag.
The actual panning is handled by QGraphicsView in ScrollHandDrag mode.
"""
# Check if the mouse is being dragged
if self._mouse_pressed and (event.buttons() & Qt.LeftButton):
# If the mouse moved more than the threshold, consider it a drag
dist = (event.pos() - self._press_view_pos).manhattanLength()
if dist > self._drag_threshold:
self._was_dragging = True
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
"""
After releasing the left button, go back to arrow cursor.
If it wasn't a drag, treat as a click (Editor Mode: add dot).
"""
# Let QGraphicsView handle release first
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton and self._mouse_pressed:
self._mouse_pressed = False
# Always go back to arrow cursor AFTER letting QGraphicsView handle release
self.viewport().setCursor(Qt.ArrowCursor)
if not self._was_dragging:
# It's a click: if editor mode is ON add a dot
if self.editor_mode:
self._add_point(event.pos())
self._was_dragging = False
def wheelEvent(self, event):
"""Mouse wheel = zoom."""
zoom_in_factor = 1.25
zoom_out_factor = 1 / zoom_in_factor
if event.angleDelta().y() > 0:
self.scale(zoom_in_factor, zoom_in_factor)
else:
self.scale(zoom_out_factor, zoom_out_factor)
event.accept()
# -------------- Red Dots --------------
def _add_point(self, view_pos):
"""Add a red dot at scene coords corresponding to view_pos."""
scene_pos = self.mapToScene(view_pos)
x, y = scene_pos.x(), scene_pos.y()
self.points.append((x, y))
dot = self._create_dot_item(x, y)
self.point_items.append(dot)
self.scene.addItem(dot)
def _remove_point(self, view_pos):
"""Right-click: remove nearest dot if within threshold."""
scene_pos = self.mapToScene(view_pos)
x_click, y_click = scene_pos.x(), scene_pos.y()
# Define threshold for removing a point
threshold = 10
closest_idx = None
min_dist = float('inf')
# Find the closest point to the click
for i, (x, y) in enumerate(self.points):
dist_sq = (x - x_click)**2 + (y - y_click)**2
if dist_sq < min_dist:
min_dist = dist_sq
closest_idx = i
# Remove the closest point if it's within the threshold
if closest_idx is not None and min_dist <= threshold**2:
self.scene.removeItem(self.point_items[closest_idx])
del self.point_items[closest_idx]
del self.points[closest_idx]
def _create_dot_item(self, x, y):
"""Helper for creating a small red ellipse item."""
r = self.dot_radius
ellipse = QGraphicsEllipseItem(x - r, y - r, 2*r, 2*r)
ellipse.setBrush(QBrush(Qt.red))
ellipse.setPen(QPen(Qt.red))
return ellipse
def _clear_point_items(self):
"""Remove all dot items from the scene."""
for item in self.point_items:
self.scene.removeItem(item)
self.point_items = []
class MainWindow(QMainWindow):
"""
Main window with:
- Button to load in image
- Editor mode toggle button
- Button for exporting placed points
"""
def __init__(self):
super().__init__()
self.setWindowTitle("Test GUI")
main_widget = QWidget()
main_layout = QVBoxLayout(main_widget)
self.image_view = ImageGraphicsView()
main_layout.addWidget(self.image_view)
btn_layout = QHBoxLayout()
# Load Image
self.btn_load_image = QPushButton("Load Image")
self.btn_load_image.clicked.connect(self.load_image)
btn_layout.addWidget(self.btn_load_image)
# Editor Mode
self.btn_editor_mode = QPushButton("Editor Mode: OFF")
self.btn_editor_mode.setCheckable(True)
self.btn_editor_mode.setStyleSheet("background-color: lightgray;")
self.btn_editor_mode.clicked.connect(self.toggle_editor_mode)
btn_layout.addWidget(self.btn_editor_mode)
# Export Points
self.btn_export_points = QPushButton("Export Points")
self.btn_export_points.clicked.connect(self.export_points)
btn_layout.addWidget(self.btn_export_points)
main_layout.addLayout(btn_layout)
self.setCentralWidget(main_widget)
self.resize(900, 600)
def load_image(self):
"""Open a file dialog to pick an image then load it."""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(
self, "Open Image", "",
"Images (*.png *.jpg *.jpeg *.bmp *.tif)",
options=options
)
if file_path:
self.image_view.load_image(file_path)
def toggle_editor_mode(self):
"""Toggle whether left-click places dots and right-click removes dots."""
is_checked = self.btn_editor_mode.isChecked()
self.image_view.set_editor_mode(is_checked)
if is_checked:
self.btn_editor_mode.setText("Editor Mode: ON")
self.btn_editor_mode.setStyleSheet("background-color: #ffcccc;")
else:
self.btn_editor_mode.setText("Editor Mode: OFF")
self.btn_editor_mode.setStyleSheet("background-color: lightgray;")
def export_points(self):
"""Save the list of dot coords to a .npy file."""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getSaveFileName(
self, "Export Points", "",
"NumPy Files (*.npy);;All Files (*)",
options=options
)
if file_path:
points_array = np.array(self.image_view.points)
np.save(file_path, points_array)
print(f"Exported {len(points_array)} points to {file_path}")
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
import sys
import numpy as np
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QGraphicsView, QGraphicsScene,
QGraphicsEllipseItem, QGraphicsPixmapItem, QPushButton,
QHBoxLayout, QVBoxLayout, QWidget, QFileDialog
)
from PyQt5.QtGui import QPixmap, QPen, QBrush
from PyQt5.QtCore import Qt, QRectF
class ImageGraphicsView(QGraphicsView):
"""
Custom class inheriting from QGraphicsView for displaying an image and placing red dots.
"""
def __init__(self, parent=None):
super().__init__(parent)
# Create scene and add it to the view
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
# Zoom around mouse pointer
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
# Image item
self.image_item = QGraphicsPixmapItem()
self.scene.addItem(self.image_item)
# Points and dot items
self.points = []
self.point_items = []
self.editor_mode = False
self.dot_radius = 4
# Enable built-in panning around image, but force arrow cursor initially
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.viewport().setCursor(Qt.ArrowCursor)
# Track clicking vs. dragging
self._mouse_pressed = False
self._press_view_pos = None
self._drag_threshold = 5
self._was_dragging = False
def load_image(self, image_path):
"""Load an image and fit it in the view."""
pixmap = QPixmap(image_path)
if not pixmap.isNull():
self.image_item.setPixmap(pixmap)
# Avoid TypeError by converting to QRectF
self.setSceneRect(QRectF(pixmap.rect()))
# Clear existing dots from previous image
self.points.clear()
self._clear_point_items()
# Reset transform then fit image in view
self.resetTransform()
self.fitInView(self.image_item, Qt.KeepAspectRatio)
def set_editor_mode(self, mode: bool):
"""If True: place/remove dots; if False: do nothing on click."""
self.editor_mode = mode
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._mouse_pressed = True
self._was_dragging = False
self._press_view_pos = event.pos()
# Switch to closed-hand cursor while left mouse is down
self.viewport().setCursor(Qt.ClosedHandCursor)
elif event.button() == Qt.RightButton:
# If Editor Mode is on remove the nearest dot
if self.editor_mode:
self._remove_point(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
"""
If movement > _drag_threshold: consider it a drag.
The actual panning is handled by QGraphicsView in ScrollHandDrag mode.
"""
# Check if the mouse is being dragged
if self._mouse_pressed and (event.buttons() & Qt.LeftButton):
# If the mouse moved more than the threshold, consider it a drag
dist = (event.pos() - self._press_view_pos).manhattanLength()
if dist > self._drag_threshold:
self._was_dragging = True
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
"""
After releasing the left button, go back to arrow cursor.
If it wasn't a drag, treat as a click (Editor Mode: add dot).
"""
# Let QGraphicsView handle release first
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton and self._mouse_pressed:
self._mouse_pressed = False
# Always go back to arrow cursor AFTER letting QGraphicsView handle release
self.viewport().setCursor(Qt.ArrowCursor)
if not self._was_dragging:
# It's a click: if editor mode is ON add a dot
if self.editor_mode:
self._add_point(event.pos())
self._was_dragging = False
def wheelEvent(self, event):
"""Mouse wheel = zoom."""
zoom_in_factor = 1.25
zoom_out_factor = 1 / zoom_in_factor
if event.angleDelta().y() > 0:
self.scale(zoom_in_factor, zoom_in_factor)
else:
self.scale(zoom_out_factor, zoom_out_factor)
event.accept()
# -------------- Red Dots --------------
def _add_point(self, view_pos):
"""Add a red dot at scene coords corresponding to view_pos."""
scene_pos = self.mapToScene(view_pos)
x, y = scene_pos.x(), scene_pos.y()
self.points.append((x, y))
dot = self._create_dot_item(x, y)
self.point_items.append(dot)
self.scene.addItem(dot)
def _remove_point(self, view_pos):
"""Right-click: remove nearest dot if within threshold."""
scene_pos = self.mapToScene(view_pos)
x_click, y_click = scene_pos.x(), scene_pos.y()
# Define threshold for removing a point
threshold = 10
closest_idx = None
min_dist = float('inf')
# Find the closest point to the click
for i, (x, y) in enumerate(self.points):
dist_sq = (x - x_click)**2 + (y - y_click)**2
if dist_sq < min_dist:
min_dist = dist_sq
closest_idx = i
# Remove the closest point if it's within the threshold
if closest_idx is not None and min_dist <= threshold**2:
self.scene.removeItem(self.point_items[closest_idx])
del self.point_items[closest_idx]
del self.points[closest_idx]
def _create_dot_item(self, x, y):
"""Helper for creating a small red ellipse item."""
r = self.dot_radius
ellipse = QGraphicsEllipseItem(x - r, y - r, 2*r, 2*r)
ellipse.setBrush(QBrush(Qt.red))
ellipse.setPen(QPen(Qt.red))
return ellipse
def _clear_point_items(self):
"""Remove all dot items from the scene."""
for item in self.point_items:
self.scene.removeItem(item)
self.point_items = []
class MainWindow(QMainWindow):
"""
Main window with:
- Button to load in image
- Editor mode toggle button
- Button for exporting placed points
"""
def __init__(self):
super().__init__()
self.setWindowTitle("Test GUI")
main_widget = QWidget()
main_layout = QVBoxLayout(main_widget)
self.image_view = ImageGraphicsView()
main_layout.addWidget(self.image_view)
btn_layout = QHBoxLayout()
# Load Image
self.btn_load_image = QPushButton("Load Image")
self.btn_load_image.clicked.connect(self.load_image)
btn_layout.addWidget(self.btn_load_image)
# Editor Mode
self.btn_editor_mode = QPushButton("Editor Mode: OFF")
self.btn_editor_mode.setCheckable(True)
self.btn_editor_mode.setStyleSheet("background-color: lightgray;")
self.btn_editor_mode.clicked.connect(self.toggle_editor_mode)
btn_layout.addWidget(self.btn_editor_mode)
# Export Points
self.btn_export_points = QPushButton("Export Points")
self.btn_export_points.clicked.connect(self.export_points)
btn_layout.addWidget(self.btn_export_points)
main_layout.addLayout(btn_layout)
self.setCentralWidget(main_widget)
self.resize(900, 600)
def load_image(self):
"""Open a file dialog to pick an image then load it."""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(
self, "Open Image", "",
"Images (*.png *.jpg *.jpeg *.bmp *.tif)",
options=options
)
if file_path:
self.image_view.load_image(file_path)
def toggle_editor_mode(self):
"""Toggle whether left-click places dots and right-click removes dots."""
is_checked = self.btn_editor_mode.isChecked()
self.image_view.set_editor_mode(is_checked)
if is_checked:
self.btn_editor_mode.setText("Editor Mode: ON")
self.btn_editor_mode.setStyleSheet("background-color: #ffcccc;")
else:
self.btn_editor_mode.setText("Editor Mode: OFF")
self.btn_editor_mode.setStyleSheet("background-color: lightgray;")
def export_points(self):
"""Save the list of dot coords to a .npy file."""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getSaveFileName(
self, "Export Points", "",
"NumPy Files (*.npy);;All Files (*)",
options=options
)
if file_path:
points_array = np.array(self.image_view.points)
np.save(file_path, points_array)
print(f"Exported {len(points_array)} points to {file_path}")
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment