From 2d83b67dae157c07a8c6d16735fa40247e91e2d4 Mon Sep 17 00:00:00 2001 From: mhby1g21 <mhby1g21@soton.ac.uk> Date: Mon, 28 Oct 2024 16:06:15 +0000 Subject: [PATCH] added material recog tab with split/combine --- scripts/debug_tool/GUI_debug.py | 4 +- scripts/debug_tool/tabs/material_tab.py | 657 ++++++++++++++++++++++++ 2 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 scripts/debug_tool/tabs/material_tab.py diff --git a/scripts/debug_tool/GUI_debug.py b/scripts/debug_tool/GUI_debug.py index 0a68948..3ba5252 100644 --- a/scripts/debug_tool/GUI_debug.py +++ b/scripts/debug_tool/GUI_debug.py @@ -5,13 +5,14 @@ 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: def __init__(self): self.window = tk.Tk() self.window.title("Pipeline Debug Tool") - self.window.geometry("800x600") + self.window.geometry("1600x800") # Initialize paths self.DEBUG_DIR = os.path.dirname(os.path.abspath(__file__)) # debug_tool directory @@ -29,6 +30,7 @@ class ModuleDebugGUI: 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) def run(self): self.window.mainloop() diff --git a/scripts/debug_tool/tabs/material_tab.py b/scripts/debug_tool/tabs/material_tab.py new file mode 100644 index 0000000..c21c376 --- /dev/null +++ b/scripts/debug_tool/tabs/material_tab.py @@ -0,0 +1,657 @@ +import tkinter as tk +from tkinter import ttk, messagebox, filedialog +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') + + self.config_reader = config_reader + self.input_file_path = None + + # Setup paths + 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.setup_ui() + + 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) + + 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) + + # 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) + + def setup_preview_panel(self, parent): + notebook = ttk.Notebook(parent) + notebook.pack(fill=tk.BOTH, expand=True) + + # Input/Output preview tab + io_frame = ttk.Frame(notebook) + notebook.add(io_frame, text='Input/Output') + + # Input preview + input_frame = ttk.LabelFrame(io_frame, text="Input Image") + input_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + self.input_preview = ttk.Label(input_frame) + self.input_preview.pack(padx=5, pady=5) + + # Material map preview + output_frame = ttk.LabelFrame(io_frame, text="Material Map") + output_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + 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') + + # 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 + + # 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 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() + + 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")] + ) + 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") + + def run_split_360(self): + if not self.input_file_path: + messagebox.showwarning("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() + + 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 + + def run_material_recognition(self): + if not os.path.exists(self.cubemap_dir): + messagebox.showwarning("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" + ] + + 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 + + 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 + ) + 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)}") + + 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") + 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)}") + + 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) + + 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 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' + ) + + 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 -- GitLab