Skip to content
Snippets Groups Projects
Commit e09c442c authored by mhby1g21's avatar mhby1g21
Browse files

Merge branch 'GDP-4.2.10' into 'master'

GDP 4.2.10 - Adding Debug mode to GUI.py to run individual modules

See merge request !4
parents 963bcdaf 8e3ab01e
Branches
No related tags found
1 merge request!4GDP 4.2.10 - Adding Debug mode to GUI.py to run individual modules
Showing
with 1802 additions and 3 deletions
scripts/360monodepthexecution/rgb.jpg
scripts/shifted_t.png
scripts/config.ini
*.pyc
\ No newline at end of file
Images/GUI.png

8.43 KiB

Images/GUI_debug.jpg

177 KiB

Images/Pipeline-Overview.png

92.4 KiB

......@@ -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
......
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
# 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
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
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)
# 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
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
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
# 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
# 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
# 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment