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

refactored shifter tab

parent fc32908d
No related branches found
No related tags found
1 merge request!4GDP 4.2.10 - Adding Debug mode to GUI.py to run individual modules
......@@ -3,6 +3,7 @@ import sys
import os
from tabs.config_tab import ConfigTab
from tabs.shifter_tab import ShifterTab
from utils.config_reader import ConfigReader
class ModuleDebugGUI(QMainWindow):
......@@ -34,6 +35,7 @@ class ModuleDebugGUI(QMainWindow):
# Initialize tabs
self.tabs.addTab(ConfigTab(self.config_reader), "Configuration")
self.tabs.addTab(ShifterTab(self.config_reader), "Image Shifter")
def main():
app = QApplication(sys.argv)
......
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
QGroupBox, QMessageBox)
from PyQt6.QtCore import Qt
import os
from PIL import Image, ImageTk
import subprocess
class ShifterTab:
def __init__(self, notebook, config_reader):
self.tab = ttk.Frame(notebook)
notebook.add(self.tab, text='Image Shifter')
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):
# Split into left and right frames
left_frame = ttk.Frame(self.tab)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
right_frame = ttk.Frame(self.tab)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
self.setup_control_panel(left_frame)
self.setup_preview_panel(right_frame)
def setup_control_panel(self, parent):
# Control section
control_frame = ttk.LabelFrame(parent, text="Controls", padding="5")
control_frame.pack(fill=tk.X, pady=5)
# Input file selection
input_frame = ttk.Frame(control_frame)
input_frame.pack(fill=tk.X, pady=5)
ttk.Label(input_frame, text="Input file:").pack(side=tk.LEFT, padx=5)
self.input_label = ttk.Label(input_frame, text="No file selected")
self.input_label.pack(side=tk.LEFT, padx=5)
ttk.Button(
input_frame,
text="Select Input File",
command=self.select_input_file
).pack(side=tk.RIGHT, padx=5)
# Output file display
output_frame = ttk.Frame(control_frame)
output_frame.pack(fill=tk.X, pady=5)
ttk.Label(output_frame, text="Output will be saved as:").pack(side=tk.LEFT, padx=5)
self.output_label = ttk.Label(
output_frame,
text=self.shifted_image_path
)
self.output_label.pack(side=tk.LEFT, padx=5)
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)
# Environment info
env_frame = ttk.Frame(control_frame)
env_frame.pack(fill=tk.X, pady=5)
# Command preview
cmd_group, self.cmd_text = create_group_with_text("Command Preview", 80)
layout.addWidget(cmd_group)
ttk.Label(env_frame, text="Using conda environment:").pack(side=tk.LEFT, padx=5)
self.env_label = ttk.Label(
env_frame,
text=self.config_reader.config.get('materialEnv', 'Not found')
)
self.env_label.pack(side=tk.LEFT, padx=5)
# 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))
# Command preview
cmd_frame = ttk.LabelFrame(parent, text="Command Preview", padding="5")
cmd_frame.pack(fill=tk.X, pady=5)
# Status display
status_group, self.status_text = create_group_with_text("Status", 150)
layout.addWidget(status_group)
self.cmd_text = tk.Text(cmd_frame, height=3, wrap=tk.WORD)
self.cmd_text.pack(fill=tk.X)
self.update_command_preview()
# Control buttons
button_frame = ttk.Frame(control_frame)
button_frame.pack(fill=tk.X, pady=5)
ttk.Button(
button_frame,
text="Run Shifter",
command=self.run_shifter
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="Clear Status",
command=self.clear_status
).pack(side=tk.RIGHT, padx=5)
# Status section
status_frame = ttk.LabelFrame(parent, text="Status", padding="5")
status_frame.pack(fill=tk.BOTH, expand=True, pady=5)
# Add scrollbar to status
status_scroll = ttk.Scrollbar(status_frame)
status_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.status_text = tk.Text(
status_frame,
height=10,
wrap=tk.WORD,
yscrollcommand=status_scroll.set
)
self.status_text.pack(fill=tk.BOTH, expand=True)
status_scroll.config(command=self.status_text.yview)
def setup_preview_panel(self, layout):
preview_layout = QVBoxLayout()
def setup_preview_panel(self, parent):
preview_frame = ttk.LabelFrame(parent, text="Image Preview", padding="5")
preview_frame.pack(fill=tk.BOTH, expand=True)
# Split into two preview groups
preview_group = QGroupBox("Image Previews")
previews_layout = QHBoxLayout(preview_group)
# Input image preview
input_preview_frame = ttk.LabelFrame(preview_frame, text="Input Image")
input_preview_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
input_group, self.input_preview = create_preview_group("Input Image")
output_group, self.output_preview = create_preview_group("Shifted Image")
self.input_preview = ttk.Label(input_preview_frame)
self.input_preview.pack(padx=5, pady=5)
previews_layout.addWidget(input_group)
previews_layout.addWidget(output_group)
layout.addWidget(preview_group)
# Output image preview
output_preview_frame = ttk.LabelFrame(preview_frame, text="Shifted Image")
output_preview_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
def handle_file_select(self):
file_path = select_file(
self,
"Select Input Image",
"Images (*.png *.jpg *.jpeg)"
)
self.output_preview = ttk.Label(output_preview_frame)
self.output_preview.pack(padx=5, pady=5)
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.delete(1.0, tk.END)
self.cmd_text.insert(tk.END, "Select an input file to see the command")
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.delete(1.0, tk.END)
self.cmd_text.insert(tk.END, cmd)
def select_input_file(self):
filepath = filedialog.askopenfilename(
filetypes=[("Image files", "*.png *.jpg *.jpeg")]
)
if filepath:
self.input_file_path = os.path.normpath(filepath)
self.input_label.config(text=os.path.basename(filepath))
self.update_status(f"Selected input file: {filepath}")
self.update_command_preview()
self.update_image_preview()
self.cmd_text.setText(cmd)
def update_status(self, message):
self.status_text.insert(tk.END, f"{message}\n")
self.status_text.see(tk.END)
def clear_status(self):
self.status_text.delete(1.0, tk.END)
self.status_text.append(message)
scrollbar = self.status_text.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def run_shifter(self):
if not self.input_file_path:
messagebox.showwarning("Warning", "Please select an input file first")
QMessageBox.warning(self, "Warning", "Please select an input file first")
return
self.update_status("\nRunning image shifter...")
try:
# Construct the command
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"'''
# Run the command
self.update_status(f"Executing command:\n{cmd}\n")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
text=True
success, _ = run_command(
self,
self.cmd_text.toPlainText(),
self.update_status
)
stdout, stderr = process.communicate()
if process.returncode == 0:
self.update_status("Image shifter completed successfully")
if stdout:
self.update_status("Output:\n" + stdout)
if os.path.exists(self.shifted_image_path):
if success and os.path.exists(self.shifted_image_path):
self.update_status(f"Shifted image saved to: {self.shifted_image_path}")
self.update_image_preview()
else:
self.update_status("Warning: Output file not found!")
else:
self.update_status("Image shifter failed with error:\n" + stderr)
messagebox.showerror("Error", f"Command failed:\n\n{stderr}")
except Exception as e:
error_msg = f"Failed to run image shifter: {str(e)}"
self.update_status(error_msg)
messagebox.showerror("Error", error_msg)
def update_image_preview(self):
preview_size = (300, 300) # Adjust size as needed
# Update input image preview if exists
if self.input_file_path and os.path.exists(self.input_file_path):
try:
input_img = Image.open(self.input_file_path)
input_img.thumbnail(preview_size)
input_photo = ImageTk.PhotoImage(input_img)
self.input_preview.configure(image=input_photo)
self.input_preview.image = input_photo # Keep a reference
except Exception as e:
self.update_status(f"Failed to load input preview: {str(e)}")
# Update shifted image preview if exists
if os.path.exists(self.shifted_image_path):
try:
output_img = Image.open(self.shifted_image_path)
output_img.thumbnail(preview_size)
output_photo = ImageTk.PhotoImage(output_img)
self.output_preview.configure(image=output_photo)
self.output_preview.image = output_photo # Keep a reference
except Exception as e:
self.update_status(f"Failed to load shifted image preview: {str(e)}")
\ No newline at end of file
update_preview(self.output_preview, self.shifted_image_path,
error_callback=self.update_status)
\ No newline at end of file
# utils/file_handlers.py
from PyQt6.QtWidgets import QFileDialog
from PyQt6.QtWidgets import QFileDialog, QMessageBox
import subprocess
import os
def select_file(parent, title="Select File", file_types="All Files (*)", initial_dir=""):
def select_file(parent, title="Select File", file_types="All Files (*)", initial_dir=None):
"""
Opens a file selection dialog.
Args:
parent: Parent widget
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
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,
......@@ -22,20 +30,26 @@ def select_file(parent, title="Select File", file_types="All Files (*)", initial
)
return file_path
def save_file(parent, title="Save File", file_types="All Files (*)", initial_dir="", suggested_name=""):
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
parent: Parent widget (should have config_reader attribute)
title: Dialog window title
file_types: File filter
initial_dir: Starting directory
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,
......@@ -44,21 +58,72 @@ def save_file(parent, title="Save File", file_types="All Files (*)", initial_dir
)
return file_path
def select_directory(parent, title="Select Directory", initial_dir=""):
def select_directory(parent, title="Select Directory", initial_dir=None):
"""
Opens a directory selection dialog.
Args:
parent: Parent widget
parent: Parent widget (should have config_reader attribute)
title: Dialog window title
initial_dir: Starting directory
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
\ No newline at end of file
# utils/image_handlers.py
from PyQt6.QtGui import QPixmap, QImage
import numpy as np
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
......@@ -13,6 +24,15 @@ def convert_cv_to_pixmap(cv_img):
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:
......@@ -32,3 +52,26 @@ def load_and_resize_image(image_path, max_size=800):
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}")
\ No newline at end of file
# utils/qt_widgets.py
from PyQt6.QtWidgets import (QTextEdit, QGroupBox, QVBoxLayout,
QHBoxLayout, QPushButton)
QHBoxLayout, QPushButton, QLabel)
from PyQt6.QtCore import Qt
def create_group_with_text(title, height=100):
"""
......@@ -59,3 +60,43 @@ def create_status_group():
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
\ 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