diff --git a/scripts/360monodepthexecution/masterscript-min.ps1 b/scripts/360monodepthexecution/masterscript-min.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..4fa3e86855804f0b593a5efcdaa2a5f034e4b90e --- /dev/null +++ b/scripts/360monodepthexecution/masterscript-min.ps1 @@ -0,0 +1,6 @@ +invoke-expression 'cmd /c start powershell -WindowStyle Minimized -Command { .\triggerinteractive.ps1}' +sleep 10 +Start-Process powershell "-File .\dockercopy.ps1" -WindowStyle Minimized -Wait +Start-Process powershell "-File .\executemonodepth.ps1" -WindowStyle Minimized -Wait +Start-Process powershell "-File .\extractDepthMap.ps1" -WindowStyle Minimized -Wait +Start-Process powershell "-File .\stopDockerContainer.ps1" -WindowStyle Minimized -Wait \ No newline at end of file diff --git a/scripts/Depth2Disparity/depth2disparity.py b/scripts/Depth2Disparity/depth2disparity.py new file mode 100644 index 0000000000000000000000000000000000000000..08c6bb232fc2aa7c70b4564cb33b96cd495e9606 --- /dev/null +++ b/scripts/Depth2Disparity/depth2disparity.py @@ -0,0 +1,105 @@ +# takes 2 pixel coordinates and corresponding distance values - and a relative depth map +# caculates relative to absolute function - if linear +# applies function to relative depth map to get absolute depth map +# then convert to disparity map using depth disparity formula + + +import argparse +import numpy as np +import cv2 + +def relative2abs(rel_depth_map, coord1, dist1, coord2, dist2): + # Get the relative depth values at the two points + rel_value1 = rel_depth_map[coord1[1], coord1[0]] # (y, x) + rel_value2 = rel_depth_map[coord2[1], coord2[0]] + + # Calculate the linear transformation: depth = a * rel_depth + b + a = (dist2 - dist1) / (rel_value2 - rel_value1) + b = dist1 - a * rel_value1 + + # Apply the transformation to the entire relative depth map + abs_depth_map = a * rel_depth_map + b + + # this should not be normalised, the values in the array should equate to literal distances + return abs_depth_map + +def depth2disparity(abs_depth_map, baseline=0.176, disp_scale=2.0, disp_offset=-120): + + # Get image dimensions + height, width = abs_depth_map.shape + + # Calculate angular coordinates for each pixel + unit_w = 2.0 / width # Longitude step size + unit_h = 1.0 / height # Latitude step size + + # Initialize disparity map + disparity_map = np.zeros_like(abs_depth_map, dtype=np.float32) + + for i in range(height): + theta_t = i * unit_h * np.pi # Latitude angle for the row + + for j in range(width): + # Longitude angle (not strictly needed for disparity calculation) + phi = j * unit_w * np.pi + + # Retrieve the absolute depth (r_t) for this pixel + r_t = abs_depth_map[i, j] + + # Avoid invalid or infinite depth values + if r_t <= 0: + disparity_map[i, j] = 0 + continue + + try: + # Compute denominator with stability checks + epsilon = 1e-8 # Small value to prevent division instability + tan_theta_t = np.tan(theta_t) if np.abs(np.cos(theta_t)) > epsilon else np.sign(np.sin(theta_t)) * np.inf + + denominator = r_t * np.sin(theta_t) + r_t * np.cos(theta_t) * tan_theta_t + if denominator <= 0: + disparity_map[i, j] = 0 + continue + + # Calculate angular disparity (d) + angle_disp = np.arctan(baseline / denominator) - theta_t + + # Convert angular disparity to pixel disparity + disparity_map[i, j] = (angle_disp / (unit_h * np.pi) - disp_offset) * disp_scale + + except ZeroDivisionError: + disparity_map[i, j] = 0 + + return disparity_map + +if __name__ == "__main__": + # Set up argument parser + parser = argparse.ArgumentParser(description="Convert relative depth map to absolute depth map.") + parser.add_argument("rel_depth_map", type=str, help="Path to the relative depth map (image file).") + parser.add_argument("coord1", type=int, nargs=2, help="Pixel coordinates of the first point (x y).") + parser.add_argument("dist1", type=float, help="Absolute depth value at the first point.") + parser.add_argument("coord2", type=int, nargs=2, help="Pixel coordinates of the second point (x y).") + parser.add_argument("dist2", type=float, help="Absolute depth value at the second point.") + + # Parse arguments + args = parser.parse_args() + + # Load the relative depth map (dummy placeholder for now) + print(f"Loading relative depth map from: {args.rel_depth_map}") + rel_depth_map = cv2.imread(args.rel_depth_map, cv2.IMREAD_GRAYSCALE) # Load as grayscale + if rel_depth_map is None: + raise FileNotFoundError(f"Unable to load file: {args.rel_depth_map}") + + # Normalise depth map + rel_depth_map = rel_depth_map / 255.0 + + # Convert relative to absolute depth + abs_depth_map = relative2abs( + rel_depth_map, tuple(args.coord1), args.dist1, tuple(args.coord2), args.dist2 + ) + + # Convert depth map to disparity map + disparity_map = depth2disparity(abs_depth_map) + + # save disparity map + #cv2.imwrite(f"{args.rel_depth_map}-disparity.png", disparity_map) + \ No newline at end of file diff --git a/scripts/debug_tool/GUI_debug.py b/scripts/debug_tool/GUI_debug.py index 1ce869060e8f77cfaeba0c421f9912e24c4b7967..eecf781d7df6fe0e693429e60bfc8158f375dbca 100644 --- a/scripts/debug_tool/GUI_debug.py +++ b/scripts/debug_tool/GUI_debug.py @@ -9,6 +9,7 @@ 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 +from tabs.mbdnet_tab import MBDNetTab class ModuleDebugGUI(QMainWindow): def __init__(self): @@ -43,6 +44,7 @@ class ModuleDebugGUI(QMainWindow): 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") + self.tabs.addTab(MBDNetTab(self.config_reader), "MBDNet Execution") def main(): app = QApplication(sys.argv) diff --git a/scripts/debug_tool/tabs/depth_tab.py b/scripts/debug_tool/tabs/depth_tab.py index 30da9e51ae17ff8b1b2e3a9cddced0aa26365fb0..caca369486d7ca1c9ee9cc4b2f32cda2cabf2f1a 100644 --- a/scripts/debug_tool/tabs/depth_tab.py +++ b/scripts/debug_tool/tabs/depth_tab.py @@ -60,7 +60,7 @@ class DepthTab(QWidget): ("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'), + ("Run Depth Est.", lambda: self.run_depth_estimation(), 'left'), ("Clear Status", lambda: self.status_text.clear(), 'right') ] layout.addLayout(create_button_layout(*buttons)) @@ -124,19 +124,28 @@ class DepthTab(QWidget): 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): + def run_depth_estimation(self, debug=True): 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 - ) + if debug: + # Run script in debug mode + success, output = run_command( + self, + "powershell.exe -File masterscript.ps1", + self.update_status + ) + else: + # Run minified script for production + self.update_status("Running minified script...") + success, output = run_command( + self, + "powershell.exe -File masterscript-min.ps1 -WindowStyle Minimized", + self.update_status + ) # Change back to original directory os.chdir(original_dir) diff --git a/scripts/debug_tool/tabs/mbdnet_tab.py b/scripts/debug_tool/tabs/mbdnet_tab.py new file mode 100644 index 0000000000000000000000000000000000000000..165d59d067595a3ad70d4a746471c2c86bcf9219 --- /dev/null +++ b/scripts/debug_tool/tabs/mbdnet_tab.py @@ -0,0 +1,511 @@ +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 MBDNetTab(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") + self.scripts_dir = self.config_reader.directories['scriptDir'] + + def init_variables(self): + """Initialize class variables""" + self.is_processing = False + self.manual_inputs = { + 'depth_e.png': None, + 'rgb.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) + + # Status section + self.status_group, self.status_text = create_group_with_text("Status",300) + 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.MBDNet_status = create_status_label("MBDNet:") + self.split_status = create_status_label("Mesh Split:") + self.flip_status = create_status_label("Blender Flip:") + + layout.addLayout(self.MBDNet_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 MBDNet", self.run_mbdnet, '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_mbdnet(self): + """Run MBDNet 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_mbdnet_process) + self.current_thread.finished.connect(self.on_mbdnet_complete) + self.current_thread.progress.connect(self.update_status) + self.current_thread.start() + + def clean_output_directory(self, silent=False): + if silent or 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.MBDNet_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 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_mbdnet_process(self): + """Run MBDNet 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") + #self.clean_output_directory(self.output_dir, True) + # copy rgb.png to shifted_t.png and enhanced_depth_e.png to shifted_disparity.png + os.rename(os.path.join(self.input_dir, 'rgb.png'), os.path.join(self.input_dir, 'shifted_t.png')) + os.rename(os.path.join(self.input_dir, 'depth_e.png'), os.path.join(self.input_dir, 'shifted_disparity.png')) + # Run scene_completion.py + os.chdir(self.scripts_dir) + infer_cmd = self._build_mbdnet_command() + print("Final command: ", infer_cmd) + return run_command(self, infer_cmd, self.update_status)[0] + + 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_mbdnet_command(self): + """Build scene_completion.py command""" + shifted_disparity = os.path.join(self.input_dir, 'shifted_disparity.png') + shifted_t = os.path.join(self.input_dir, 'shifted_t.png') + + command = (f'python scene_completion.py {shifted_disparity} {shifted_t} ') + + # Log final command + self.update_status(f"Final command: {command}") + + return command + + def on_mbdnet_complete(self, success, error_message): + """Handle MBDNet completion""" + self.is_processing = False + self.progress_bar.setMaximum(100) + + if success: + print("MBDNet process completed successfully") + update_status_indicator(self.MBDNet_status, "Complete") + try: + os.rename(os.path.join(self.input_dir, 'shifted_t.png'), os.path.join(self.input_dir, 'rgb.png')) + os.rename(os.path.join(self.input_dir, 'shifted_disparity.png'), os.path.join(self.input_dir, 'depth_e.png')) + os.rename(os.path.join(self.output_dir, 'scene_completed_prediction.mtl'), os.path.join(self.output_dir, 'Input_prediction_mesh.mtl')) + os.rename(os.path.join(self.output_dir, 'scene_completed_prediction.obj'), os.path.join(self.output_dir, 'Input_prediction_mesh.obj')) + except Exception as e: + print(f"Error renaming files: {str(e)}") + self.update_status(f"Error renaming files: {str(e)}") + self.update_status("Please make sure you are connected to the University Network/Gloabl Protect") + + self.update_depth_preview() + else: + print("MBDNet process failed") + update_status_indicator(self.MBDNet_status, "Failed") + QMessageBox.critical(self, "Error", f"MBDNet failed: {error_message}") + + def update_depth_preview(self): + """Update depth preview image""" + depth_path = os.path.join(self.input_dir, "shifted_disparity.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_mesh.obj")): + raise Exception("Missing Input_prediction_mesh.obj file") + print("Running mesh split") + cmd = (f'call "{self.config_reader.config["condaDir"]}\\Scripts\\activate.bat" ' + f'{self.config_reader.config["materialEnv"]} && ' + f'python replace.py') + print("Command: ", cmd) + 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.MBDNet_status, "Running") + if not self._run_mbdnet_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.MBDNet_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/shifter_tab.py b/scripts/debug_tool/tabs/shifter_tab.py index d532b5d8784654212562dd14723ce6a0724ab98e..7add01bacc19e53f996f0eb1c9b7eee4194ef7c0 100644 --- a/scripts/debug_tool/tabs/shifter_tab.py +++ b/scripts/debug_tool/tabs/shifter_tab.py @@ -104,18 +104,38 @@ class ShifterTab(QWidget): scrollbar.setValue(scrollbar.maximum()) def run_shifter(self): + """GUI version of shifter - calls the thread-safe version""" if not self.input_file_path: QMessageBox.warning(self, "Warning", "Please select an input file first") - return + return False self.update_status("\nRunning image shifter...") - success, _ = run_command( - self, - self.cmd_text.toPlainText(), - self.update_status - ) + success, _ = self.run_shifter_process(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 + error_callback=self.update_status) + return success + + def run_shifter_process(self, status_callback=None): + """Thread-safe version of shifter that doesn't use GUI components""" + try: + if not self.input_file_path: + raise ValueError("No input file selected") + + # Construct command without using GUI components + conda_dir = self.config_reader.config["condaDir"] + material_env = self.config_reader.config["materialEnv"] + shifter_script = os.path.join(self.config_reader.directories['scriptDir'], "shifter.py") + + command = f'"{conda_dir}\\condabin\\activate.bat" {material_env} && ' \ + f'python "{shifter_script}" "{self.input_file_path}" "{self.shifted_image_path}"' + + # Run the command + return run_command(None, command, status_callback) + + except Exception as e: + if status_callback: + status_callback(f"Error in shifter process: {str(e)}") + return False, str(e) \ No newline at end of file diff --git a/scripts/scene_completion.py b/scripts/scene_completion.py index fdb3fcc0b99b87e3d7cf5ca9f6ef062b89e1c075..6baef21f331197d3d88ea8cebfb0e564d8e4e9ff 100644 --- a/scripts/scene_completion.py +++ b/scripts/scene_completion.py @@ -4,11 +4,15 @@ from scp import SCPClient import os import subprocess import time +import sys load_dotenv() hostname = os.getenv("HOSTNAME") -username = os.getenv("USERNAME") +#username = os.getenv("USERNAME") password = os.getenv("PASSWORD") +# hostname = "soton.ac.uk" +username = "kproject" +# password = "i7-13700H" def send_files(shifted_disparity_path, shifted_t_path): # sends two files needed to iridis @@ -110,7 +114,15 @@ def get_completed_scene(shifted_disparity_path, shifted_t_path): print(output) remote_file_path = "/mainfs/ECShome/kproject/mona/MDBNet360_GDP/output/scene_completed_prediction.obj" - local_file_path = "edgenet-360/Data/Input/scene_completed_prediction.obj" + current_dir = os.getcwd() + parent_dir = os.path.dirname(current_dir) + local_file_folder = os.path.join(parent_dir, "edgenet-360/Output") + local_file_path = os.path.join(local_file_folder,"scene_completed_prediction.obj") + with SCPClient(client.get_transport()) as scp: + scp.get(remote_file_path, local_file_path) # Download file + + remote_file_path = "/mainfs/ECShome/kproject/mona/MDBNet360_GDP/output/scene_completed_prediction.mtl" + local_file_path = os.path.join(local_file_folder,"scene_completed_prediction.mtl") with SCPClient(client.get_transport()) as scp: scp.get(remote_file_path, local_file_path) # Download file @@ -128,11 +140,11 @@ def get_completed_scene(shifted_disparity_path, shifted_t_path): if __name__ == "__main__": if len(sys.argv) != 3: - print("Usage: python shifter.py <shifted_disparity_path> <shifted_t_path>") + print("Usage: python scene_completion.py <shifted_disparity_path> <shifted_t_path>") sys.exit(1) shifted_disparity_path = sys.argv[1] shifted_t_path = sys.argv[2] - saved_path = get_completed_scene(shifted_disparity_path, shifted_t_path) + saved_path = get_completed_scene(shifted_disparity_path, shifted_t_path)[1] print(f"Shifted image saved as {saved_path}") \ No newline at end of file diff --git a/scripts/simple_tab.py b/scripts/simple_tab.py index 0f383dfc7c88657a2a4ecfda208c382c1a3c1a98..867c06ab3385c05cd959f7f19be0288d913229b2 100644 --- a/scripts/simple_tab.py +++ b/scripts/simple_tab.py @@ -45,22 +45,46 @@ class PipelineWorker(QThread): self.progress.emit("Cleaning EdgeNet output directory...") def copy_file(self): - self.progress.emit("Copying input file to scripts/360monodepthexecution...") + # Determine which file to use as input + self.tab.depth.depth_input_path = self.tab.shifter.shifted_image_path if self.tab.should_shift_image else self.tab.input_path + + if self.tab.should_shift_image: + self.progress.emit("Copying shifted image to scripts/360monodepthexecution...") + else: + self.progress.emit("Copying input file to scripts/360monodepthexecution...") + self.tab.depth.copy_file() def shift_image(self): print("Starting shift_image") # Debug print - if not self.tab.shift_image_check.isChecked(): + if not self.tab.should_shift_image: self.progress.emit("Skipping image shift...") return - self.progress.emit("Shifting input image...") - self.tab.shifter.run_shifter() - print("Completed shift_image") # Debug print + + try: + self.progress.emit("Shifting input image...") + # Set input path for shifter + self.tab.shifter.input_file_path = self.tab.input_path + # Use the thread-safe version + success, output = self.tab.shifter.run_shifter_process(self.progress.emit) + + if not success: + raise RuntimeError(f"Image shifting failed: {output}") + + print("Completed shift_image") + + # Change material recognition input file path to shifted image + self.tab.material.input_file_path = self.tab.shifter.shifted_image_path + + return self.tab.shifter.shifted_image_path + except Exception as e: + print(f"Shift image failed: {str(e)}") + raise def run_depth_estimation(self): print("Starting depth_estimation") # Debug print self.progress.emit("Running depth estimation...") - self.tab.depth.run_depth_estimation() + self.tab.depth.run_depth_estimation(False) print("Completed depth_estimation") # Debug print def run_material_recognition(self): @@ -107,7 +131,7 @@ class PipelineWorker(QThread): self.progress.emit("Running EdgeNet enhance360.py and infer360.py...") try: - self.tab.edge_net.include_top = self.tab.include_top_check.isChecked() + self.tab.edge_net.include_top = self.tab.should_include_top # Use cached state self.tab.edge_net._run_edge_net_process() print("Completed edge_net") except Exception as e: @@ -121,11 +145,12 @@ class PipelineWorker(QThread): self.tab.edge_net._run_blender_flip_process() self.progress.emit("Completed blender flip (blenderFlip.py)") self.progress.emit("Post-processing completed!") + self.progress.emit("File saved in edgenet-360\Output") def run_pipeline(self): self.clean_temp_files() - self.copy_file() self.shift_image() + self.copy_file() self.run_depth_estimation() self.run_material_recognition() self.run_edge_net() @@ -138,6 +163,10 @@ class SimpleTab(QWidget): self.config_reader = config_reader self.input_path = None self.pipeline_thread = None + + # Store states that will be used by worker thread + self.should_shift_image = False + self.should_include_top = False # Initialize module instances self.shifter = ShifterTab(self.config_reader) @@ -342,7 +371,7 @@ class SimpleTab(QWidget): layout.addWidget(controls_group) - # Image Distance section + ## Image Distance section self.image_distance_group = QGroupBox("Image Distance") distance_layout = QVBoxLayout(self.image_distance_group) info_label = QLabel("Please select two point on the image and input the distance from the camera to that point.") @@ -353,24 +382,24 @@ class SimpleTab(QWidget): self.points_info_label = QLabel() # Label to display points and distances self.points_info_label.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignCenter) self.image_distance_group.setStyleSheet(""" - QGroupBox { - font-weight: bold; - border: 2px solid grey; - border-radius: 20px; - margin-top: 10px; - background-color: #3e3e3e; - padding: 20px; - } - QGroupBox::title { - margin: 10px; - background-color: transparent; - color: white; - } - QLabel { - margin: 5px; - background-color: #3e3e3e; - color: white; - } + QGroupBox { + font-weight: bold; + border: 2px solid grey; + border-radius: 20px; + margin-top: 10px; + background-color: #3e3e3e; + padding: 20px; + } + QGroupBox::title { + margin: 10px; + background-color: transparent; + color: white; + } + QLabel { + margin: 5px; + background-color: #3e3e3e; + color: white; + } """) # Center the ClickableLabel within its parent layout distance_preview_layout = QHBoxLayout() @@ -382,11 +411,11 @@ class SimpleTab(QWidget): self.distance_reset_btn.clicked.connect(self.distance_preview.clear_points) self.distance_reset_btn.setFixedSize(150, 40) self.distance_reset_btn.setStyleSheet(""" - QPushButton { - margin: 5px; - padding: 5px; - border-radius: 10px; - } + QPushButton { + margin: 5px; + padding: 5px; + border-radius: 10px; + } """) distance_btn_layout = QHBoxLayout() distance_btn_layout.addStretch() @@ -526,7 +555,7 @@ class SimpleTab(QWidget): self.update_status("Waiting for distance points...") self.info_labels["Status:"].setText("Waiting for distance points...") # Enable the run pipeline button - #self.run_pipeline_btn.setEnabled(True) + self.run_pipeline_btn.setEnabled(True) # Provide input path to all modules self.shifter.input_file_path = file_path @@ -574,6 +603,10 @@ class SimpleTab(QWidget): if self.pipeline_thread and self.pipeline_thread.isRunning(): QMessageBox.warning(self, "Warning", "Pipeline is already running") return + + # Cache checkbox states before starting thread + self.should_shift_image = self.shift_image_check.isChecked() + self.should_include_top = self.include_top_check.isChecked() # Show progress bar and update status self.progress_bar.show() @@ -586,7 +619,8 @@ class SimpleTab(QWidget): self.pipeline_thread.finished.connect(self.pipeline_completed) # Disable controls while running - self.setEnabled(False) + #self.setEnabled(False) + self.disable_buttons_while_running() self.progress_bar.setEnabled(True) # Keep progress bar enabled #TODO: Add model selection for EdgeNet or MDBNet @@ -616,4 +650,13 @@ class SimpleTab(QWidget): self.info_labels["Status:"].setText(message.split("...")[-1] if "..." in message else message) # Scroll to bottom scrollbar = self.status_text.verticalScrollBar() - scrollbar.setValue(scrollbar.maximum()) \ No newline at end of file + scrollbar.setValue(scrollbar.maximum()) + + def disable_buttons_while_running(self): + self.select_btn.setEnabled(False) + self.run_pipeline_btn.setEnabled(False) + self.include_top_check.setEnabled(False) + self.shift_image_check.setEnabled(False) + self.ssc_model_combo.setEnabled(False) + self.distance_reset_btn.setEnabled(False) + self.distance_preview.setEnabled(False) \ No newline at end of file