diff --git a/modules/mainWindow.py b/modules/mainWindow.py index 45ca7e8716b4b891b32614580df2e6b3d2240e32..3f4a30cbd49c4451f778ba3c940a9fceda6b3449 100644 --- a/modules/mainWindow.py +++ b/modules/mainWindow.py @@ -1,11 +1,11 @@ import math import numpy as np -from scipy.signal import savgol_filter from PyQt5.QtWidgets import ( QMainWindow, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QFileDialog ) from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtCore import QCloseEvent from compute_cost_image import compute_cost_image from preprocess_image import preprocess_image from advancedSettingsWidget import AdvancedSettingsWidget @@ -21,28 +21,26 @@ class MainWindow(QMainWindow): self._circle_calibrated_radius = 6 self._last_loaded_file_path = None - # For the contrast slider + # Value for the contrast slider self._current_clip_limit = 0.01 - # Outer widget + layout + # Outer widget and layout self._main_widget = QWidget() 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() - # 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.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% + self._main_layout.addWidget(self._left_container, 7) # 70% ratio of the full window - # 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) - # Hide it initially 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) @@ -64,7 +62,6 @@ class MainWindow(QMainWindow): self.btn_clear_points.clicked.connect(self.clear_points) btn_layout.addWidget(self.btn_clear_points) - # "Advanced Settings" toggle self.btn_advanced = QPushButton("Advanced Settings") self.btn_advanced.setCheckable(True) self.btn_advanced.clicked.connect(self._toggle_advanced_settings) @@ -76,7 +73,10 @@ class MainWindow(QMainWindow): self._old_central_widget = 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: self._advanced_widget.show() else: @@ -84,8 +84,11 @@ class MainWindow(QMainWindow): # Force re-layout self.adjustSize() + def open_circle_editor(self): - """ Replace central widget with circle editor. """ + """ + Replace central widget with circle editor. + """ if not self._last_loaded_pixmap: print("No image loaded yet! Cannot open circle editor.") return @@ -102,10 +105,17 @@ class MainWindow(QMainWindow): self._editor = 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 print(f"Circle Editor done. Radius = {final_radius}") + # Update cost image and path using new radius if self._last_loaded_file_path: cost_img = compute_cost_image( self._last_loaded_file_path, @@ -118,6 +128,7 @@ class MainWindow(QMainWindow): self.image_view._rebuild_full_path() self._update_advanced_images() + # Swap back to central widget editor_widget = self.takeCentralWidget() if editor_widget is not None: editor_widget.setParent(None) @@ -131,15 +142,23 @@ class MainWindow(QMainWindow): self._editor = None def toggle_rainbow(self): + """ + Toggle rainbow coloring of the path. + """ self.image_view.toggle_rainbow() 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() 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) @@ -158,7 +177,10 @@ class MainWindow(QMainWindow): self._last_loaded_file_path = file_path 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 if self._last_loaded_file_path: cost_img = compute_cost_image( @@ -174,6 +196,10 @@ class MainWindow(QMainWindow): self._update_advanced_images() 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: return pm_np = self._qpixmap_to_gray_float(self._last_loaded_pixmap) @@ -185,7 +211,16 @@ class MainWindow(QMainWindow): cost_img_np = self.image_view.cost_image 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 = img.convertToFormat(QImage.Format_ARGB32) ptr = img.bits() @@ -205,12 +240,9 @@ class MainWindow(QMainWindow): print("No path to export.") 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 - # 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() for ax, ay in anchor_points: min_dist = float('inf') @@ -245,7 +277,16 @@ class MainWindow(QMainWindow): print(f"Exported path with {len(full_xy)} points to {file_path}") def clear_points(self): + """ + Clears points from the image. + """ 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) \ No newline at end of file