Skip to content
Snippets Groups Projects
Commit 6bcc3a83 authored by s224361's avatar s224361
Browse files

Cleaned up modules

parent 4cfed8cb
No related branches found
No related tags found
No related merge requests found
import math import math
import numpy as np import numpy as np
from scipy.signal import savgol_filter
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QMainWindow, QPushButton, QHBoxLayout, QMainWindow, QPushButton, QHBoxLayout,
QVBoxLayout, QWidget, QFileDialog QVBoxLayout, QWidget, QFileDialog
) )
from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import QCloseEvent
from compute_cost_image import compute_cost_image from compute_cost_image import compute_cost_image
from preprocess_image import preprocess_image from preprocess_image import preprocess_image
from advancedSettingsWidget import AdvancedSettingsWidget from advancedSettingsWidget import AdvancedSettingsWidget
...@@ -21,28 +21,26 @@ class MainWindow(QMainWindow): ...@@ -21,28 +21,26 @@ class MainWindow(QMainWindow):
self._circle_calibrated_radius = 6 self._circle_calibrated_radius = 6
self._last_loaded_file_path = None self._last_loaded_file_path = None
# For the contrast slider # Value for the contrast slider
self._current_clip_limit = 0.01 self._current_clip_limit = 0.01
# Outer widget + layout # Outer widget and layout
self._main_widget = QWidget() self._main_widget = QWidget()
self._main_layout = QHBoxLayout(self._main_widget) self._main_layout = QHBoxLayout(self._main_widget)
# The "left" part: container for the image area + its controls # Container for the image area and its controls
self._left_panel = QVBoxLayout() self._left_panel = QVBoxLayout()
# We'll make a container widget for the left panel, so we can set stretches: # Container widget for stretching the panel
self._left_container = QWidget() self._left_container = QWidget()
self._left_container.setLayout(self._left_panel) self._left_container.setLayout(self._left_panel)
# Now we add them to the main layout with 70%:30% ratio self._main_layout.addWidget(self._left_container, 7) # 70% ratio of the full window
self._main_layout.addWidget(self._left_container, 7) # 70%
# We haven't added the advanced widget yet, but we'll do so with ratio=3 => 30% # Advanced widget window
self._advanced_widget = AdvancedSettingsWidget(self) self._advanced_widget = AdvancedSettingsWidget(self)
# Hide it initially
self._advanced_widget.hide() self._advanced_widget.hide()
self._main_layout.addWidget(self._advanced_widget, 3) self._main_layout.addWidget(self._advanced_widget, 3) # 30% ratio of the full window
self.setCentralWidget(self._main_widget) self.setCentralWidget(self._main_widget)
...@@ -64,7 +62,6 @@ class MainWindow(QMainWindow): ...@@ -64,7 +62,6 @@ class MainWindow(QMainWindow):
self.btn_clear_points.clicked.connect(self.clear_points) self.btn_clear_points.clicked.connect(self.clear_points)
btn_layout.addWidget(self.btn_clear_points) btn_layout.addWidget(self.btn_clear_points)
# "Advanced Settings" toggle
self.btn_advanced = QPushButton("Advanced Settings") self.btn_advanced = QPushButton("Advanced Settings")
self.btn_advanced.setCheckable(True) self.btn_advanced.setCheckable(True)
self.btn_advanced.clicked.connect(self._toggle_advanced_settings) self.btn_advanced.clicked.connect(self._toggle_advanced_settings)
...@@ -76,7 +73,10 @@ class MainWindow(QMainWindow): ...@@ -76,7 +73,10 @@ class MainWindow(QMainWindow):
self._old_central_widget = None self._old_central_widget = None
self._editor = None self._editor = None
def _toggle_advanced_settings(self, checked): def _toggle_advanced_settings(self, checked: bool):
"""
Toggles the visibility of the advanced settings widget.
"""
if checked: if checked:
self._advanced_widget.show() self._advanced_widget.show()
else: else:
...@@ -84,8 +84,11 @@ class MainWindow(QMainWindow): ...@@ -84,8 +84,11 @@ class MainWindow(QMainWindow):
# Force re-layout # Force re-layout
self.adjustSize() self.adjustSize()
def open_circle_editor(self): def open_circle_editor(self):
""" Replace central widget with circle editor. """ """
Replace central widget with circle editor.
"""
if not self._last_loaded_pixmap: if not self._last_loaded_pixmap:
print("No image loaded yet! Cannot open circle editor.") print("No image loaded yet! Cannot open circle editor.")
return return
...@@ -102,10 +105,17 @@ class MainWindow(QMainWindow): ...@@ -102,10 +105,17 @@ class MainWindow(QMainWindow):
self._editor = editor self._editor = editor
self.setCentralWidget(editor) self.setCentralWidget(editor)
def _on_circle_editor_done(self, final_radius):
def _on_circle_editor_done(self, final_radius: int):
"""
Updates the calibrated radius, computes the cost image based on the new radius,
and updates the image view with the new cost image.
It also restores the previous central widget and cleans up the editor widget.
"""
self._circle_calibrated_radius = final_radius self._circle_calibrated_radius = final_radius
print(f"Circle Editor done. Radius = {final_radius}") print(f"Circle Editor done. Radius = {final_radius}")
# Update cost image and path using new radius
if self._last_loaded_file_path: if self._last_loaded_file_path:
cost_img = compute_cost_image( cost_img = compute_cost_image(
self._last_loaded_file_path, self._last_loaded_file_path,
...@@ -118,6 +128,7 @@ class MainWindow(QMainWindow): ...@@ -118,6 +128,7 @@ class MainWindow(QMainWindow):
self.image_view._rebuild_full_path() self.image_view._rebuild_full_path()
self._update_advanced_images() self._update_advanced_images()
# Swap back to central widget
editor_widget = self.takeCentralWidget() editor_widget = self.takeCentralWidget()
if editor_widget is not None: if editor_widget is not None:
editor_widget.setParent(None) editor_widget.setParent(None)
...@@ -131,15 +142,23 @@ class MainWindow(QMainWindow): ...@@ -131,15 +142,23 @@ class MainWindow(QMainWindow):
self._editor = None self._editor = None
def toggle_rainbow(self): def toggle_rainbow(self):
"""
Toggle rainbow coloring of the path.
"""
self.image_view.toggle_rainbow() self.image_view.toggle_rainbow()
def load_image(self): def load_image(self):
"""
Load an image and update the image view and cost image.
The supported image formats are: PNG, JPG, JPEG, BMP, and TIF.
"""
options = QFileDialog.Options() options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName( file_path, _ = QFileDialog.getOpenFileName(
self, "Open Image", "", self, "Open Image", "",
"Images (*.png *.jpg *.jpeg *.bmp *.tif)", "Images (*.png *.jpg *.jpeg *.bmp *.tif)",
options=options options=options
) )
if file_path: if file_path:
self.image_view.load_image(file_path) self.image_view.load_image(file_path)
...@@ -158,7 +177,10 @@ class MainWindow(QMainWindow): ...@@ -158,7 +177,10 @@ class MainWindow(QMainWindow):
self._last_loaded_file_path = file_path self._last_loaded_file_path = file_path
self._update_advanced_images() self._update_advanced_images()
def update_contrast(self, clip_limit): def update_contrast(self, clip_limit: float):
"""
Updates and applies the contrast value of the image.
"""
self._current_clip_limit = clip_limit self._current_clip_limit = clip_limit
if self._last_loaded_file_path: if self._last_loaded_file_path:
cost_img = compute_cost_image( cost_img = compute_cost_image(
...@@ -174,6 +196,10 @@ class MainWindow(QMainWindow): ...@@ -174,6 +196,10 @@ class MainWindow(QMainWindow):
self._update_advanced_images() self._update_advanced_images()
def _update_advanced_images(self): def _update_advanced_images(self):
"""
Updates the advanced images display with the latest image.
If no image has been loaded, the method returns without making any updates.
"""
if not self._last_loaded_pixmap: if not self._last_loaded_pixmap:
return return
pm_np = self._qpixmap_to_gray_float(self._last_loaded_pixmap) pm_np = self._qpixmap_to_gray_float(self._last_loaded_pixmap)
...@@ -185,7 +211,16 @@ class MainWindow(QMainWindow): ...@@ -185,7 +211,16 @@ class MainWindow(QMainWindow):
cost_img_np = self.image_view.cost_image cost_img_np = self.image_view.cost_image
self._advanced_widget.update_displays(contrasted_blurred, cost_img_np) self._advanced_widget.update_displays(contrasted_blurred, cost_img_np)
def _qpixmap_to_gray_float(self, qpix): def _qpixmap_to_gray_float(self, qpix: QPixmap) -> np.ndarray:
"""
Convert a QPixmap to a grayscale float array.
Args:
qpix: The QPixmap to be converted.
Returns:
A 2D numpy array representing the grayscale image.
"""
img = qpix.toImage() img = qpix.toImage()
img = img.convertToFormat(QImage.Format_ARGB32) img = img.convertToFormat(QImage.Format_ARGB32)
ptr = img.bits() ptr = img.bits()
...@@ -205,12 +240,9 @@ class MainWindow(QMainWindow): ...@@ -205,12 +240,9 @@ class MainWindow(QMainWindow):
print("No path to export.") print("No path to export.")
return return
# We'll consider each anchor point as "USER-PLACED".
# But unlike a distance-threshold approach, we assign each anchor
# to exactly one closest path point.
anchor_points = self.image_view.anchor_points anchor_points = self.image_view.anchor_points
# For each anchor, find the index of the closest path point # Finds the index of the closest path point for each anchor point
user_placed_indices = set() user_placed_indices = set()
for ax, ay in anchor_points: for ax, ay in anchor_points:
min_dist = float('inf') min_dist = float('inf')
...@@ -245,7 +277,16 @@ class MainWindow(QMainWindow): ...@@ -245,7 +277,16 @@ class MainWindow(QMainWindow):
print(f"Exported path with {len(full_xy)} points to {file_path}") print(f"Exported path with {len(full_xy)} points to {file_path}")
def clear_points(self): def clear_points(self):
"""
Clears points from the image.
"""
self.image_view.clear_guide_points() self.image_view.clear_guide_points()
def closeEvent(self, event): def closeEvent(self, event: QCloseEvent):
"""
Handle the window close event.
Args:
event: The close event.
"""
super().closeEvent(event) super().closeEvent(event)
\ No newline at end of file
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