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]
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,9 +561,11 @@ 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()
@@ -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)
+87 -26
View File
@@ -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):
+5 -3
View File
@@ -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)