testing changes

This commit is contained in:
2026-06-28 12:58:38 -07:00
parent ddb30d98f2
commit b5de8709de
3 changed files with 111 additions and 34 deletions
+19 -5
View File
@@ -63,6 +63,7 @@ right = 0
[Options] [Options]
show_welcome_dialog = true show_welcome_dialog = true
first_startup = true
[Preferences] [Preferences]
2d_data_bypass = false 2d_data_bypass = false
@@ -539,8 +540,20 @@ class MainApplication(QMainWindow):
# Check if we should pop up the welcome screen # Check if we should pop up the welcome screen
should_show_welcome = file_cfg.getboolean("Options", "show_welcome_dialog", fallback=True) 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") file_cfg.set("Options", "show_welcome_dialog", "false")
try: try:
with open(cfg_path, "w") as f: with open(cfg_path, "w") as f:
@@ -548,10 +561,12 @@ class MainApplication(QMainWindow):
except Exception as e: except Exception as e:
print(f"Warning: Could not save preference: {e}") print(f"Warning: Could not save preference: {e}")
welcome = WelcomeDialog(self, direct=True) welcome = WelcomeDialog(self, direct=True, first=False)
welcome.show() welcome.show()
def init_ui(self): def init_ui(self):
central = QWidget() central = QWidget()
self.setCentralWidget(central) self.setCentralWidget(central)
@@ -1150,7 +1165,6 @@ class MainApplication(QMainWindow):
self.analysis_clearing_bypass = file_cfg.getboolean("Preferences", "analysis_clearing_bypass", fallback=False) 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.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 # 2. Sync the UI Menu checkmarks visually
if hasattr(self, 'pref_actions'): if hasattr(self, 'pref_actions'):
@@ -2535,7 +2549,7 @@ def config_init():
has_changes = True has_changes = True
continue continue
for option in file_cfg.options(section): for option in list(file_cfg.options(section)):
if not ref_cfg.has_option(section, option): if not ref_cfg.has_option(section, option):
file_cfg.remove_option(section, option) file_cfg.remove_option(section, option)
has_changes = True has_changes = True
@@ -2545,7 +2559,7 @@ def config_init():
file_cfg.add_section(section) file_cfg.add_section(section)
has_changes = True 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): if not file_cfg.has_option(section, option):
default_val = ref_cfg.get(section, option) default_val = ref_cfg.get(section, option)
file_cfg.set(section, option, default_val) file_cfg.set(section, option, default_val)
+83 -22
View File
@@ -19,11 +19,11 @@ from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas 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.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 PySide6.QtGui import QPixmap, QImage
from src.shared.flaresbasewidget import FlaresBaseWidget 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): 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. 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): def __init__(self, channel_name, data_list, color_map, image_path=None, parent=None):
# Create a 1-row, 2-column subplot array self.fig = Figure(figsize=(11.0, 5.5))
# 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.ax = self.fig.subplots(1, 2)
super().__init__(self.fig) super().__init__(self.fig)
self.setParent(parent) self.setParent(parent)
@@ -516,7 +517,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
layout = QVBoxLayout(popup) layout = QVBoxLayout(popup)
layout.setContentsMargins(0, 0, 0, 0) # Strip extra outer layout spacing 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( expanded_canvas = StaticChannelCanvas(
channel_name, channel_name,
@@ -571,7 +572,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
header.setAlignment(Qt.AlignmentFlag.AlignCenter) header.setAlignment(Qt.AlignmentFlag.AlignCenter)
card_layout.addWidget(header) 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 # 3. Instantiate the canvas with a custom size flag or constraint
# Adjust your StaticChannelCanvas __init__ to check if it should render in 'compact' mode # Adjust your StaticChannelCanvas __init__ to check if it should render in 'compact' mode
@@ -687,7 +688,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
layout = QVBoxLayout(popup) layout = QVBoxLayout(popup)
layout.setContentsMargins(10, 10, 10, 10) 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 # This one renders full size (900x520) for analytical reading
expanded_canvas = StaticChannelCanvas( expanded_canvas = StaticChannelCanvas(
@@ -707,6 +708,44 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
self._summary_popups.append(popup) 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): class ParticipantFoldChannelsWidget(FlaresBaseWidget):
def __init__(self, haemo_dict, cha_dict): def __init__(self, haemo_dict, cha_dict):
super().__init__("ParticipantFoldChannels") super().__init__("ParticipantFoldChannels")
@@ -811,32 +850,54 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
self.multi_progress.add_participant(os.path.basename(file_path), total_channels) 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() self.multi_progress.show()
print(f"After 1: {datetime.now()}")
if current_process().name == 'MainProcess': 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 # Signal Routing
self.manager = Manager() self.orchestrator_thread.started.connect(self.orchestrator.run)
self.result_queue = self.manager.Queue() self.orchestrator.setup_finished.connect(self.on_orchestration_success)
self.progress_queue = self.manager.Queue() self.orchestrator.setup_failed.connect(self.on_orchestration_failed)
self.active_processes = []
# 3. Start ALL processes at once # Automatic lifecycle cleanup configuration
for file_path in selected_files: self.orchestrator.setup_finished.connect(self.orchestrator_thread.quit)
p = Process( self.orchestrator.setup_failed.connect(self.orchestrator_thread.quit)
target=single_participant_worker, self.orchestrator_thread.finished.connect(self.orchestrator_thread.deleteLater)
args=(file_path, self.haemo_dict[file_path], self.result_queue, self.progress_queue) self.orchestrator.setup_finished.connect(self.orchestrator.deleteLater)
) self.orchestrator.setup_failed.connect(self.orchestrator.deleteLater)
p.start()
self.active_processes.append(p)
# 4. Start the GUI listener 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.completed_count = 0
self.result_timer = QTimer() self.result_timer = QTimer()
self.result_timer.timeout.connect(self.check_parallel_results) self.result_timer.timeout.connect(self.check_parallel_results)
self.result_timer.start() 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}")
def check_parallel_results(self): def check_parallel_results(self):
# Check for progress/completion signals # Check for progress/completion signals
+5 -3
View File
@@ -15,7 +15,7 @@ from src.shared.shareddata import APP_NAME, CURRENT_VERSION, CHANGELOG_URL, reso
class WelcomeDialog(QDialog): class WelcomeDialog(QDialog):
def __init__(self, parent=None, direct=True): def __init__(self, parent=None, direct=True, first=False):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle(f"What's New - {APP_NAME.upper()}") self.setWindowTitle(f"What's New - {APP_NAME.upper()}")
self.setMinimumSize(550, 450) self.setMinimumSize(550, 450)
@@ -27,8 +27,10 @@ class WelcomeDialog(QDialog):
logo_label = QLabel(self) logo_label = QLabel(self)
# NOTE: might not work on mac and need the icns file # NOTE: might not work on mac and need the icns file
logo_label.setPixmap(QIcon(resource_path("icons/main.ico")).pixmap(48, 48)) logo_label.setPixmap(QIcon(resource_path("../../icons/main.ico")).pixmap(48, 48))
if direct: 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) title_label = QLabel(f"<h2>{APP_NAME.upper()} has been sucessfully updated to version {CURRENT_VERSION}!</h2>", self)
else: else:
title_label = QLabel(f"<h2>{APP_NAME.upper()} is currently running version {CURRENT_VERSION}.</h2>", self) title_label = QLabel(f"<h2>{APP_NAME.upper()} is currently running version {CURRENT_VERSION}.</h2>", self)