diff --git a/.gitignore b/.gitignore
index 8e1c9ff719ae01a8e81bc7dfdfc5bc50f90a8997..6b5efc082fa8b5f261078601f901ca8a784b7092 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 scripts/360monodepthexecution/rgb.jpg
 scripts/shifted_t.png
-scripts/config.ini
\ No newline at end of file
+scripts/config.ini
+*.pyc
\ No newline at end of file
diff --git a/Images/GUI.png b/Images/GUI.png
new file mode 100644
index 0000000000000000000000000000000000000000..cec8260f51fdddbe1468f9abcbd5681fc095f889
Binary files /dev/null and b/Images/GUI.png differ
diff --git a/Images/GUI_debug.jpg b/Images/GUI_debug.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..84cf1c61e1ec96929a1e62035cb4beb5c3da0e36
Binary files /dev/null and b/Images/GUI_debug.jpg differ
diff --git a/Images/Pipeline-Overview.png b/Images/Pipeline-Overview.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bc8a50b5d4226980c091eb7e9ac60ce73ba4d53
Binary files /dev/null and b/Images/Pipeline-Overview.png differ
diff --git a/README.md b/README.md
index c5437e52429f5f8cbe2751371f0946ace41a04e9..dd50a29fe0671ff674af6cf18f5431fb4b5e71e3 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,8 @@ The repository is structured as follows:
 - `Dynamic-Backward-Attention_Transformer/`: Submodule for material recognition using Dynamic Backward Attention Transformer
 - `RIR_Analysis`: Python notebook for sine sweep and deconvolution by Mona
 - `scripts/`: Automation and integration scripts
+  - `360monodepthexecution/`: Powershell automation scripts for docker (360monodepth)
+  - `debug_tool/`: Debug tools to run modules one by one
 - `Unity/`: 
   - `AV-VR/`: Main Unity project folder, extending GDP work
   - `S3A/`: Dr. Hansung's original Unity project for reference (Steam Audio integration, sound source positioning)
@@ -25,6 +27,7 @@ The repository is structured as follows:
 
 - `scripts/config.ini`: Modify the value in this file to fit system
 - `scripts/GUI.py`: Main script to be run after following the setup instructions
+- `scripts/debug_tool/GUI_debug.py`: Main script for debugging the module one by one
 - `AVVR-Papers/report.pdf`: 23/24 GDP's report
 - `Manual.docx` / `Manual.pdf`: User manual provided by the GDP group
 - `Intern-logs/Internship-Report.pdf`: 10-week internship technical report
@@ -99,7 +102,7 @@ python GUI.py
 
 6. GUI choices 
 
-![GUI](Intern-Logs/Readme_Images/GUI.png)
+![GUI](Images/GUI.png)
 - Tick Create depth map, tick include Top for mesh (.obj) with ceiling.
 - Choose image from different scenes folder (KT, ST, UL, MR, LR) in edgenet360/Data
 - The pipeline should run for about 5-15 minutes depending on the system spec.
@@ -107,9 +110,14 @@ python GUI.py
 
 Refer to Manual.pdf for detailed prerequisites and setup instructions for the ML pipeline if needed and Unity VR rendering.
 
+7. Debug GUI to run modules one by one for troubleshooting
+
+![Debug GUI](Images/GUI_debug.jpg)
+- Save time by not needing to run all modules sequentially to isolate error faster.
+- Easier to understand the underlying input and output of each modules.
 ## Pipeline Overview
 
-![image](Intern-Logs/Readme_Images/Pipeline-Overview.png)
+![Pipeline Overview](Images/Pipeline-Overview.png)
 
 ## Video Demonstration
 
