From fc32908da7bcdf2825991f7b17ab15ee33d25216 Mon Sep 17 00:00:00 2001 From: mhby1g21 <mhby1g21@soton.ac.uk> Date: Tue, 29 Oct 2024 15:52:28 +0000 Subject: [PATCH] refactoring to use PyQt6, starting with config tab --- scripts/debug_tool/GUI_debug.py | 50 ++++--- scripts/debug_tool/tabs/config_tab.py | 162 +++++++++------------ scripts/debug_tool/utils/file_handlers.py | 64 ++++++++ scripts/debug_tool/utils/image_handlers.py | 34 +++++ scripts/debug_tool/utils/image_utils.py | 24 --- scripts/debug_tool/utils/qt_widgets.py | 61 ++++++++ 6 files changed, 256 insertions(+), 139 deletions(-) create mode 100644 scripts/debug_tool/utils/file_handlers.py create mode 100644 scripts/debug_tool/utils/image_handlers.py delete mode 100644 scripts/debug_tool/utils/image_utils.py create mode 100644 scripts/debug_tool/utils/qt_widgets.py diff --git a/scripts/debug_tool/GUI_debug.py b/scripts/debug_tool/GUI_debug.py index 38938de..dbacfdf 100644 --- a/scripts/debug_tool/GUI_debug.py +++ b/scripts/debug_tool/GUI_debug.py @@ -1,19 +1,15 @@ -import tkinter as tk -from tkinter import ttk +from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QTabWidget +import sys import os from tabs.config_tab import ConfigTab -from tabs.shifter_tab import ShifterTab -from tabs.depth_tab import DepthTab -from tabs.material_tab import MaterialTab -from tabs.edge_net_tab import EdgeNetTab from utils.config_reader import ConfigReader -class ModuleDebugGUI: +class ModuleDebugGUI(QMainWindow): def __init__(self): - self.window = tk.Tk() - self.window.title("Pipeline Debug Tool") - self.window.geometry("1600x800") + super().__init__() + self.setWindowTitle("Pipeline Debug Tool") + self.setGeometry(100, 100, 1600, 800) # Initialize paths self.DEBUG_DIR = os.path.dirname(os.path.abspath(__file__)) # debug_tool directory @@ -23,21 +19,27 @@ class ModuleDebugGUI: # Read configuration self.config_reader = ConfigReader(self.DEBUG_DIR, self.ROOT_DIR) - # Create notebook for tabs - self.notebook = ttk.Notebook(self.window) - self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - - # Initialize tabs - self.config_tab = ConfigTab(self.notebook, self.config_reader) - self.shifter_tab = ShifterTab(self.notebook, self.config_reader) - self.depth_tab = DepthTab(self.notebook, self.config_reader) - self.material_tab = MaterialTab(self.notebook, self.config_reader) - self.edge_net_tab = EdgeNetTab(self.notebook, self.config_reader) + # Setup UI + self.setup_ui() + + def setup_ui(self): + # Create main widget and layout + main_widget = QWidget() + self.setCentralWidget(main_widget) + layout = QVBoxLayout(main_widget) + # Create tab widget + self.tabs = QTabWidget() + layout.addWidget(self.tabs) - def run(self): - self.window.mainloop() + # Initialize tabs + self.tabs.addTab(ConfigTab(self.config_reader), "Configuration") + +def main(): + app = QApplication(sys.argv) + window = ModuleDebugGUI() + window.show() + sys.exit(app.exec()) if __name__ == "__main__": - app = ModuleDebugGUI() - app.run() \ No newline at end of file + main() \ No newline at end of file diff --git a/scripts/debug_tool/tabs/config_tab.py b/scripts/debug_tool/tabs/config_tab.py index 22c6008..17d9ad1 100644 --- a/scripts/debug_tool/tabs/config_tab.py +++ b/scripts/debug_tool/tabs/config_tab.py @@ -1,115 +1,95 @@ -import tkinter as tk -from tkinter import ttk, messagebox +# tabs/config_tab.py +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QMessageBox +from PyQt6.QtGui import QTextCharFormat, QColor, QTextCursor +from utils.qt_widgets import create_group_with_text, create_button_layout import os -class ConfigTab: - def __init__(self, notebook, config_reader): - self.tab = ttk.Frame(notebook) - notebook.add(self.tab, text='Configuration Check') - +class ConfigTab(QWidget): + def __init__(self, config_reader): + super().__init__() self.config_reader = config_reader self.setup_ui() - - def setup_ui(self): - # Main container with padding - main_frame = ttk.Frame(self.tab, padding="10") - main_frame.pack(fill=tk.BOTH, expand=True) - - # Config section - config_frame = ttk.LabelFrame(main_frame, text="Config Values", padding="5") - config_frame.pack(fill=tk.X, pady=5) - - self.config_text = tk.Text(config_frame, height=6, wrap=tk.WORD) - self.config_text.pack(fill=tk.X) - - # Directory paths section - dir_frame = ttk.LabelFrame(main_frame, text="Directory Paths", padding="5") - dir_frame.pack(fill=tk.X, pady=5) - - self.dir_text = tk.Text(dir_frame, height=8, wrap=tk.WORD) - self.dir_text.pack(fill=tk.X) - - # File paths section - file_frame = ttk.LabelFrame(main_frame, text="File Paths", padding="5") - file_frame.pack(fill=tk.X, pady=5) - - self.file_text = tk.Text(file_frame, height=5, wrap=tk.WORD) - self.file_text.pack(fill=tk.X) - # Path verification section - verify_frame = ttk.LabelFrame(main_frame, text="Path Verification", padding="5") - verify_frame.pack(fill=tk.X, pady=5) - - self.verify_text = tk.Text(verify_frame, height=8, wrap=tk.WORD) - self.verify_text.pack(fill=tk.X) - - # Buttons - button_frame = ttk.Frame(main_frame) - button_frame.pack(fill=tk.X, pady=5) - - ttk.Button( - button_frame, - text="Refresh Config", - command=self.refresh_all - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Verify Paths", - command=self.verify_paths - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Save Debug Info", - command=self.save_debug_info - ).pack(side=tk.RIGHT, padx=5) + def setup_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + + # Create groups and text areas using the utility function + config_group, self.config_text = create_group_with_text("Config Values", 100) + dir_group, self.dir_text = create_group_with_text("Directory Paths", 120) + file_group, self.file_text = create_group_with_text("File Paths", 80) + verify_group, self.verify_text = create_group_with_text("Path Verification", 120) + + # Add groups to layout + main_layout.addWidget(config_group) + main_layout.addWidget(dir_group) + main_layout.addWidget(file_group) + main_layout.addWidget(verify_group) + + # Create buttons using the utility function + buttons = [ + ("Refresh Config", self.refresh_all, 'left'), + ("Verify Paths", self.verify_paths, 'left'), + ("Save Debug Info", self.save_debug_info, 'right') + ] + button_layout = create_button_layout(*buttons) + main_layout.addLayout(button_layout) # Initial display self.refresh_all() def refresh_all(self): - self.refresh_config_display() - self.refresh_directory_display() - self.refresh_file_display() + self.display_config() + self.display_directories() + self.display_files() self.verify_paths() - def refresh_config_display(self): - self.config_text.delete(1.0, tk.END) + def display_config(self): + self.config_text.clear() for key, value in self.config_reader.config.items(): - self.config_text.insert(tk.END, f"{key} = {value}\n") + self.config_text.append(f"{key} = {value}") - def refresh_directory_display(self): - self.dir_text.delete(1.0, tk.END) + def display_directories(self): + self.dir_text.clear() for key, path in self.config_reader.directories.items(): - self.dir_text.insert(tk.END, f"{key}: {path}\n") + self.dir_text.append(f"{key}: {path}") - def refresh_file_display(self): - self.file_text.delete(1.0, tk.END) + def display_files(self): + self.file_text.clear() for key, path in self.config_reader.file_paths.items(): - self.file_text.insert(tk.END, f"{key}: {path}\n") + self.file_text.append(f"{key}: {path}") def verify_paths(self): - self.verify_text.delete(1.0, tk.END) + self.verify_text.clear() + + # Create formats for colored text + green_format = QTextCharFormat() + green_format.setForeground(QColor("green")) + red_format = QTextCharFormat() + red_format.setForeground(QColor("red")) - # Verify directories - self.verify_text.insert(tk.END, "Directory Verification:\n") + self.verify_text.append("Directory Verification:") for key, path in self.config_reader.directories.items(): exists = os.path.exists(path) status = "✓ exists" if exists else "✗ missing" - color = "green" if exists else "red" - self.verify_text.insert(tk.END, f"{key}: {status}\n", color) - - self.verify_text.insert(tk.END, "\nFile Verification:\n") + cursor = self.verify_text.textCursor() + cursor.movePosition(QTextCursor.MoveOperation.End) + self.verify_text.setTextCursor(cursor) + self.verify_text.insertPlainText(f"{key}: ") + self.verify_text.setCurrentCharFormat(green_format if exists else red_format) + self.verify_text.insertPlainText(f"{status}\n") + + self.verify_text.setCurrentCharFormat(QTextCharFormat()) + self.verify_text.append("\nFile Verification:") for key, path in self.config_reader.file_paths.items(): exists = os.path.exists(path) status = "✓ exists" if exists else "✗ missing" - color = "green" if exists else "red" - self.verify_text.insert(tk.END, f"{key}: {status}\n", color) - - # Add tags for colors - self.verify_text.tag_config("green", foreground="green") - self.verify_text.tag_config("red", foreground="red") + cursor = self.verify_text.textCursor() + cursor.movePosition(QTextCursor.MoveOperation.End) + self.verify_text.setTextCursor(cursor) + self.verify_text.insertPlainText(f"{key}: ") + self.verify_text.setCurrentCharFormat(green_format if exists else red_format) + self.verify_text.insertPlainText(f"{status}\n") def save_debug_info(self): debug_info = "=== Pipeline Debug Information ===\n\n" @@ -130,13 +110,13 @@ class ConfigTab: status = "exists" if exists else "missing" debug_info += f"{key}: {path} ({status})\n" - # Save to debug tool directory try: debug_path = os.path.join(self.config_reader.directories['debugDir'], - "pipeline_debug_info.txt") + "debug_config_info.txt") with open(debug_path, "w") as f: f.write(debug_info) - messagebox.showinfo("Success", - f"Debug information saved to:\n{debug_path}") + QMessageBox.information(self, "Success", + f"Debug information saved to:\n{debug_path}") except Exception as e: - messagebox.showerror("Error", f"Failed to save debug info: {str(e)}") \ No newline at end of file + QMessageBox.critical(self, "Error", + f"Failed to save debug info: {str(e)}") \ No newline at end of file diff --git a/scripts/debug_tool/utils/file_handlers.py b/scripts/debug_tool/utils/file_handlers.py new file mode 100644 index 0000000..8d79ed1 --- /dev/null +++ b/scripts/debug_tool/utils/file_handlers.py @@ -0,0 +1,64 @@ +# utils/file_handlers.py +from PyQt6.QtWidgets import QFileDialog + +def select_file(parent, title="Select File", file_types="All Files (*)", initial_dir=""): + """ + Opens a file selection dialog. + + Args: + parent: Parent widget + title: Dialog window title + file_types: File filter (e.g., "Images (*.png *.jpg);;All Files (*)") + initial_dir: Starting directory for the dialog + + Returns: + Selected file path or empty string if cancelled + """ + file_path, _ = QFileDialog.getOpenFileName( + parent, + title, + initial_dir, + file_types + ) + return file_path + +def save_file(parent, title="Save File", file_types="All Files (*)", initial_dir="", suggested_name=""): + """ + Opens a save file dialog. + + Args: + parent: Parent widget + title: Dialog window title + file_types: File filter + initial_dir: Starting directory + suggested_name: Default filename + + Returns: + Selected save path or empty string if cancelled + """ + file_path, _ = QFileDialog.getSaveFileName( + parent, + title, + os.path.join(initial_dir, suggested_name), + file_types + ) + return file_path + +def select_directory(parent, title="Select Directory", initial_dir=""): + """ + Opens a directory selection dialog. + + Args: + parent: Parent widget + title: Dialog window title + initial_dir: Starting directory + + Returns: + Selected directory path or empty string if cancelled + """ + return QFileDialog.getExistingDirectory( + parent, + title, + initial_dir, + QFileDialog.Option.ShowDirsOnly + ) diff --git a/scripts/debug_tool/utils/image_handlers.py b/scripts/debug_tool/utils/image_handlers.py new file mode 100644 index 0000000..9303376 --- /dev/null +++ b/scripts/debug_tool/utils/image_handlers.py @@ -0,0 +1,34 @@ +from PyQt6.QtGui import QPixmap, QImage +import numpy as np +import cv2 + +def convert_cv_to_pixmap(cv_img): + height, width = cv_img.shape[:2] + if len(cv_img.shape) == 3: + bytes_per_line = 3 * width + qt_img = QImage(cv_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) + else: + bytes_per_line = width + qt_img = QImage(cv_img.data, width, height, bytes_per_line, QImage.Format.Format_Grayscale8) + return QPixmap.fromImage(qt_img) + +def load_and_resize_image(image_path, max_size=800): + try: + img = cv2.imread(image_path) + if img is None: + raise Exception("Failed to load image") + + # Convert to RGB + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + # Calculate resize ratio + height, width = img.shape[:2] + ratio = min(max_size/width, max_size/height) + + if ratio < 1: + new_size = (int(width * ratio), int(height * ratio)) + img = cv2.resize(img, new_size, interpolation=cv2.INTER_AREA) + + return img + except Exception as e: + raise Exception(f"Error loading image: {str(e)}") \ No newline at end of file diff --git a/scripts/debug_tool/utils/image_utils.py b/scripts/debug_tool/utils/image_utils.py deleted file mode 100644 index 740992a..0000000 --- a/scripts/debug_tool/utils/image_utils.py +++ /dev/null @@ -1,24 +0,0 @@ -from PIL import Image, ImageTk -import os - -def create_preview(image_path, size=(300, 300)): - """Create a thumbnail preview of the given image""" - try: - if not os.path.exists(image_path): - raise FileNotFoundError(f"Image file not found: {image_path}") - - img = Image.open(image_path) - img.thumbnail(size) - return ImageTk.PhotoImage(img) - except Exception as e: - raise Exception(f"Failed to create image preview: {str(e)}") - -def verify_image(image_path): - """Verify that an image file exists and can be opened""" - try: - if not os.path.exists(image_path): - return False - Image.open(image_path) - return True - except: - return False \ No newline at end of file diff --git a/scripts/debug_tool/utils/qt_widgets.py b/scripts/debug_tool/utils/qt_widgets.py new file mode 100644 index 0000000..59c5284 --- /dev/null +++ b/scripts/debug_tool/utils/qt_widgets.py @@ -0,0 +1,61 @@ +# utils/qt_widgets.py +from PyQt6.QtWidgets import (QTextEdit, QGroupBox, QVBoxLayout, + QHBoxLayout, QPushButton) + +def create_group_with_text(title, height=100): + """ + Creates a QGroupBox containing a QTextEdit. + + Args: + title: Group box title + height: Fixed height for text edit + Returns: + tuple: (QGroupBox, QTextEdit) + """ + group = QGroupBox(title) + layout = QVBoxLayout(group) + text_edit = QTextEdit() + text_edit.setFixedHeight(height) + text_edit.setReadOnly(True) + layout.addWidget(text_edit) + return group, text_edit + +def create_button_layout(*buttons): + """ + Creates a horizontal button layout with optional stretch. + + Args: + buttons: List of tuples (label, callback, position) + position can be 'left', 'right', or None for default + Returns: + QHBoxLayout with arranged buttons + """ + layout = QHBoxLayout() + + # Add left-aligned buttons + for label, callback, position in buttons: + if position == 'left': + btn = QPushButton(label) + btn.clicked.connect(callback) + layout.addWidget(btn) + + # Add stretch in the middle + layout.addStretch() + + # Add right-aligned buttons + for label, callback, position in buttons: + if position == 'right': + btn = QPushButton(label) + btn.clicked.connect(callback) + layout.addWidget(btn) + + return layout + +def create_status_group(): + """ + Creates a standard status display group. + + Returns: + tuple: (QGroupBox, QTextEdit) + """ + return create_group_with_text("Status", 80) \ No newline at end of file -- GitLab