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