From 34d72aafada7dc2957688080d8bdb7155c720700 Mon Sep 17 00:00:00 2001 From: mhby1g21 <mhby1g21@soton.ac.uk> Date: Tue, 29 Oct 2024 16:44:58 +0000 Subject: [PATCH] refactored material tab (much more managable file size/lines now) --- scripts/debug_tool/GUI_debug.py | 2 + scripts/debug_tool/tabs/material_tab.py | 812 ++++++--------------- scripts/debug_tool/utils/image_handlers.py | 30 +- scripts/debug_tool/utils/qt_widgets.py | 85 ++- 4 files changed, 328 insertions(+), 601 deletions(-) diff --git a/scripts/debug_tool/GUI_debug.py b/scripts/debug_tool/GUI_debug.py index 670e565..a4c39fb 100644 --- a/scripts/debug_tool/GUI_debug.py +++ b/scripts/debug_tool/GUI_debug.py @@ -5,6 +5,7 @@ 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 utils.config_reader import ConfigReader class ModuleDebugGUI(QMainWindow): @@ -38,6 +39,7 @@ class ModuleDebugGUI(QMainWindow): self.tabs.addTab(ConfigTab(self.config_reader), "Configuration") self.tabs.addTab(ShifterTab(self.config_reader), "Image Shifter") self.tabs.addTab(DepthTab(self.config_reader), "MonoDepth Estimation") + self.tabs.addTab(MaterialTab(self.config_reader), "Material Recognition") def main(): app = QApplication(sys.argv) diff --git a/scripts/debug_tool/tabs/material_tab.py b/scripts/debug_tool/tabs/material_tab.py index c21c376..b38111d 100644 --- a/scripts/debug_tool/tabs/material_tab.py +++ b/scripts/debug_tool/tabs/material_tab.py @@ -1,15 +1,20 @@ -import tkinter as tk -from tkinter import ttk, messagebox, filedialog +# tabs/material_tab.py +from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, + QMessageBox, QTabWidget) +from PyQt6.QtCore import Qt import os -from PIL import Image, ImageTk -import subprocess -import shutil -class MaterialTab: - def __init__(self, notebook, config_reader): - self.tab = ttk.Frame(notebook) - notebook.add(self.tab, text='Material Recognition') - +from utils.qt_widgets import (create_group_with_text, create_button_layout, + create_info_group, create_preview_group, + create_status_label, create_preview_grid, + update_status_indicator, get_status_text) +from utils.file_handlers import select_file, clean_directory, run_command +from utils.image_handlers import (update_preview, update_face_previews, + clear_previews) + +class MaterialTab(QWidget): + def __init__(self, config_reader): + super().__init__() self.config_reader = config_reader self.input_file_path = None @@ -17,641 +22,250 @@ class MaterialTab: self.material_recog_dir = self.config_reader.directories['materialRecogDir'] self.checkpoint_file = self.config_reader.file_paths['checkpointFile'] self.cubemap_dir = os.path.join(self.material_recog_dir, "cubemap_faces") + self.material_output_dir = os.path.join(self.material_recog_dir, "output", "cubemap_faces") self.setup_ui() + self.verify_checkpoint() def setup_ui(self): - # Split into left and right frames - left_frame = ttk.Frame(self.tab) - left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) - - right_frame = ttk.Frame(self.tab) - right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) - - self.setup_control_panel(left_frame) - self.setup_preview_panel(right_frame) + main_layout = QHBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + + # Left panel (controls) + left_layout = QVBoxLayout() + self.setup_control_panel(left_layout) + main_layout.addLayout(left_layout) + + # Right panel (preview) + right_layout = QVBoxLayout() + self.setup_preview_panel(right_layout) + main_layout.addLayout(right_layout) - def setup_control_panel(self, parent): - # Control section - control_frame = ttk.LabelFrame(parent, text="Controls", padding="5") - control_frame.pack(fill=tk.X, pady=5) - - # Input file selection - input_frame = ttk.Frame(control_frame) - input_frame.pack(fill=tk.X, pady=5) - - ttk.Label(input_frame, text="Input file:").pack(side=tk.LEFT, padx=5) - self.input_label = ttk.Label(input_frame, text="No file selected") - self.input_label.pack(side=tk.LEFT, padx=5) - - # Checkpoint file verification - checkpoint_frame = ttk.Frame(control_frame) - checkpoint_frame.pack(fill=tk.X, pady=5) - - ttk.Label(checkpoint_frame, text="Checkpoint file:").pack(side=tk.LEFT, padx=5) - self.checkpoint_label = ttk.Label( - checkpoint_frame, - text="✓ Found" if os.path.exists(self.checkpoint_file) else "✗ Missing", - foreground="green" if os.path.exists(self.checkpoint_file) else "red" - ) - self.checkpoint_label.pack(side=tk.LEFT, padx=5) + def setup_control_panel(self, layout): + # Info display + info_rows = [ + ("Input file:", "No file selected"), + ("Checkpoint:", "✓ Found" if os.path.exists(self.checkpoint_file) else "✗ Missing") + ] + info_group, self.info_labels = create_info_group("Controls", info_rows) + layout.addWidget(info_group) # Progress indicators - self.progress_frame = ttk.LabelFrame(control_frame, text="Progress", padding="5") - self.progress_frame.pack(fill=tk.X, pady=5) - - # Split progress - split_frame = ttk.Frame(self.progress_frame) - split_frame.pack(fill=tk.X, pady=2) - ttk.Label(split_frame, text="Split 360:").pack(side=tk.LEFT, padx=5) - self.split_status = ttk.Label(split_frame, text="Not started") - self.split_status.pack(side=tk.LEFT, padx=5) - - # Material recognition progress - recog_frame = ttk.Frame(self.progress_frame) - recog_frame.pack(fill=tk.X, pady=2) - ttk.Label(recog_frame, text="Material Recognition:").pack(side=tk.LEFT, padx=5) - self.recog_status = ttk.Label(recog_frame, text="Not started") - self.recog_status.pack(side=tk.LEFT, padx=5) - - # Combine progress - combine_frame = ttk.Frame(self.progress_frame) - combine_frame.pack(fill=tk.X, pady=2) - ttk.Label(combine_frame, text="Combine Output:").pack(side=tk.LEFT, padx=5) - self.combine_status = ttk.Label(combine_frame, text="Not started") - self.combine_status.pack(side=tk.LEFT, padx=5) - - # Control buttons - button_frame = ttk.Frame(control_frame) - button_frame.pack(fill=tk.X, pady=5) - - ttk.Button( - button_frame, - text="Clean Working Dir", - command=self.clean_working_dir - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Select Input", - command=self.select_input_file - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Run Split 360", - command=self.run_split_360 - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Run Material Recognition", - command=self.run_material_recognition - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Run Combine", - command=self.run_combine - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Run All Steps", - command=self.run_all_steps - ).pack(side=tk.LEFT, padx=5) - - ttk.Button( - button_frame, - text="Clear Status", - command=self.clear_status - ).pack(side=tk.RIGHT, padx=5) - - # Status section - status_frame = ttk.LabelFrame(parent, text="Status", padding="5") - status_frame.pack(fill=tk.BOTH, expand=True, pady=5) - - # Add scrollbar to status - status_scroll = ttk.Scrollbar(status_frame) - status_scroll.pack(side=tk.RIGHT, fill=tk.Y) - - self.status_text = tk.Text( - status_frame, - height=10, - wrap=tk.WORD, - yscrollcommand=status_scroll.set - ) - self.status_text.pack(fill=tk.BOTH, expand=True) - status_scroll.config(command=self.status_text.yview) + progress_group = QGroupBox("Progress") + progress_layout = QVBoxLayout(progress_group) + + self.split_status = create_status_label("Split 360:", "Not started") + self.recognition_status = create_status_label("Material Recognition:", "Not started") + self.combine_status = create_status_label("Combine Output:", "Not started") + + progress_layout.addLayout(self.split_status) + progress_layout.addLayout(self.recognition_status) + progress_layout.addLayout(self.combine_status) + layout.addWidget(progress_group) + + # Buttons + buttons = [ + ("Clean Working Dir", self.clean_working_dir, 'left'), + ("Select Input", self.select_input, 'left'), + ("Run Split 360", self.run_split_360, 'left'), + ("Run Recognition", self.run_material_recognition, 'left'), + ("Run Combine", self.run_combine, 'left'), + ("Run All Steps", self.run_all_steps, 'left'), + ("Clear Status", lambda: self.status_text.clear(), 'right') + ] + layout.addLayout(create_button_layout(*buttons)) + + # Status display + status_group, self.status_text = create_group_with_text("Status", 150) + layout.addWidget(status_group) - def setup_preview_panel(self, parent): - notebook = ttk.Notebook(parent) - notebook.pack(fill=tk.BOTH, expand=True) + def setup_preview_panel(self, layout): + preview_tabs = QTabWidget() # Input/Output preview tab - io_frame = ttk.Frame(notebook) - notebook.add(io_frame, text='Input/Output') + io_tab = QWidget() + io_layout = QVBoxLayout(io_tab) - # Input preview - input_frame = ttk.LabelFrame(io_frame, text="Input Image") - input_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + input_group, self.input_preview = create_preview_group("Input Image") + output_group, self.output_preview = create_preview_group("Material Map") - self.input_preview = ttk.Label(input_frame) - self.input_preview.pack(padx=5, pady=5) + io_layout.addWidget(input_group) + io_layout.addWidget(output_group) + preview_tabs.addTab(io_tab, "Input/Output") - # Material map preview - output_frame = ttk.LabelFrame(io_frame, text="Material Map") - output_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + # RGB faces preview tab + rgb_tab = QWidget() + rgb_layout = QVBoxLayout(rgb_tab) - self.output_preview = ttk.Label(output_frame) - self.output_preview.pack(padx=5, pady=5) - - # RGB Cube faces preview tab - rgb_faces_frame = ttk.Frame(notebook) - notebook.add(rgb_faces_frame, text='RGB Cube Faces') - - # Create grid for RGB cube faces self.rgb_face_previews = {} - face_names = ['front', 'back', 'left', 'right', 'top', 'bottom'] - row = 0 - col = 0 - for face in face_names: - frame = ttk.LabelFrame(rgb_faces_frame, text=face) - frame.grid(row=row, column=col, padx=5, pady=5, sticky='nsew') - - label = ttk.Label(frame) - label.pack(padx=5, pady=5) - self.rgb_face_previews[face] = label - - col += 1 - if col > 2: # 3 columns - col = 0 - row += 1 - - # Configure grid weights for RGB faces - rgb_faces_frame.grid_columnconfigure(0, weight=1) - rgb_faces_frame.grid_columnconfigure(1, weight=1) - rgb_faces_frame.grid_columnconfigure(2, weight=1) - - # Material Cube faces preview tab - material_faces_frame = ttk.Frame(notebook) - notebook.add(material_faces_frame, text='Material Cube Faces') + faces_layout = create_preview_grid( + ['front', 'back', 'left', 'right', 'top', 'bottom'], + self.rgb_face_previews + ) + rgb_layout.addLayout(faces_layout) + preview_tabs.addTab(rgb_tab, "RGB Cube Faces") + + # Material faces preview tab + material_tab = QWidget() + material_layout = QVBoxLayout(material_tab) - # Create grid for material cube faces self.material_face_previews = {} - row = 0 - col = 0 - face_names = ['frontrgb', 'backrgb', 'leftrgb', 'rightrgb', 'toprgb', 'bottomrgb'] - for face in face_names: - frame = ttk.LabelFrame(material_faces_frame, text=face) - frame.grid(row=row, column=col, padx=5, pady=5, sticky='nsew') - - label = ttk.Label(frame) - label.pack(padx=5, pady=5) - self.material_face_previews[face] = label - - col += 1 - if col > 2: # 3 columns - col = 0 - row += 1 + material_faces_layout = create_preview_grid( + ['front', 'back', 'left', 'right', 'top', 'bottom'], + self.material_face_previews + ) + material_layout.addLayout(material_faces_layout) + preview_tabs.addTab(material_tab, "Material Cube Faces") + + layout.addWidget(preview_tabs) - # Configure grid weights for material faces - material_faces_frame.grid_columnconfigure(0, weight=1) - material_faces_frame.grid_columnconfigure(1, weight=1) - material_faces_frame.grid_columnconfigure(2, weight=1) + def select_input(self): + file_path = select_file( + self, + "Select Input Image", + "Images (*.png *.jpg *.jpeg)" + ) + + if file_path: + self.input_file_path = file_path + self.info_labels["Input file:"].setText(os.path.basename(file_path)) + self.update_status(f"Selected input file: {file_path}") + update_preview(self.input_preview, file_path, + error_callback=self.update_status) + self.reset_status_indicators() def clean_working_dir(self): - """Clean all working directories""" - try: - directories_to_clean = [ - # Main cubemap_faces directory - self.cubemap_dir, - # Output cubemap_faces directory for material maps - os.path.join(self.material_recog_dir, "output", "cubemap_faces"), - ] - - for directory in directories_to_clean: - if os.path.exists(directory): - self.update_status(f"Cleaning directory: {directory}") - shutil.rmtree(directory) - # Recreate directories - os.makedirs(directory, exist_ok=True) - - self.update_status("All working directories cleaned") - - # Reset status indicators - self.split_status.config(text="Not started") - self.recog_status.config(text="Not started") - self.combine_status.config(text="Not started") - - # Clear all previews - self.clear_previews() + dirs_to_clean = [self.cubemap_dir, self.material_output_dir] + + for directory in dirs_to_clean: + if not clean_directory(directory, self.update_status): + QMessageBox.critical(self, "Error", f"Failed to clean directory: {directory}") + return - except Exception as e: - error_msg = f"Error cleaning directories: {str(e)}" - self.update_status(error_msg) - messagebox.showerror("Error", error_msg) - - def clear_previews(self): - """Clear all image previews""" - self.input_preview.configure(image='') - self.output_preview.configure(image='') - for face_preview in self.rgb_face_previews.values(): - face_preview.configure(image='') - for face_preview in self.material_face_previews.values(): - face_preview.configure(image='') - - def select_input_file(self): - filepath = filedialog.askopenfilename( - filetypes=[("Image files", "*.png *.jpg *.jpeg")] + clear_previews( + self.input_preview, + self.output_preview, + self.rgb_face_previews, + self.material_face_previews ) - if filepath: - self.input_file_path = os.path.normpath(filepath) - self.input_label.config(text=os.path.basename(filepath)) - self.update_status(f"Selected input file: {filepath}") - self.update_input_preview() - - # Reset status indicators - self.split_status.config(text="Not started") - self.recog_status.config(text="Not started") - self.combine_status.config(text="Not started") + self.reset_status_indicators() def run_split_360(self): if not self.input_file_path: - messagebox.showwarning("Warning", "Please select an input file first") + QMessageBox.warning(self, "Warning", "Please select an input file first") return - try: - self.update_status("Running 360 image splitting...") - self.split_status.config(text="Running...", foreground="orange") - - # Change to material recognition directory - original_dir = os.getcwd() - os.chdir(self.material_recog_dir) - - # Run split_img.py - cmd = f'''call "{self.config_reader.config["condaDir"]}\\condabin\\activate.bat" {self.config_reader.config["materialEnv"]} && python split_img.py "{self.input_file_path}" && call "{self.config_reader.config["condaDir"]}\\condabin\\deactivate.bat"''' - - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True, - text=True - ) - stdout, stderr = process.communicate() - - # Change back to original directory - os.chdir(original_dir) - - if process.returncode == 0: - self.update_status("Split 360 completed successfully") - self.split_status.config(text="✓ Complete", foreground="green") - self.update_face_previews() - else: - self.update_status(f"Split 360 failed:\n{stderr}") - self.split_status.config(text="✗ Failed", foreground="red") - raise Exception(stderr) - - except Exception as e: - self.update_status(f"Error in split 360: {str(e)}") - self.split_status.config(text="✗ Failed", foreground="red") - messagebox.showerror("Error", f"Split 360 failed: {str(e)}") - - def run_command(self, cmd, description): - """Run a single command and return its output""" - self.update_status(f"Executing {description}:\n{cmd}") - - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True, - text=True - ) - stdout, stderr = process.communicate() + self.update_status("Running 360 image splitting...") + update_status_indicator(self.split_status, "Running") - if process.returncode != 0: - self.update_status(f"Error in {description}:\n{stderr}") - raise Exception(f"{description} failed: {stderr}") - - if stdout: - self.update_status(f"{description} output:\n{stdout}") - - return process.returncode == 0 + original_dir = os.getcwd() + os.chdir(self.material_recog_dir) + + cmd = f'''call "{self.config_reader.config["condaDir"]}\\condabin\\activate.bat" {self.config_reader.config["materialEnv"]} && python split_img.py "{self.input_file_path}" && call "{self.config_reader.config["condaDir"]}\\condabin\\deactivate.bat"''' + + success, _ = run_command(self, cmd, self.update_status) + + os.chdir(original_dir) + + if success: + update_status_indicator(self.split_status, "Complete") + self.update_face_previews('rgb') + else: + update_status_indicator(self.split_status, "Failed") def run_material_recognition(self): if not os.path.exists(self.cubemap_dir): - messagebox.showwarning("Warning", "Please run Split 360 first") + QMessageBox.warning(self, "Warning", "Please run Split 360 first") return - try: - self.update_status("Running material recognition...") - self.recog_status.config(text="Running...", foreground="orange") - - # Save current directory - original_dir = os.getcwd() - - # Change to material recognition directory - os.chdir(self.material_recog_dir) - - # Combine conda activation and Python command into a single command using cmd /c - material_cmd = ( - f'cmd /c ""{self.config_reader.config["condaDir"]}\\Scripts\\activate.bat" {self.config_reader.config["materialEnv"]} && ' - f'python train_sota.py --data-root "./datasets" ' - f'--batch-size 1 --tag dpglt --gpus 1 --num-nodes 1 ' - f'--epochs 200 --mode 95 --seed 42 ' - f'--test "{self.checkpoint_file}" ' - f'--infer "{self.material_recog_dir}/cubemap_faces/"' - ) - - self.update_status(f"Executing command:\n{material_cmd}") - - process = subprocess.Popen( - material_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True, - text=True - ) - stdout, stderr = process.communicate() - - if stdout: - self.update_status("\nOutput:\n" + stdout) - if stderr: - self.update_status("\nError output:\n" + stderr) - - # Check error level - if process.returncode != 0: - self.update_status("Error: Material recognition failed. Please check the output above for more details.") - raise Exception(stderr if stderr else "Material recognition failed") - - self.update_status("Material recognition completed successfully") - self.recog_status.config(text="✓ Complete", foreground="green") - - except Exception as e: - self.update_status(f"Error in material recognition: {str(e)}") - self.recog_status.config(text="✗ Failed", foreground="red") - messagebox.showerror("Error", f"Material recognition failed: {str(e)}") - - finally: - self.update_material_face_previews() - if original_dir: - os.chdir(original_dir) - - def verify_input_directory(self): - """Verify that the input directory has the correct files""" - required_files = ['front.png', 'back.png', 'left.png', 'right.png', 'top.png', 'bottom.png'] - missing_files = [] - - for file in required_files: - if not os.path.exists(os.path.join(self.cubemap_dir, file)): - missing_files.append(file) - - if missing_files: - self.update_status("Missing required files:") - for file in missing_files: - self.update_status(f"- {file}") - return False - - self.update_status("All required input files present") - return True - - def verify_working_directory(self): - """Verify that we're in the correct directory with required files""" - required_paths = [ - "./datasets", - self.checkpoint_file, - "train_sota.py" - ] + self.update_status("Running material recognition...") + update_status_indicator(self.recognition_status, "Running") - missing_paths = [] - for path in required_paths: - if not os.path.exists(path): - missing_paths.append(path) - - if missing_paths: - self.update_status("Missing required paths:") - for path in missing_paths: - self.update_status(f"- {path}") - return False - - self.update_status("All required paths present") - return True - - # Add a method to verify conda environment - def verify_conda_env(self): - """Verify that the conda environment exists""" - try: - cmd = f'call "{self.config_reader.config["condaDir"]}\\condabin\\conda.bat" env list' - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True, - text=True - ) - stdout, stderr = process.communicate() - - if process.returncode == 0: - env_name = self.config_reader.config["materialEnv"] - if env_name in stdout: - self.update_status(f"Found conda environment: {env_name}") - return True - else: - self.update_status(f"Conda environment not found: {env_name}") - return False - else: - self.update_status(f"Failed to list conda environments: {stderr}") - return False - except Exception as e: - self.update_status(f"Error checking conda environment: {str(e)}") - return False + original_dir = os.getcwd() + os.chdir(self.material_recog_dir) + cmd = ( + f'cmd /c ""{self.config_reader.config["condaDir"]}\\Scripts\\activate.bat" {self.config_reader.config["materialEnv"]} && ' + f'python train_sota.py --data-root "./datasets" ' + f'--batch-size 1 --tag dpglt --gpus 1 --num-nodes 1 ' + f'--epochs 200 --mode 95 --seed 42 ' + f'--test "{self.checkpoint_file}" ' + f'--infer "{self.material_recog_dir}/cubemap_faces/"' + ) + + success, _ = run_command(self, cmd, self.update_status) + + os.chdir(original_dir) + + if success: + update_status_indicator(self.recognition_status, "Complete") + self.update_face_previews('material') + else: + update_status_indicator(self.recognition_status, "Failed") + def run_combine(self): - try: - self.update_status("Running combine step...") - self.combine_status.config(text="Running...", foreground="orange") - - # Change to material recognition directory - original_dir = os.getcwd() - os.chdir(self.material_recog_dir) - - # Run combine script - cmd = f'''call "{self.config_reader.config["condaDir"]}\\condabin\\activate.bat" {self.config_reader.config["materialEnv"]} && python combine_img.py && call "{self.config_reader.config["condaDir"]}\\condabin\\deactivate.bat"''' - - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True, - text=True + self.update_status("Running combine step...") + update_status_indicator(self.combine_status, "Running") + + original_dir = os.getcwd() + os.chdir(self.material_recog_dir) + + cmd = f'''call "{self.config_reader.config["condaDir"]}\\condabin\\activate.bat" {self.config_reader.config["materialEnv"]} && python combine_img.py && call "{self.config_reader.config["condaDir"]}\\condabin\\deactivate.bat"''' + + success, _ = run_command(self, cmd, self.update_status) + + os.chdir(original_dir) + + if success: + update_status_indicator(self.combine_status, "Complete") + output_path = os.path.join( + self.config_reader.directories['edgeNetDir'], + 'Data', 'Input', 'material.png' ) - stdout, stderr = process.communicate() - - # Change back to original directory - os.chdir(original_dir) - - if process.returncode == 0: - self.update_status("Combine step completed successfully") - self.combine_status.config(text="✓ Complete", foreground="green") - self.update_output_preview() - else: - self.update_status(f"Combine step failed:\n{stderr}") - self.combine_status.config(text="✗ Failed", foreground="red") - raise Exception(stderr) - - except Exception as e: - self.update_status(f"Error in combine step: {str(e)}") - self.combine_status.config(text="✗ Failed", foreground="red") - messagebox.showerror("Error", f"Combine step failed: {str(e)}") + update_preview(self.output_preview, output_path, + error_callback=self.update_status) + else: + update_status_indicator(self.combine_status, "Failed") def run_all_steps(self): - """Run all steps in sequence""" if not self.input_file_path: - messagebox.showwarning("Warning", "Please select an input file first") + QMessageBox.warning(self, "Warning", "Please select an input file first") return - try: - # Run all steps - self.run_split_360() - if self.split_status.cget("text") == "✓ Complete": - self.run_material_recognition() - if self.recog_status.cget("text") == "✓ Complete": - self.run_combine() - - except Exception as e: - self.update_status(f"Error in pipeline: {str(e)}") - messagebox.showerror("Error", f"Pipeline failed: {str(e)}") + self.run_split_360() + if get_status_text(self.split_status) == "Complete": + self.run_material_recognition() + if get_status_text(self.recognition_status) == "Complete": + self.run_combine() def update_status(self, message): - self.status_text.insert(tk.END, f"{message}\n") - self.status_text.see(tk.END) - - def clear_status(self): - self.status_text.delete(1.0, tk.END) + self.status_text.append(message) + scrollbar = self.status_text.verticalScrollBar() + scrollbar.setValue(scrollbar.maximum()) - def update_input_preview(self): - """Update the input image preview""" - if self.input_file_path and os.path.exists(self.input_file_path): - try: - preview_size = (300, 150) # Adjust size as needed for 360 image - input_img = Image.open(self.input_file_path) - input_img.thumbnail(preview_size) - input_photo = ImageTk.PhotoImage(input_img) - self.input_preview.configure(image=input_photo) - self.input_preview.image = input_photo - except Exception as e: - self.update_status(f"Failed to load input preview: {str(e)}") + def reset_status_indicators(self): + for status in [self.split_status, self.recognition_status, self.combine_status]: + update_status_indicator(status, "Not started") - def update_rgb_face_previews(self): - """Update the RGB cube face previews""" - preview_size = (150, 150) - face_names = { - 'front': 'front', - 'back': 'back', - 'left': 'left', - 'right': 'right', - 'top': 'top', - 'bottom': 'bottom' - } - - for face, filename in face_names.items(): - face_path = os.path.join(self.cubemap_dir, f"{filename}.png") - if os.path.exists(face_path): - try: - face_img = Image.open(face_path) - face_img.thumbnail(preview_size) - face_photo = ImageTk.PhotoImage(face_img) - self.rgb_face_previews[face].configure(image=face_photo) - self.rgb_face_previews[face].image = face_photo - except Exception as e: - self.update_status(f"Failed to load RGB {face} preview: {str(e)}") - - def update_material_face_previews(self): - """Update the material map cube face previews""" - preview_size = (150, 150) - material_dir = os.path.join(self.material_recog_dir, "output", "cubemap_faces") - face_names = { - 'frontrgb': 'frontrgb', - 'backrgb': 'backrgb', - 'leftrgb': 'leftrgb', - 'rightrgb': 'rightrgb', - 'toprgb': 'toprgb', - 'bottomrgb': 'bottomrgb' - } - - for face, filename in face_names.items(): - face_path = os.path.join(material_dir, f"{filename}.png") - if os.path.exists(face_path): - try: - face_img = Image.open(face_path) - face_img.thumbnail(preview_size) - face_photo = ImageTk.PhotoImage(face_img) - self.material_face_previews[face].configure(image=face_photo) - self.material_face_previews[face].image = face_photo - except Exception as e: - self.update_status(f"Failed to load material {face} preview: {str(e)}") - - def update_face_previews(self): - """Update both RGB and material cube face previews""" - self.update_rgb_face_previews() - self.update_material_face_previews() - """Update the material map cube face previews""" - preview_size = (150, 150) - material_dir = os.path.join(self.material_recog_dir, "output", "cubemap_faces") - face_names = { - 'front': 'front', - 'back': 'back', - 'left': 'left', - 'right': 'right', - 'top': 'top', - 'bottom': 'bottom' - } - - for face, filename in face_names.items(): - face_path = os.path.join(material_dir, f"{filename}.png") - if os.path.exists(face_path): - try: - face_img = Image.open(face_path) - face_img.thumbnail(preview_size) - face_photo = ImageTk.PhotoImage(face_img) - self.material_face_previews[face].configure(image=face_photo) - self.material_face_previews[face].image = face_photo - except Exception as e: - self.update_status(f"Failed to load material {face} preview: {str(e)}") - - def update_output_preview(self): - """Update the material map preview""" - try: - # First check in EdgeNet Input directory - output_path = os.path.join( - self.config_reader.directories['edgeNetDir'], - 'Data', - 'Input', - 'material.png' + def update_face_previews(self, preview_type='rgb'): + if preview_type == 'rgb': + update_face_previews( + self.rgb_face_previews, + self.cubemap_dir, + '.png', + self.update_status + ) + else: + update_face_previews( + self.material_face_previews, + self.material_output_dir, + 'rgb.png', + self.update_status ) - - if not os.path.exists(output_path): - self.update_status("Material map not found in expected location") - return - - preview_size = (300, 150) # Adjust size for 360 image - output_img = Image.open(output_path) - output_img.thumbnail(preview_size) - output_photo = ImageTk.PhotoImage(output_img) - self.output_preview.configure(image=output_photo) - self.output_preview.image = output_photo - - except Exception as e: - self.update_status(f"Failed to load material map preview: {str(e)}") def verify_checkpoint(self): - """Verify that the checkpoint file exists""" - if not os.path.exists(self.checkpoint_file): - self.update_status("Warning: Checkpoint file not found!") - self.checkpoint_label.config(text="✗ Missing", foreground="red") - return False - - self.checkpoint_label.config(text="✓ Found", foreground="green") - return True \ No newline at end of file + exists = os.path.exists(self.checkpoint_file) + self.info_labels["Checkpoint:"].setText("✓ Found" if exists else "✗ Missing") + self.info_labels["Checkpoint:"].setStyleSheet( + "color: green" if exists else "color: red") + return exists \ No newline at end of file diff --git a/scripts/debug_tool/utils/image_handlers.py b/scripts/debug_tool/utils/image_handlers.py index 497d1ea..e1cdcdd 100644 --- a/scripts/debug_tool/utils/image_handlers.py +++ b/scripts/debug_tool/utils/image_handlers.py @@ -74,4 +74,32 @@ def update_preview(preview_label, image_path, max_size=300, error_callback=None) else: preview_label.clear() if error_callback: - error_callback(f"Image not found: {image_path}") \ No newline at end of file + error_callback(f"Image not found: {image_path}") + +def update_face_previews(preview_dict, src_dir, suffix='', error_callback=None): + """ + Updates a dictionary of face preview labels with images from directory. + + Args: + preview_dict: Dictionary of face preview labels + src_dir: Directory containing face images + suffix: Suffix for face image filenames + error_callback: Optional callback function for error handling + """ + for face, preview in preview_dict.items(): + face_path = os.path.join(src_dir, face + suffix) + update_preview(preview, face_path, error_callback=error_callback) + +def clear_previews(*preview_widgets): + """ + Clears multiple preview widgets. + + Args: + preview_widgets: List of preview widgets to clear + """ + for widget in preview_widgets: + if isinstance(widget, dict): + for preview in widget.values(): + preview.clear() + else: + widget.clear() \ No newline at end of file diff --git a/scripts/debug_tool/utils/qt_widgets.py b/scripts/debug_tool/utils/qt_widgets.py index 85d49c4..6003e78 100644 --- a/scripts/debug_tool/utils/qt_widgets.py +++ b/scripts/debug_tool/utils/qt_widgets.py @@ -99,4 +99,87 @@ def create_preview_group(title): preview = QLabel() preview.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(preview) - return group, preview \ No newline at end of file + return group, preview + +def create_status_label(label_text, initial_status="Not started"): + """ + Creates a status indicator layout with label and status. + + Args: + label_text: Text for the label + initial_status: Initial status text + Returns: + QHBoxLayout layout with label and status + """ + layout = QHBoxLayout() + label = QLabel(label_text) + status = QLabel(initial_status) + layout.addWidget(label) + layout.addWidget(status, stretch=1) + return layout + +def create_preview_grid(face_names, preview_dict, cols=3): + """ + Creates a grid layout of preview groups for cubemap faces. + + Args: + face_names: List of face names + preview_dict: Dictionary to store preview widgets + cols: Number of columns + Returns: + QVBoxLayout layout with preview groups in grids for cubemap faces + """ + grid = QVBoxLayout() + row_layout = QHBoxLayout() + count = 0 + + for face in face_names: + group, preview = create_preview_group(face) + preview_dict[face] = preview + row_layout.addWidget(group) + count += 1 + + if count % cols == 0: + grid.addLayout(row_layout) + row_layout = QHBoxLayout() + + if count % cols != 0: + grid.addLayout(row_layout) + + return grid + +def update_status_indicator(status_layout, state): + """ + Updates a status indicator with new state and color. + + Args: + status_layout: QHBoxLayout layout with label and status + state: New status text + """ + label = status_layout.itemAt(1).widget() + states = { + "Running": ("Running...", "orange"), + "Complete": ("✓ Complete", "green"), + "Failed": ("✗ Failed", "red"), + "Not started": ("Not started", "black") + } + + if state in states: + text, color = states[state] + label.setText(text) + label.setStyleSheet(f"color: {color}") + else: + label.setText(state) + label.setStyleSheet("") + +def get_status_text(status_layout): + """ + Gets the current status text without markers. + + Args: + status_layout: QHBoxLayout layout with label and status + Returns: + str: Current status text + """ + text = status_layout.itemAt(1).widget().text() + return text.replace("✓ ", "").replace("✗ ", "") \ No newline at end of file -- GitLab