testing changes
This commit is contained in:
@@ -63,6 +63,7 @@ right = 0
|
||||
|
||||
[Options]
|
||||
show_welcome_dialog = true
|
||||
first_startup = true
|
||||
|
||||
[Preferences]
|
||||
2d_data_bypass = false
|
||||
@@ -539,8 +540,20 @@ class MainApplication(QMainWindow):
|
||||
|
||||
# Check if we should pop up the welcome screen
|
||||
should_show_welcome = file_cfg.getboolean("Options", "show_welcome_dialog", fallback=True)
|
||||
first_startup = file_cfg.getboolean("Options", "first_startup", fallback=False)
|
||||
|
||||
if should_show_welcome:
|
||||
if first_startup:
|
||||
file_cfg.set("Options", "first_startup", "false")
|
||||
try:
|
||||
with open(cfg_path, "w") as f:
|
||||
file_cfg.write(f)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not save preference: {e}")
|
||||
|
||||
welcome = WelcomeDialog(self, direct=True, first=first_startup)
|
||||
welcome.show()
|
||||
|
||||
elif should_show_welcome:
|
||||
file_cfg.set("Options", "show_welcome_dialog", "false")
|
||||
try:
|
||||
with open(cfg_path, "w") as f:
|
||||
@@ -548,10 +561,12 @@ class MainApplication(QMainWindow):
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not save preference: {e}")
|
||||
|
||||
welcome = WelcomeDialog(self, direct=True)
|
||||
welcome = WelcomeDialog(self, direct=True, first=False)
|
||||
welcome.show()
|
||||
|
||||
|
||||
|
||||
|
||||
def init_ui(self):
|
||||
central = QWidget()
|
||||
self.setCentralWidget(central)
|
||||
@@ -1150,7 +1165,6 @@ class MainApplication(QMainWindow):
|
||||
self.analysis_clearing_bypass = file_cfg.getboolean("Preferences", "analysis_clearing_bypass", fallback=False)
|
||||
self.folding_bypass = file_cfg.getboolean("Preferences", "folding_bypass", fallback=False)
|
||||
|
||||
self.show_welcome_dialog = file_cfg.getboolean("Options", "show_welcome_dialog", fallback=True)
|
||||
|
||||
# 2. Sync the UI Menu checkmarks visually
|
||||
if hasattr(self, 'pref_actions'):
|
||||
@@ -2535,7 +2549,7 @@ def config_init():
|
||||
has_changes = True
|
||||
continue
|
||||
|
||||
for option in file_cfg.options(section):
|
||||
for option in list(file_cfg.options(section)):
|
||||
if not ref_cfg.has_option(section, option):
|
||||
file_cfg.remove_option(section, option)
|
||||
has_changes = True
|
||||
@@ -2545,7 +2559,7 @@ def config_init():
|
||||
file_cfg.add_section(section)
|
||||
has_changes = True
|
||||
|
||||
for option in ref_cfg.options(section):
|
||||
for option in list(ref_cfg.options(section)):
|
||||
if not file_cfg.has_option(section, option):
|
||||
default_val = ref_cfg.get(section, option)
|
||||
file_cfg.set(section, option, default_val)
|
||||
|
||||
@@ -19,11 +19,11 @@ from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
|
||||
|
||||
from PySide6.QtWidgets import QFrame, QGridLayout, QHBoxLayout, QLabel, QProgressBar, QPushButton, QScrollArea, QSizePolicy, QWidget, QDialog, QVBoxLayout
|
||||
from PySide6.QtCore import Qt, QSize, QTimer
|
||||
from PySide6.QtCore import QThread, Qt, QSize, QTimer
|
||||
from PySide6.QtGui import QPixmap, QImage
|
||||
|
||||
from src.shared.flaresbasewidget import FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
from src.shared.shareddata import APP_NAME, resource_path
|
||||
|
||||
|
||||
class MultiProgressDialog(QDialog):
|
||||
@@ -106,9 +106,10 @@ class StaticChannelCanvas(FigureCanvas):
|
||||
Renders the interactive pie chart on the left, and a matching PNG image on the right.
|
||||
"""
|
||||
def __init__(self, channel_name, data_list, color_map, image_path=None, parent=None):
|
||||
# Create a 1-row, 2-column subplot array
|
||||
# figsize=(11.0, 5.5) creates a wide 2:1 widescreen aspect window layout
|
||||
self.fig, self.ax = plt.subplots(1, 2, figsize=(11.0, 5.5))
|
||||
self.fig = Figure(figsize=(11.0, 5.5))
|
||||
|
||||
self.ax = self.fig.subplots(1, 2)
|
||||
|
||||
super().__init__(self.fig)
|
||||
self.setParent(parent)
|
||||
|
||||
@@ -516,7 +517,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
layout = QVBoxLayout(popup)
|
||||
layout.setContentsMargins(0, 0, 0, 0) # Strip extra outer layout spacing
|
||||
|
||||
target_png_path = "images/brain.png"
|
||||
target_png_path = resource_path("../../images/brain.png")
|
||||
|
||||
expanded_canvas = StaticChannelCanvas(
|
||||
channel_name,
|
||||
@@ -571,7 +572,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
header.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
card_layout.addWidget(header)
|
||||
|
||||
target_png_path = "images/brain.png"
|
||||
target_png_path = resource_path("../../images/brain.png")
|
||||
|
||||
# 3. Instantiate the canvas with a custom size flag or constraint
|
||||
# Adjust your StaticChannelCanvas __init__ to check if it should render in 'compact' mode
|
||||
@@ -687,7 +688,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
layout = QVBoxLayout(popup)
|
||||
layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
target_png_path = "images/brain.png"
|
||||
target_png_path = resource_path("../../images/brain.png")
|
||||
|
||||
# This one renders full size (900x520) for analytical reading
|
||||
expanded_canvas = StaticChannelCanvas(
|
||||
@@ -707,6 +708,44 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
self._summary_popups.append(popup)
|
||||
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from multiprocessing import Manager, Process
|
||||
|
||||
class ProcessOrchestrator(QObject):
|
||||
# Fires when Manager + Processes are completely ready
|
||||
# Emits: (manager_instance, result_queue, progress_queue, active_processes_list)
|
||||
setup_finished = Signal(object, object, object, list)
|
||||
setup_failed = Signal(str)
|
||||
|
||||
def __init__(self, selected_files, haemo_dict, worker_func):
|
||||
super().__init__()
|
||||
self.selected_files = selected_files
|
||||
self.haemo_dict = haemo_dict
|
||||
self.worker_func = worker_func
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
# 🟢 [Delay 1 Fix] Instantiate Manager completely off the main thread
|
||||
manager = Manager()
|
||||
result_queue = manager.Queue()
|
||||
progress_queue = manager.Queue()
|
||||
active_processes = []
|
||||
|
||||
# 🟢 [Delay 2 Fix] Perform heavy pickling loop safely in the background
|
||||
for file_path in self.selected_files:
|
||||
p = Process(
|
||||
target=self.worker_func,
|
||||
args=(file_path, self.haemo_dict[file_path], result_queue, progress_queue)
|
||||
)
|
||||
p.start()
|
||||
active_processes.append(p)
|
||||
|
||||
# Deliver setup assets back to the GUI Main Thread
|
||||
self.setup_finished.emit(manager, result_queue, progress_queue, active_processes)
|
||||
except Exception as e:
|
||||
self.setup_failed.emit(str(e))
|
||||
|
||||
|
||||
class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha_dict):
|
||||
super().__init__("ParticipantFoldChannels")
|
||||
@@ -811,31 +850,53 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
|
||||
self.multi_progress.add_participant(os.path.basename(file_path), total_channels)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
print(f"Before: {datetime.now()}")
|
||||
self.multi_progress.show()
|
||||
|
||||
print(f"After 1: {datetime.now()}")
|
||||
|
||||
if current_process().name == 'MainProcess':
|
||||
# Create a clean background thread worker execution channel
|
||||
self.orchestrator_thread = QThread()
|
||||
self.orchestrator = ProcessOrchestrator(selected_files, self.haemo_dict, single_participant_worker)
|
||||
self.orchestrator.moveToThread(self.orchestrator_thread)
|
||||
|
||||
# 2. Setup Multiprocessing Manager
|
||||
self.manager = Manager()
|
||||
self.result_queue = self.manager.Queue()
|
||||
self.progress_queue = self.manager.Queue()
|
||||
self.active_processes = []
|
||||
# Signal Routing
|
||||
self.orchestrator_thread.started.connect(self.orchestrator.run)
|
||||
self.orchestrator.setup_finished.connect(self.on_orchestration_success)
|
||||
self.orchestrator.setup_failed.connect(self.on_orchestration_failed)
|
||||
|
||||
# 3. Start ALL processes at once
|
||||
for file_path in selected_files:
|
||||
p = Process(
|
||||
target=single_participant_worker,
|
||||
args=(file_path, self.haemo_dict[file_path], self.result_queue, self.progress_queue)
|
||||
)
|
||||
p.start()
|
||||
self.active_processes.append(p)
|
||||
# Automatic lifecycle cleanup configuration
|
||||
self.orchestrator.setup_finished.connect(self.orchestrator_thread.quit)
|
||||
self.orchestrator.setup_failed.connect(self.orchestrator_thread.quit)
|
||||
self.orchestrator_thread.finished.connect(self.orchestrator_thread.deleteLater)
|
||||
self.orchestrator.setup_finished.connect(self.orchestrator.deleteLater)
|
||||
self.orchestrator.setup_failed.connect(self.orchestrator.deleteLater)
|
||||
|
||||
self.orchestrator_thread.start()
|
||||
print(f"After 4: {datetime.now()}")
|
||||
|
||||
def on_orchestration_success(self, manager, result_queue, progress_queue, active_processes):
|
||||
""" Executed on the Main GUI Thread once background process setup finishes """
|
||||
self.manager = manager
|
||||
self.result_queue = result_queue
|
||||
self.progress_queue = progress_queue
|
||||
self.active_processes = active_processes
|
||||
|
||||
# 🟢 Safely initialize and trigger your polling listener
|
||||
self.completed_count = 0
|
||||
self.result_timer = QTimer()
|
||||
self.result_timer.timeout.connect(self.check_parallel_results)
|
||||
self.result_timer.start()
|
||||
|
||||
def on_orchestration_failed(self, error_msg):
|
||||
""" Fallback handler if Windows permissions or pickling fails in background """
|
||||
if hasattr(self, 'multi_progress'):
|
||||
self.multi_progress.close()
|
||||
print(f"[CRITICAL FAILURE] Background Orchestration Failed:\n{error_msg}")
|
||||
|
||||
# 4. Start the GUI listener
|
||||
self.completed_count = 0
|
||||
self.result_timer = QTimer()
|
||||
self.result_timer.timeout.connect(self.check_parallel_results)
|
||||
self.result_timer.start()
|
||||
|
||||
|
||||
def check_parallel_results(self):
|
||||
|
||||
@@ -15,7 +15,7 @@ from src.shared.shareddata import APP_NAME, CURRENT_VERSION, CHANGELOG_URL, reso
|
||||
|
||||
|
||||
class WelcomeDialog(QDialog):
|
||||
def __init__(self, parent=None, direct=True):
|
||||
def __init__(self, parent=None, direct=True, first=False):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle(f"What's New - {APP_NAME.upper()}")
|
||||
self.setMinimumSize(550, 450)
|
||||
@@ -27,8 +27,10 @@ class WelcomeDialog(QDialog):
|
||||
logo_label = QLabel(self)
|
||||
|
||||
# NOTE: might not work on mac and need the icns file
|
||||
logo_label.setPixmap(QIcon(resource_path("icons/main.ico")).pixmap(48, 48))
|
||||
if direct:
|
||||
logo_label.setPixmap(QIcon(resource_path("../../icons/main.ico")).pixmap(48, 48))
|
||||
if first:
|
||||
title_label = QLabel(f"<h2>Welcome to {APP_NAME.upper()}!</h2>", self)
|
||||
elif direct:
|
||||
title_label = QLabel(f"<h2>{APP_NAME.upper()} has been sucessfully updated to version {CURRENT_VERSION}!</h2>", self)
|
||||
else:
|
||||
title_label = QLabel(f"<h2>{APP_NAME.upper()} is currently running version {CURRENT_VERSION}.</h2>", self)
|
||||
|
||||
Reference in New Issue
Block a user