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