diff --git a/scripts/debug_tool/GUI_debug.py b/scripts/debug_tool/GUI_debug.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ce869060e8f77cfaeba0c421f9912e24c4b7967
--- /dev/null
+++ b/scripts/debug_tool/GUI_debug.py
@@ -0,0 +1,54 @@
+from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QTabWidget
+from PyQt6.QtCore import Qt
+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(QMainWindow):
+    def __init__(self):
+        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
+        self.SCRIPT_DIR = os.path.dirname(self.DEBUG_DIR)            # scripts directory
+        self.ROOT_DIR = os.path.dirname(self.SCRIPT_DIR)
+        
+        # Read configuration
+        self.config_reader = ConfigReader(self.DEBUG_DIR, self.ROOT_DIR)
+        
+        # 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)
+        
+        # Initialize tabs
+        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")
+        self.tabs.addTab(EdgeNetTab(self.config_reader), "EdgeNet Execution")
+
+def main():
+    app = QApplication(sys.argv)
+    window = ModuleDebugGUI()
+    window.show()
+    sys.exit(app.exec())
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/scripts/debug_tool/__init__.py b/scripts/debug_tool/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/debug_tool/tabs/__init__.py b/scripts/debug_tool/tabs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/debug_tool/tabs/config_tab.py b/scripts/debug_tool/tabs/config_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..17d9ad1f4605686a8f8648f177a55d33c31f2480
--- /dev/null
+++ b/scripts/debug_tool/tabs/config_tab.py
@@ -0,0 +1,122 @@
+# 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(QWidget):
+    def __init__(self, config_reader):
+        super().__init__()
+        self.config_reader = config_reader
+        self.setup_ui()
+        
+    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.display_config()
+        self.display_directories()
+        self.display_files()
+        self.verify_paths()
+
+    def display_config(self):
+        self.config_text.clear()
+        for key, value in self.config_reader.config.items():
+            self.config_text.append(f"{key} = {value}")
+
+    def display_directories(self):
+        self.dir_text.clear()
+        for key, path in self.config_reader.directories.items():
+            self.dir_text.append(f"{key}: {path}")
+
+    def display_files(self):
+        self.file_text.clear()
+        for key, path in self.config_reader.file_paths.items():
+            self.file_text.append(f"{key}: {path}")
+
+    def verify_paths(self):
+        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"))
+        
+        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"
+            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"
+            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"
+        
+        debug_info += "=== Config Values ===\n"
+        for key, value in self.config_reader.config.items():
+            debug_info += f"{key} = {value}\n"
+            
+        debug_info += "\n=== Directory Paths ===\n"
+        for key, path in self.config_reader.directories.items():
+            exists = os.path.exists(path)
+            status = "exists" if exists else "missing"
+            debug_info += f"{key}: {path} ({status})\n"
+            
+        debug_info += "\n=== File Paths ===\n"
+        for key, path in self.config_reader.file_paths.items():
+            exists = os.path.exists(path)
+            status = "exists" if exists else "missing"
+            debug_info += f"{key}: {path} ({status})\n"
+        
+        try:
+            debug_path = os.path.join(self.config_reader.directories['debugDir'], 
+                                    "debug_config_info.txt")
+            with open(debug_path, "w") as f:
+                f.write(debug_info)
+            QMessageBox.information(self, "Success", 
+                                f"Debug information saved to:\n{debug_path}")
+        except Exception as e:
+            QMessageBox.critical(self, "Error", 
+                             f"Failed to save debug info: {str(e)}")
\ No newline at end of file
diff --git a/scripts/debug_tool/tabs/depth_tab.py b/scripts/debug_tool/tabs/depth_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..30da9e51ae17ff8b1b2e3a9cddced0aa26365fb0
--- /dev/null
+++ b/scripts/debug_tool/tabs/depth_tab.py
@@ -0,0 +1,158 @@
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, 
+                           QGroupBox, QMessageBox)
+from PyQt6.QtCore import Qt
+import os
+
+from utils.qt_widgets import (create_group_with_text, create_button_layout, 
+                            create_info_group, create_preview_group)
+from utils.file_handlers import select_file, clean_directory, copy_file, run_command
+from utils.image_handlers import update_preview
+
+class DepthTab(QWidget):
+    def __init__(self, config_reader):
+        super().__init__()
+        self.config_reader = config_reader
+        self.depth_input_path = None
+        
+        # Initialize paths
+        self.copy_dest_path = os.path.join(
+            self.config_reader.directories['monoDepthDir'], 
+            'rgb.jpg'
+        )
+        self.depth_output_path = os.path.join(
+            self.config_reader.directories['edgeNetDir'],
+            'Data', 'Input', 'depth_e.png'
+        )
+        self.input_dir = os.path.join(
+            self.config_reader.directories['edgeNetDir'],
+            'Data', 'Input'
+        )
+        
+        self.setup_ui()
+
+    def setup_ui(self):
+        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, layout):
+        # Info display
+        info_rows = [
+            ("Input file:", "No file selected"),
+            ("Copy destination:", self.copy_dest_path),
+            ("Depth map output:", self.depth_output_path)
+        ]
+        info_group, self.info_labels = create_info_group("Controls", info_rows)
+        layout.addWidget(info_group)
+        
+        # Buttons
+        buttons = [
+            ("Select Input", self.handle_file_select, 'left'),
+            ("Clean Data/Input Dir", self.clean_input_dir, 'left'),
+            ("Remove rgb.png from dest path", self.remove_rgb, 'left'),
+            ("Copy File", self.copy_file, 'left'),
+            ("Run Depth Est.", self.run_depth_estimation, '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, layout):
+        preview_group = QGroupBox("Image Previews")
+        preview_layout = QHBoxLayout(preview_group)
+        
+        input_group, self.input_preview = create_preview_group("RGB Image")
+        output_group, self.output_preview = create_preview_group("Depth Map")
+        
+        preview_layout.addWidget(input_group)
+        preview_layout.addWidget(output_group)
+        layout.addWidget(preview_group)
+
+    def handle_file_select(self):
+        file_path = select_file(
+            self,
+            "Select Input Image",
+            "Images (*.png *.jpg *.jpeg)"
+        )
+        
+        if file_path:
+            self.depth_input_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)
+
+    def remove_rgb(self):   
+        rgb_path = self.copy_dest_path
+        if os.path.exists(rgb_path):
+            os.remove(rgb_path)
+            self.update_status("Removed rgb.png")
+        else:
+            self.update_status("rgb.png not found")
+
+    def clean_input_dir(self):
+        success = clean_directory(self.input_dir, self.update_status)
+        if success:
+            self.output_preview.clear()
+        else:
+            QMessageBox.critical(self, "Error", "Failed to clean input directory")
+
+    def copy_file(self):
+        if not self.depth_input_path:
+            QMessageBox.warning(self, "Warning", "Please select an input file first")
+            return
+
+        # Copy to monodepth directory
+        if not copy_file(self.depth_input_path, self.copy_dest_path, self.update_status):
+            QMessageBox.critical(self, "Error", "Failed to copy file to monodepth directory")
+            return
+
+        # Copy to edgenet directory
+        edge_rgb_path = os.path.join(self.input_dir, 'rgb.png')
+        if not copy_file(self.depth_input_path, edge_rgb_path, self.update_status):
+            QMessageBox.critical(self, "Error", "Failed to copy file to edgenet directory")
+
+    def run_depth_estimation(self):
+        try:
+            self.update_status("Running depth estimation...")
+            
+            # Change to mono depth directory and run script
+            original_dir = os.getcwd()
+            os.chdir(self.config_reader.directories['monoDepthDir'])
+            
+            success, output = run_command(
+                self,
+                "powershell.exe -File masterscript.ps1",
+                self.update_status
+            )
+            
+            # Change back to original directory
+            os.chdir(original_dir)
+            
+            if success and os.path.exists(self.depth_output_path):
+                self.update_status(f"Depth map generated at: {self.depth_output_path}")
+                update_preview(self.output_preview, self.depth_output_path, 
+                             error_callback=self.update_status)
+                
+        except Exception as e:
+            error_msg = f"Failed to run depth estimation: {str(e)}"
+            self.update_status(error_msg)
+            QMessageBox.critical(self, "Error", error_msg)
+
+    def update_status(self, message):
+        self.status_text.append(message)
+        # Scroll to bottom
+        scrollbar = self.status_text.verticalScrollBar()
+        scrollbar.setValue(scrollbar.maximum())
\ No newline at end of file
diff --git a/scripts/debug_tool/tabs/edge_net_tab.py b/scripts/debug_tool/tabs/edge_net_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc888bed9a226847193e7eecfc4546fc2c1e37dc
--- /dev/null
+++ b/scripts/debug_tool/tabs/edge_net_tab.py
@@ -0,0 +1,518 @@
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, 
+                           QPushButton, QCheckBox, QProgressBar, QTextEdit,
+                           QMessageBox, QFileDialog)
+from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
+from PyQt6.QtGui import QPixmap
+import os
+import threading
+import queue
+import time
+import sys
+
+from utils.file_handlers import (select_file, run_command, clean_directory, 
+                               copy_file)
+from utils.image_handlers import update_preview, clear_previews
+from utils.qt_widgets import (create_group_with_text, create_button_layout,
+                            create_preview_group, create_status_label,
+                            update_status_indicator, show_confirmation_dialog)
+
+class ProcessThread(QThread):
+    finished = pyqtSignal(bool, str)
+    progress = pyqtSignal(str)
+    
+    def __init__(self, func, *args, **kwargs):
+        super().__init__()
+        self.func = func
+        self.args = args
+        self.kwargs = kwargs
+        self.running = True
+        
+    def run(self):
+        try:
+            if self.running:
+                result = self.func(*self.args, **self.kwargs)
+                self.finished.emit(True, "")
+        except Exception as e:
+            self.finished.emit(False, str(e))
+            
+    def stop(self):
+        self.running = False
+        self.wait()
+
+class EdgeNetTab(QWidget):
+    def __init__(self, config_reader):
+        super().__init__()
+        self.config_reader = config_reader
+        self.setup_paths()
+        self.init_variables()
+        self.init_ui()
+        self.connect_signals()
+        self.current_thread = None
+        
+    def closeEvent(self, event):
+        """Handle widget close event"""
+        if self.current_thread and self.current_thread.isRunning():
+            self.current_thread.stop()
+            self.current_thread.wait()
+        event.accept()
+        
+    def setup_paths(self):
+        """Initialize directory and file paths"""
+        self.edge_net_dir = self.config_reader.directories['edgeNetDir']
+        self.output_dir = self.config_reader.directories['outputDir'] 
+        self.input_dir = os.path.join(self.edge_net_dir, "Data", "Input")
+        
+    def init_variables(self):
+        """Initialize class variables"""
+        self.include_top = False
+        self.is_processing = False
+        self.manual_inputs = {
+            'depth_e.png': None,
+            'rgb.png': None,
+            'material.png': None
+        }
+        self.output_queue = queue.Queue()
+        
+    def init_ui(self):
+        """Initialize user interface"""
+        layout = QVBoxLayout()
+        
+        # Split into left and right sections
+        hlayout = QHBoxLayout()
+        left_panel = self.create_left_panel()
+        right_panel = self.create_right_panel()
+        
+        hlayout.addWidget(left_panel)
+        hlayout.addWidget(right_panel)
+        layout.addLayout(hlayout)
+        
+        # Initialize progress bar to stopped state
+        self.progress_bar.setMaximum(100)
+        self.progress_bar.setValue(0)
+        
+        self.setLayout(layout)
+        
+    def create_left_panel(self):
+        """Create left control panel"""
+        widget = QWidget()
+        layout = QVBoxLayout()
+        
+        # Input files section
+        files_group = self.create_input_files_group()
+        layout.addWidget(files_group)
+        
+        # Include top checkbox with Qt6 state
+        self.top_checkbox = QCheckBox("Include Top in Mesh")
+        self.top_checkbox.setCheckState(Qt.CheckState.Unchecked)
+        self.top_checkbox.stateChanged.connect(self.on_include_top_changed)
+        layout.addWidget(self.top_checkbox)
+        
+        # Status section
+        self.status_group, self.status_text = create_group_with_text("Status")
+        layout.addWidget(self.status_group)
+        
+        # Progress section
+        progress_group = self.create_progress_group()
+        layout.addWidget(progress_group)
+        
+        # Control buttons
+        button_layout = self.create_control_buttons()
+        layout.addLayout(button_layout)
+        
+        widget.setLayout(layout)
+        return widget
+        
+    def create_input_files_group(self):
+        """Create input files selection group"""
+        group = QWidget()
+        layout = QVBoxLayout()
+        
+        # File selection buttons
+        for file_type in self.manual_inputs.keys():
+            row = QHBoxLayout()
+            row.addWidget(QLabel(f"{file_type}:"))
+            label = QLabel("Using default")
+            setattr(self, f"{file_type.split('.')[0]}_label", label)
+            row.addWidget(label)
+            
+            btn = QPushButton("Select")
+            btn.clicked.connect(lambda checked, ft=file_type: self.select_input_file(ft))
+            row.addWidget(btn)
+            
+            layout.addLayout(row)
+            
+        # Reset button
+        reset_btn = QPushButton("Reset to Default Inputs")
+        reset_btn.clicked.connect(self.reset_inputs)
+        layout.addWidget(reset_btn)
+        
+        group.setLayout(layout)
+        return group
+        
+    def create_progress_group(self):
+        """Create progress indicators group"""
+        group = QWidget()
+        layout = QVBoxLayout()
+        
+        # Status indicators
+        self.edge_status = create_status_label("EdgeNet:")
+        self.split_status = create_status_label("Mesh Split:")
+        self.flip_status = create_status_label("Blender Flip:")
+        
+        layout.addLayout(self.edge_status)
+        layout.addLayout(self.split_status)
+        layout.addLayout(self.flip_status)
+        
+        # Progress bar
+        self.progress_bar = QProgressBar()
+        layout.addWidget(self.progress_bar)
+        
+        # Operation label
+        self.operation_label = QLabel()
+        layout.addWidget(self.operation_label)
+        
+        group.setLayout(layout)
+        return group
+        
+    def create_right_panel(self):
+        """Create right preview panel"""
+        widget = QWidget()
+        layout = QVBoxLayout()
+        
+        # Preview groups
+        depth_group, self.depth_preview = create_preview_group("Enhanced Depth")
+        mesh_group, self.mesh_preview = create_preview_group("Generated Mesh")
+        
+        layout.addWidget(depth_group)
+        layout.addWidget(mesh_group)
+        
+        widget.setLayout(layout)
+        return widget
+        
+    def create_control_buttons(self):
+        """Create control buttons layout"""
+        return create_button_layout(
+            ("Run EdgeNet", self.run_edge_net, 'left'),
+            ("Run Mesh Split", self.run_mesh_split, 'left'),
+            ("Run Blender Flip", self.run_blender_flip, 'left'),
+            ("Run All Steps", self.run_all_steps, 'left'),
+            ("Clean Output", self.clean_output_directory, 'right'),
+            ("Clean All", self.clean_all_files, 'right')
+        )
+        
+    def connect_signals(self):
+        """Connect signals and slots"""
+        # Add any additional signal connections here
+        pass
+        
+    def select_input_file(self, file_type):
+        """Handle input file selection"""
+        file_path = select_file(self, "Select Input File", "Image Files (*.png)", initial_dir=self.input_dir)
+        if file_path:
+            self.manual_inputs[file_type] = file_path
+            label = getattr(self, f"{file_type.split('.')[0]}_label")
+            label.setText(os.path.basename(file_path))
+            self.update_status(f"Selected {file_type}: {file_path}")
+            
+    def reset_inputs(self):
+        """Reset input selections to default"""
+        self.manual_inputs = {k: None for k in self.manual_inputs}
+        for file_type in self.manual_inputs:
+            label = getattr(self, f"{file_type.split('.')[0]}_label")
+            label.setText("Using default")
+        self.update_status("Reset all inputs to default")
+        
+    def run_edge_net(self):
+        """Run EdgeNet processing"""
+        if self.is_processing:
+            QMessageBox.warning(self, "Warning", "A process is already running!")
+            return
+            
+        self.is_processing = True
+        self.progress_bar.setMaximum(0)
+        
+        self.current_thread = ProcessThread(self._run_edge_net_process)
+        self.current_thread.finished.connect(self.on_edge_net_complete)
+        self.current_thread.progress.connect(self.update_status)
+        self.current_thread.start()
+        
+    def clean_output_directory(self):
+        if show_confirmation_dialog(self, "Confirm Clean", "Clean output directory?"):
+            if clean_directory(self.output_dir, self.update_status):
+                self.update_status("Output directory cleaned")
+                update_status_indicator(self.split_status, "Not started")
+                update_status_indicator(self.flip_status, "Not started")
+                clear_previews(self.mesh_preview)
+
+    def clean_all_files(self):
+        if show_confirmation_dialog(self, "Confirm Clean", "Clean all files?"):
+            directories = [self.input_dir, self.output_dir]
+            for directory in directories:
+                clean_directory(directory, self.update_status)
+            
+            # remove monodepth image copied in 360monodepth if exists
+            if os.path.exists(os.path.join(self.config_reader.directories['monoDepthDir'], 'rgb.jpg')):
+                monodepth_image = os.path.join(self.config_reader.directories['monoDepthDir'], 'rgb.jpg')
+                os.remove(monodepth_image)
+            # remove shifted image from shifter if exists
+            if os.path.exists(os.path.join(self.config_reader.directories['scriptDir'], 'shifted_t.png')):
+                shifted_image = os.path.join(self.config_reader.directories['scriptDir'], 'shifted_t.png')
+                os.remove(shifted_image)
+            
+            update_status_indicator(self.edge_status, "Not started")
+            update_status_indicator(self.split_status, "Not started")
+            update_status_indicator(self.flip_status, "Not started")
+            clear_previews(self.depth_preview, self.mesh_preview)
+
+    def update_status(self, message):
+        """Update status text"""
+        self.status_text.append(message)
+        
+    def on_include_top_changed(self, state):
+        """Handle include top checkbox change"""
+        # In PyQt6, CheckState.Checked has value 2
+        self.include_top = (state == 2)  # or state == Qt.CheckState.Checked
+        self.update_status(f"Include top changed to: {self.include_top}")
+        
+    def verify_inputs(self):
+        """Verify input files exist"""
+        required_files = {
+            'depth_e.png': self.get_input_path('depth_e.png'),
+            'rgb.png': self.get_input_path('rgb.png')
+        }
+        
+        missing = [f for f, p in required_files.items() if not os.path.exists(p)]
+        if missing:
+            self.update_status("Missing required files: " + ", ".join(missing))
+            return False
+        return True
+        
+    def get_input_path(self, file_type):
+        """Get path for input file"""
+        return self.manual_inputs.get(file_type) or os.path.join(self.input_dir, file_type)
+        
+    def _run_edge_net_process(self):
+        """Run EdgeNet processing"""
+        # Change to EdgeNet directory
+        os.chdir(self.edge_net_dir)
+        try:
+            if not self.verify_inputs():
+                self.copy_input_files()
+                if not self.verify_inputs():
+                    raise Exception("Missing required input files")
+                    
+            # Run enhance360.py
+            enhance_cmd = self._build_enhance_command()
+            if run_command(self, enhance_cmd, self.update_status)[0]:
+                # Run infer360.py
+                infer_cmd = self._build_infer_command()
+                return run_command(self, infer_cmd, self.update_status)[0]
+            return False
+            
+        except Exception as e:
+            self.update_status(f"Error: {str(e)}")
+            return False
+            
+    def _build_enhance_command(self):
+        """Build enhance360.py command"""
+        return (f'wsl bash -c "source {self.config_reader.config["wslAnacondaDir"]}/activate'
+                f' {self.config_reader.config["edgeNetEnv"]} && '
+                f'python enhance360.py Input depth_e.png rgb.png enhanced_depth_e.png"')
+                
+    def _build_infer_command(self):
+        """Build infer360.py command"""
+        # Debug print to verify include_top state
+        self.update_status(f"Current include_top state: {self.include_top}")
+        
+        base_cmd = (f'wsl bash -c "source {self.config_reader.config["wslAnacondaDir"]}/activate'
+                   f' {self.config_reader.config["edgeNetEnv"]} && '
+                   f'python infer360.py Input enhanced_depth_e.png material.png rgb.png Input')
+        
+        if self.include_top:
+            command = base_cmd + ' --include_top y"'
+        else:
+            command = base_cmd + '"'
+        
+        # Log final command
+        self.update_status(f"Final command: {command}")
+        
+        return command
+                
+    def on_edge_net_complete(self, success, error_message):
+        """Handle EdgeNet completion"""
+        self.is_processing = False
+        self.progress_bar.setMaximum(100)
+        
+        if success:
+            update_status_indicator(self.edge_status, "Complete")
+            self.update_depth_preview()
+        else:
+            update_status_indicator(self.edge_status, "Failed")
+            QMessageBox.critical(self, "Error", f"EdgeNet failed: {error_message}")
+            
+    def update_depth_preview(self):
+        """Update depth preview image"""
+        depth_path = os.path.join(self.input_dir, "enhanced_depth_e.png")
+        update_preview(self.depth_preview, depth_path, 300)
+
+    def run_mesh_split(self):
+        if self.is_processing:
+            QMessageBox.warning(self, "Warning", "A process is already running!")
+            return
+            
+        self.is_processing = True
+        self.progress_bar.setMaximum(0)
+        
+        self.current_thread = ProcessThread(self._run_mesh_split_process)
+        self.current_thread.finished.connect(self.on_mesh_split_complete)
+        self.current_thread.progress.connect(self.update_status)
+        self.current_thread.start()
+
+    def _run_mesh_split_process(self):
+        """Execute mesh splitting process"""
+        # Change to EdgeNet directory
+        os.chdir(self.edge_net_dir)
+        try:
+            if not os.path.exists(os.path.join(self.output_dir, "Input_prediction.obj")):
+                raise Exception("Missing Input_prediction.obj file")
+                
+            cmd = (f'call "{self.config_reader.config["condaDir"]}\\Scripts\\activate.bat" '
+                  f'{self.config_reader.config["materialEnv"]} && '
+                  f'python replace.py')
+                  
+            return run_command(self, cmd, self.update_status)[0]
+            
+        except Exception as e:
+            self.update_status(f"Error: {str(e)}")
+            return False
+
+    def on_mesh_split_complete(self, success, error_message):
+        """Handle mesh split completion"""
+        self.is_processing = False
+        self.progress_bar.setMaximum(100)
+        
+        if success:
+            update_status_indicator(self.split_status, "Complete")
+            self.update_mesh_preview()
+        else:
+            update_status_indicator(self.split_status, "Failed")
+            QMessageBox.critical(self, "Error", f"Mesh splitting failed: {error_message}")
+
+    def run_blender_flip(self):
+        if self.is_processing:
+            QMessageBox.warning(self, "Warning", "A process is already running!")
+            return
+            
+        self.is_processing = True
+        self.progress_bar.setMaximum(0)
+        
+        self.current_thread = ProcessThread(self._run_blender_flip_process)
+        self.current_thread.finished.connect(self.on_blender_flip_complete)
+        self.current_thread.progress.connect(self.update_status)
+        self.current_thread.start()
+
+    def _run_blender_flip_process(self):
+        """Execute Blender flip process"""
+        # Change to scripts directory
+        os.chdir(self.config_reader.directories['scriptDir'])
+        try:
+            mesh_path = os.path.join(self.output_dir, "Input_prediction_mesh.obj")
+            if not os.path.exists(mesh_path):
+                raise Exception("Missing Input_prediction_mesh.obj file")
+                
+            cmd = (f'call "{self.config_reader.config["condaDir"]}\\Scripts\\activate.bat" '
+                  f'{self.config_reader.config["unityEnv"]} && '
+                  f'python blenderFlip.py "{mesh_path}"')
+                  
+            return run_command(self, cmd, self.update_status)[0]
+            
+        except Exception as e:
+            self.update_status(f"Error: {str(e)}")
+            return False
+
+    def on_blender_flip_complete(self, success, error_message):
+        """Handle Blender flip completion"""
+        self.is_processing = False
+        self.progress_bar.setMaximum(100)
+        
+        if success:
+            update_status_indicator(self.flip_status, "Complete")
+            self.update_mesh_preview()
+        else:
+            update_status_indicator(self.flip_status, "Failed")
+            QMessageBox.critical(self, "Error", f"Blender flip failed: {error_message}")
+
+    def run_all_steps(self):
+        if self.is_processing:
+            QMessageBox.warning(self, "Warning", "A process is already running!")
+            return
+
+        self.is_processing = True
+        self.progress_bar.setMaximum(0)  # Set to indeterminate mode
+
+        self.current_thread = ProcessThread(self._run_all_steps_process)
+        self.current_thread.finished.connect(self.on_all_steps_complete)
+        self.current_thread.progress.connect(self.update_status)
+        self.current_thread.start()
+
+    def _run_all_steps_process(self):
+        """Execute complete pipeline process"""
+        try:
+            self.update_status("Starting complete pipeline processing...")
+            
+            # Run EdgeNet
+            self.update_status("Running EdgeNet...")
+            update_status_indicator(self.edge_status, "Running")
+            if not self._run_edge_net_process():
+                raise Exception("EdgeNet processing failed")
+                
+            # Run Mesh Split
+            self.update_status("Running Mesh Split...")
+            update_status_indicator(self.split_status, "Running")
+            if not self._run_mesh_split_process():
+                raise Exception("Mesh splitting failed")
+                
+            # Run Blender Flip
+            self.update_status("Running Blender Flip...")
+            update_status_indicator(self.flip_status, "Running")
+            if not self._run_blender_flip_process():
+                raise Exception("Blender flip failed")
+                
+            return True
+            
+        except Exception as e:
+            self.update_status(f"Pipeline error: {str(e)}")
+            return False
+
+    def on_all_steps_complete(self, success, error_message):
+        """Handle complete pipeline completion"""
+        self.is_processing = False
+        self.progress_bar.setMaximum(100)
+        self.progress_bar.setValue(0)
+
+        if success:
+            self.update_status("Complete pipeline processing finished successfully!")
+            update_status_indicator(self.edge_status, "Complete")
+            update_status_indicator(self.split_status, "Complete") 
+            update_status_indicator(self.flip_status, "Complete")
+            QMessageBox.information(self, "Success", 
+                                  "All processing steps completed successfully!")
+        else:
+            self.update_status(f"Pipeline failed: {error_message}")
+            QMessageBox.critical(self, "Error", f"Pipeline failed: {error_message}")
+
+    def update_mesh_preview(self):
+        """Update mesh preview image"""
+        # Implement mesh preview rendering
+        # This could involve taking a screenshot of the mesh
+        # or loading a pre-rendered preview image
+        pass
+
+    def copy_input_files(self):
+        """Copy manual input files to input directory"""
+        os.makedirs(self.input_dir, exist_ok=True)
+        
+        for file_type, path in self.manual_inputs.items():
+            if path:
+                dest = os.path.join(self.input_dir, file_type)
+                copy_file(path, dest, self.update_status)
diff --git a/scripts/debug_tool/tabs/material_tab.py b/scripts/debug_tool/tabs/material_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..b38111daed8eeae8998acc77a474f9f2c6a22683
--- /dev/null
+++ b/scripts/debug_tool/tabs/material_tab.py
@@ -0,0 +1,271 @@
+# tabs/material_tab.py
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                           QMessageBox, QTabWidget)
+from PyQt6.QtCore import Qt
+import os
+
+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
+        
+        # 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.material_output_dir = os.path.join(self.material_recog_dir, "output", "cubemap_faces")
+        
+        self.setup_ui()
+        self.verify_checkpoint()
+
+    def setup_ui(self):
+        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, 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
+        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, layout):
+        preview_tabs = QTabWidget()
+        
+        # Input/Output preview tab
+        io_tab = QWidget()
+        io_layout = QVBoxLayout(io_tab)
+        
+        input_group, self.input_preview = create_preview_group("Input Image")
+        output_group, self.output_preview = create_preview_group("Material Map")
+        
+        io_layout.addWidget(input_group)
+        io_layout.addWidget(output_group)
+        preview_tabs.addTab(io_tab, "Input/Output")
+        
+        # RGB faces preview tab
+        rgb_tab = QWidget()
+        rgb_layout = QVBoxLayout(rgb_tab)
+        
+        self.rgb_face_previews = {}
+        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)
+        
+        self.material_face_previews = {}
+        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)
+
+    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):
+        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
+            
+        clear_previews(
+            self.input_preview, 
+            self.output_preview,
+            self.rgb_face_previews,
+            self.material_face_previews
+        )
+        self.reset_status_indicators()
+
+    def run_split_360(self):
+        if not self.input_file_path:
+            QMessageBox.warning(self, "Warning", "Please select an input file first")
+            return
+            
+        self.update_status("Running 360 image splitting...")
+        update_status_indicator(self.split_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 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):
+            QMessageBox.warning(self, "Warning", "Please run Split 360 first")
+            return
+            
+        self.update_status("Running material recognition...")
+        update_status_indicator(self.recognition_status, "Running")
+        
+        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):
+        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'
+            )
+            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):
+        if not self.input_file_path:
+            QMessageBox.warning(self, "Warning", "Please select an input file first")
+            return
+            
+        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.append(message)
+        scrollbar = self.status_text.verticalScrollBar()
+        scrollbar.setValue(scrollbar.maximum())
+
+    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_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
+            )
+
+    def verify_checkpoint(self):
+        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/tabs/shifter_tab.py b/scripts/debug_tool/tabs/shifter_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..d532b5d8784654212562dd14723ce6a0724ab98e
--- /dev/null
+++ b/scripts/debug_tool/tabs/shifter_tab.py
@@ -0,0 +1,121 @@
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, 
+                           QGroupBox, QMessageBox)
+from PyQt6.QtCore import Qt
+import os
+
+from utils.qt_widgets import (create_group_with_text, create_button_layout, 
+                            create_info_group, create_preview_group)
+from utils.file_handlers import select_file, run_command
+from utils.image_handlers import update_preview
+
+class ShifterTab(QWidget):
+    def __init__(self, config_reader):
+        super().__init__()
+        self.config_reader = config_reader
+        self.input_file_path = None
+        self.shifted_image_path = os.path.join(
+            self.config_reader.directories['scriptDir'], 
+            "shifted_t.png"
+        )
+        self.setup_ui()
+
+    def setup_ui(self):
+        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, layout):
+        # Info display
+        info_rows = [
+            ("Input file:", "No file selected"),
+            ("Output file:", self.shifted_image_path),
+            ("Environment:", self.config_reader.config.get('materialEnv', 'Not found'))
+        ]
+        info_group, self.info_labels = create_info_group("Controls", info_rows)
+        layout.addWidget(info_group)
+        
+        # Command preview
+        cmd_group, self.cmd_text = create_group_with_text("Command Preview", 80)
+        layout.addWidget(cmd_group)
+        
+        # Buttons
+        buttons = [
+            ("Select Input", self.handle_file_select, 'left'),
+            ("Run Shifter", self.run_shifter, '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)
+        
+        self.update_command_preview()
+
+    def setup_preview_panel(self, layout):
+        preview_layout = QVBoxLayout()
+        
+        # Split into two preview groups
+        preview_group = QGroupBox("Image Previews")
+        previews_layout = QHBoxLayout(preview_group)
+        
+        input_group, self.input_preview = create_preview_group("Input Image")
+        output_group, self.output_preview = create_preview_group("Shifted Image")
+        
+        previews_layout.addWidget(input_group)
+        previews_layout.addWidget(output_group)
+        layout.addWidget(preview_group)
+
+    def handle_file_select(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}")
+            self.update_command_preview()
+            update_preview(self.input_preview, file_path, error_callback=self.update_status)
+
+    def update_command_preview(self):
+        if not self.input_file_path:
+            self.cmd_text.setText("Select an input file to see the command")
+            return
+            
+        cmd = f'''call "{self.config_reader.config["condaDir"]}\\condabin\\activate.bat" {self.config_reader.config["materialEnv"]} && python "{os.path.join(self.config_reader.directories['scriptDir'], "shifter.py")}" "{self.input_file_path}" "{self.shifted_image_path}" && call "{self.config_reader.config["condaDir"]}\\condabin\\deactivate.bat"'''
+        
+        self.cmd_text.setText(cmd)
+
+    def update_status(self, message):
+        self.status_text.append(message)
+        scrollbar = self.status_text.verticalScrollBar()
+        scrollbar.setValue(scrollbar.maximum())
+
+    def run_shifter(self):
+        if not self.input_file_path:
+            QMessageBox.warning(self, "Warning", "Please select an input file first")
+            return
+        
+        self.update_status("\nRunning image shifter...")
+        success, _ = run_command(
+            self, 
+            self.cmd_text.toPlainText(),
+            self.update_status
+        )
+        
+        if success and os.path.exists(self.shifted_image_path):
+            self.update_status(f"Shifted image saved to: {self.shifted_image_path}")
+            update_preview(self.output_preview, self.shifted_image_path, 
+                         error_callback=self.update_status)
\ No newline at end of file
diff --git a/scripts/debug_tool/utils/_init__.py b/scripts/debug_tool/utils/_init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/debug_tool/utils/config_reader.py b/scripts/debug_tool/utils/config_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..6101e9127f52cf234a1cba1cbd1246122b76af44
--- /dev/null
+++ b/scripts/debug_tool/utils/config_reader.py
@@ -0,0 +1,50 @@
+import os
+
+class ConfigReader:
+    def __init__(self, script_dir, root_dir):
+        self.SCRIPT_DIR = script_dir        # debug_tool directory
+        self.PIPELINE_DIR = os.path.dirname(script_dir)  # scripts directory
+        self.ROOT_DIR = root_dir
+        
+        # Read from pipeline's config.ini
+        self.config = self.read_config()
+        self.directories = self.setup_directories()
+        self.file_paths = self.setup_file_paths()
+        
+    def read_config(self):
+        config = {}
+        # Use config.ini from scripts directory
+        config_path = os.path.join(self.PIPELINE_DIR, "config.ini")
+        try:
+            with open(config_path, 'r') as f:
+                for line in f:
+                    if '=' in line:
+                        key, value = line.strip().split('=', 1)
+                        config[key.strip()] = value.strip()
+        except FileNotFoundError:
+            raise Exception(f"config.ini not found in {self.PIPELINE_DIR}")
+        return config
+        
+    def setup_directories(self):
+        return {
+            'scriptDir': self.PIPELINE_DIR,  # Point to scripts directory
+            'debugDir': self.SCRIPT_DIR,     # debug_tool directory
+            'rootDir': self.ROOT_DIR,
+            'monoDepthDir': os.path.join(self.ROOT_DIR, "scripts", "360monodepthexecution"),
+            'outputDir': os.path.join(self.ROOT_DIR, "edgenet-360", "Output"),
+            'materialRecogDir': os.path.join(self.ROOT_DIR, "Dynamic-Backward-Attention-Transformer"),
+            'edgeNetDir': os.path.join(self.ROOT_DIR, "edgenet-360")
+        }
+        
+    def setup_file_paths(self):
+        return {
+            'checkpointFile': os.path.join(
+                self.directories['materialRecogDir'],
+                "checkpoints/dpglt_mode95/accuracy/epoch=126-valid_acc_epoch=0.87.ckpt"
+            ),
+            'shiftedImage': os.path.join(self.PIPELINE_DIR, "shifted_t.png"),  # Use scripts directory
+            'monoDepthImage': os.path.join(
+                self.directories['monoDepthDir'],
+                "rgb.jpg"
+            )
+        }
\ 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 0000000000000000000000000000000000000000..43486fbbed9abcafa154c03226ddebaf51cf24b1
--- /dev/null
+++ b/scripts/debug_tool/utils/file_handlers.py
@@ -0,0 +1,196 @@
+# utils/file_handlers.py
+from PyQt6.QtWidgets import QFileDialog, QMessageBox
+import subprocess
+import os
+import shutil
+
+def select_file(parent, title="Select File", file_types="All Files (*)", initial_dir=None):
+    """
+    Opens a file selection dialog.
+    
+    Args:
+        parent: Parent widget (should have config_reader attribute)
+        title: Dialog window title
+        file_types: File filter (e.g., "Images (*.png *.jpg);;All Files (*)")
+        initial_dir: Starting directory for the dialog. If None, uses default Data directory
+        
+    Returns:
+        Selected file path or empty string if cancelled
+    """
+    # Get default directory if not specified
+    if initial_dir is None and hasattr(parent, 'config_reader'):
+        initial_dir = os.path.join(parent.config_reader.directories['edgeNetDir'], 'Data')
+    elif initial_dir is None and hasattr(parent, 'parent') and hasattr(parent.parent(), 'config_reader'):
+        initial_dir = os.path.join(parent.parent().config_reader.directories['edgeNetDir'], 'Data')
+        
+    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=None, suggested_name=""):
+    """
+    Opens a save file dialog.
+    
+    Args:
+        parent: Parent widget (should have config_reader attribute)
+        title: Dialog window title
+        file_types: File filter
+        initial_dir: Starting directory. If None, uses default Data directory
+        suggested_name: Default filename
+        
+    Returns:
+        Selected save path or empty string if cancelled
+    """
+    # Get default directory if not specified
+    if initial_dir is None and hasattr(parent, 'config_reader'):
+        initial_dir = os.path.join(parent.config_reader.directories['edgeNetDir'], 'Data')
+    elif initial_dir is None and hasattr(parent, 'parent') and hasattr(parent.parent(), 'config_reader'):
+        initial_dir = os.path.join(parent.parent().config_reader.directories['edgeNetDir'], 'Data')
+        
+    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=None):
+    """
+    Opens a directory selection dialog.
+    
+    Args:
+        parent: Parent widget (should have config_reader attribute)
+        title: Dialog window title
+        initial_dir: Starting directory. If None, uses default Data directory
+        
+    Returns:
+        Selected directory path or empty string if cancelled
+    """
+    # Get default directory if not specified
+    if initial_dir is None and hasattr(parent, 'config_reader'):
+        initial_dir = os.path.join(parent.config_reader.directories['edgeNetDir'], 'Data')
+    elif initial_dir is None and hasattr(parent, 'parent') and hasattr(parent.parent(), 'config_reader'):
+        initial_dir = os.path.join(parent.parent().config_reader.directories['edgeNetDir'], 'Data')
+        
+    return QFileDialog.getExistingDirectory(
+        parent, 
+        title,
+        initial_dir,
+        QFileDialog.Option.ShowDirsOnly
+    )
+
+def run_command(parent, cmd, status_callback=None):
+    """
+    Runs a command and handles its output.
+    
+    Args:
+        parent: Parent widget
+        cmd: Command to execute
+        status_callback: Optional callback for status updates
+        
+    Returns:
+        tuple: (success, message)
+    """
+    try:
+        if status_callback:
+            status_callback(f"Executing command:\n{cmd}\n")
+        
+        process = subprocess.Popen(
+            cmd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            shell=True,
+            text=True
+        )
+        stdout, stderr = process.communicate()
+        
+        if process.returncode == 0:
+            if status_callback:
+                status_callback("Command completed successfully")
+                if stdout:
+                    status_callback("Output:\n" + stdout)
+            return True, stdout
+        else:
+            error_msg = f"Command failed:\n\n{stderr}"
+            if status_callback:
+                status_callback("Command failed with error:\n" + stderr)
+            QMessageBox.critical(parent, "Error", error_msg)
+            return False, stderr
+            
+    except Exception as e:
+        error_msg = f"Failed to execute command: {str(e)}"
+        if status_callback:
+            status_callback(error_msg)
+        QMessageBox.critical(parent, "Error", error_msg)
+        return False, error_msg
+    
+def clean_directory(directory_path, status_callback=None):
+    """
+    Cleans all files in a directory.
+    
+    Args:
+        directory_path: Directory to clean
+        status_callback: Optional callback for status updates
+    Returns:
+        bool: Success status
+    """
+    try:
+        if not os.path.exists(directory_path):
+            os.makedirs(directory_path)
+            if status_callback:
+                status_callback(f"Created directory: {directory_path}")
+            return True
+
+        files = os.listdir(directory_path)
+        if not files:
+            if status_callback:
+                status_callback("Directory is already empty")
+            return True
+
+        for file in files:
+            file_path = os.path.join(directory_path, file)
+            try:
+                if os.path.isfile(file_path):
+                    os.remove(file_path)
+                elif os.path.isdir(file_path):
+                    shutil.rmtree(file_path)
+            except Exception as e:
+                if status_callback:
+                    status_callback(f"Error removing {file}: {str(e)}")
+                return False
+
+        if status_callback:
+            status_callback("Directory cleaned successfully")
+        return True
+
+    except Exception as e:
+        if status_callback:
+            status_callback(f"Failed to clean directory: {str(e)}")
+        return False
+
+def copy_file(src, dest, status_callback=None):
+    """
+    Copies a file to destination.
+    
+    Args:
+        src: Source file path
+        dest: Destination file path
+        status_callback: Optional callback for status updates
+    Returns:
+        bool: Success status
+    """
+    try:
+        os.makedirs(os.path.dirname(dest), exist_ok=True)
+        shutil.copy2(src, dest)
+        if status_callback:
+            status_callback(f"Copied file to: {dest}")
+        return True
+    except Exception as e:
+        if status_callback:
+            status_callback(f"Failed to copy file: {str(e)}")
+        return False
diff --git a/scripts/debug_tool/utils/image_handlers.py b/scripts/debug_tool/utils/image_handlers.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1cdcdd8a8d34721cff7464c7f4c975c00869a9f
--- /dev/null
+++ b/scripts/debug_tool/utils/image_handlers.py
@@ -0,0 +1,105 @@
+# utils/image_handlers.py
+from PyQt6.QtGui import QPixmap, QImage
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QLabel
+import cv2
+import os
+
+def convert_cv_to_pixmap(cv_img):
+    """
+    Converts OpenCV image to QPixmap.
+    
+    Args:  
+        cv_img: OpenCV image as numpy array 
+    Returns:    
+        QPixmap image  
+    """
+    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):
+    """
+    Loads and resizes an image while maintaining aspect ratio.
+    
+    Args:
+        image_path: Path to the image file
+        max_size: Maximum size for the larger dimension
+    Returns:
+        Resized image as numpy array
+    """
+    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)}")
+
+def update_preview(preview_label, image_path, max_size=300, error_callback=None):
+    """
+    Updates a QLabel with an image preview.
+    
+    Args:
+        preview_label: QLabel widget to update
+        image_path: Path to the image file
+        max_size: Maximum size for preview
+        error_callback: Optional callback function for error handling
+    """
+    if image_path and os.path.exists(image_path):
+        try:
+            img = load_and_resize_image(image_path, max_size)
+            pixmap = convert_cv_to_pixmap(img)
+            preview_label.setPixmap(pixmap)
+        except Exception as e:
+            if error_callback:
+                error_callback(f"Failed to load preview: {str(e)}")
+    else:
+        preview_label.clear()
+        if error_callback:
+            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
new file mode 100644
index 0000000000000000000000000000000000000000..5014f16a4603c408761e91e81746f5ceb1c67a3f
--- /dev/null
+++ b/scripts/debug_tool/utils/qt_widgets.py
@@ -0,0 +1,195 @@
+# utils/qt_widgets.py
+from PyQt6.QtWidgets import (QTextEdit, QGroupBox, QVBoxLayout, 
+                           QHBoxLayout, QPushButton, QLabel,
+                           QMessageBox)
+from PyQt6.QtCore import Qt
+
+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)
+
+def create_info_group(title, rows):
+    """
+    Creates a QGroupBox with rows of label pairs.
+    
+    Args:
+        title: Group box title
+        rows: List of tuples (label_text, value_text)
+    Returns:
+        tuple: (QGroupBox, dict of value QLabels)
+    """
+    group = QGroupBox(title)
+    layout = QVBoxLayout(group)
+    labels = {}
+    
+    for label_text, value_text in rows:
+        row = QHBoxLayout()
+        row.addWidget(QLabel(label_text))
+        value_label = QLabel(value_text)
+        labels[label_text] = value_label
+        row.addWidget(value_label, stretch=1)
+        layout.addLayout(row)
+    
+    return group, labels
+
+def create_preview_group(title):
+    """
+    Creates an image preview group.
+    
+    Args:
+        title: Group box title
+    Returns:
+        tuple: (QGroupBox, QLabel)
+    """
+    group = QGroupBox(title)
+    layout = QVBoxLayout(group)
+    preview = QLabel()
+    preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
+    layout.addWidget(preview)
+    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("✗ ", "")
+
+def show_confirmation_dialog(parent, title, message):
+    """Show a confirmation dialog and return True if user clicks Yes"""
+    return QMessageBox.question(
+        parent, 
+        title, 
+        message,
+        QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
+    ) == QMessageBox.StandardButton.Yes
\ No newline at end of file