1 Commits

Author SHA1 Message Date
87073fb218 more boris implementation 2025-10-15 10:00:44 -07:00
2 changed files with 611 additions and 28 deletions

View File

@@ -55,7 +55,7 @@ import pyvistaqt # type: ignore
# External library imports for mne
from mne import (
EvokedArray, SourceEstimate, Info, Epochs, Label,
EvokedArray, SourceEstimate, Info, Epochs, Label, Annotations,
events_from_annotations, read_source_spaces,
stc_near_sensors, pick_types, grand_average, get_config, set_config, read_labels_from_annot
) # type: ignore
@@ -132,6 +132,8 @@ ENHANCE_NEGATIVE_CORRELATION: bool
SHORT_CHANNEL: bool
REMOVE_EVENTS: list
VERBOSITY = True
# FIXME: Shouldn't need each ordering - just order it before checking
@@ -179,6 +181,7 @@ REQUIRED_KEYS: dict[str, Any] = {
"PSP_THRESHOLD": float,
"SHORT_CHANNEL": bool,
"REMOVE_EVENTS": list,
# "REJECT_PAIRS": bool,
# "FORCE_DROP_ANNOTATIONS": list,
# "FILTER_LOW_PASS": float,
@@ -1470,9 +1473,15 @@ def resource_path(relative_path):
def fold_channels(raw: BaseRaw) -> None:
# if getattr(sys, 'frozen', False):
path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary"
logger.info(path)
set_config('MNE_NIRS_FOLD_PATH', resource_path(path)) # type: ignore
# Locate the fOLD excel files
set_config('MNE_NIRS_FOLD_PATH', resource_path("../../mne_data/fOLD/fOLD-public-master/Supplementary")) # type: ignore
# # Locate the fOLD excel files
# else:
# logger.info("yabba")
# set_config('MNE_NIRS_FOLD_PATH', resource_path("../../mne_data/fOLD/fOLD-public-master/Supplementary")) # type: ignore
output = None
@@ -1534,8 +1543,8 @@ def fold_channels(raw: BaseRaw) -> None:
"Brain_Outside",
]
cmap1 = plt.cm.get_cmap('tab20') # First 20 colors
cmap2 = plt.cm.get_cmap('tab20b') # Next 20 colors
cmap1 = plt.get_cmap('tab20') # First 20 colors
cmap2 = plt.get_cmap('tab20b') # Next 20 colors
# Combine the colors from both colormaps
colors = [cmap1(i) for i in range(20)] + [cmap2(i) for i in range(20)] # Total 40 colors
@@ -1611,6 +1620,7 @@ def fold_channels(raw: BaseRaw) -> None:
for ax in axes[len(hbo_channel_names):]:
ax.axis('off')
plt.show()
return fig, legend_fig
@@ -2935,6 +2945,19 @@ def process_participant(file_path, progress_callback=None):
logger.info("14")
# Step 14: Design Matrix
events_to_remove = REMOVE_EVENTS
filtered_annotations = [ann for ann in raw.annotations if ann['description'] not in events_to_remove]
new_annot = Annotations(
onset=[ann['onset'] for ann in filtered_annotations],
duration=[ann['duration'] for ann in filtered_annotations],
description=[ann['description'] for ann in filtered_annotations]
)
# Set the new annotations
raw_haemo.set_annotations(new_annot)
design_matrix, fig_design_matrix = make_design_matrix(raw_haemo, short_chans)
fig_individual["Design Matrix"] = fig_design_matrix
if progress_callback: progress_callback(15)

606
main.py
View File

@@ -47,6 +47,8 @@ from PySide6.QtGui import QAction, QKeySequence, QIcon, QIntValidator, QDoubleVa
CURRENT_VERSION = "1.0.1"
API_URL = "https://git.research.dezeeuw.ca/api/v1/repos/tyler/flares/releases"
API_URL_SECONDARY = "https://git.research2.dezeeuw.ca/api/v1/repos/tyler/flares/releases"
PLATFORM_NAME = platform.system().lower()
# Selectable parameters on the right side of the window
@@ -143,6 +145,7 @@ SECTIONS = [
{
"title": "Design Matrix",
"params": [
{"name": "REMOVE_EVENTS", "default": "None", "type": list, "help": "Remove events matching the names provided before generating the Design Matrix"},
# {"name": "DRIFT_MODEL", "default": "cosine", "type": str, "help": "Drift model for GLM."},
# {"name": "DURATION_BETWEEN_ACTIVITIES", "default": 35, "type": int, "help": "Time between activities (s)."},
# {"name": "SHORT_CHANNEL_REGRESSION", "default": True, "type": bool, "help": "Use short channel regression."},
@@ -170,6 +173,63 @@ SECTIONS = [
class CommandConsole(QWidget):
def __init__(self, parent=None):
super().__init__(parent, Qt.WindowType.Window)
self.setWindowTitle("Custom Console")
self.output_area = QTextEdit()
self.output_area.setReadOnly(True)
self.input_line = QLineEdit()
self.input_line.returnPressed.connect(self.handle_command)
layout = QVBoxLayout()
layout.addWidget(self.output_area)
layout.addWidget(self.input_line)
self.setLayout(layout)
# Define your commands
self.commands = {
"hello": self.cmd_hello,
"add": self.cmd_add,
"help": self.cmd_help
}
def handle_command(self):
command_text = self.input_line.text()
self.input_line.clear()
self.output_area.append(f"> {command_text}")
parts = command_text.strip().split()
if not parts:
return
command_name = parts[0]
args = parts[1:]
func = self.commands.get(command_name)
if func:
try:
result = func(*args)
if result:
self.output_area.append(str(result))
except Exception as e:
self.output_area.append(f"[Error] {e}")
else:
self.output_area.append(f"[Unknown command] '{command_name}'")
# Example commands
def cmd_hello(self, *args):
return "Hello from the console!"
def cmd_add(self, a, b):
return f"Result: {int(a) + int(b)}"
def cmd_help(self, *args):
return f"Available commands: {', '.join(self.commands.keys())}"
class UpdateDownloadThread(QThread):
"""
Thread that downloads and extracts an update package and emits a signal on completion or error.
@@ -276,22 +336,28 @@ class UpdateCheckThread(QThread):
return (normalize(v1) > normalize(v2)) - (normalize(v1) < normalize(v2))
def get_latest_release_for_platform(self):
response = requests.get(API_URL, timeout=5)
response.raise_for_status()
releases = response.json()
urls = [API_URL, API_URL_SECONDARY]
for url in urls:
try:
response = requests.get(API_URL, timeout=5)
response.raise_for_status()
releases = response.json()
if not releases:
return None, None
if not releases:
return None, None
latest = releases[0]
tag = latest["tag_name"].lstrip("v")
latest = releases[0]
tag = latest["tag_name"].lstrip("v")
for asset in latest.get("assets", []):
if PLATFORM_NAME in asset["name"].lower():
return tag, asset["browser_download_url"]
for asset in latest.get("assets", []):
if PLATFORM_NAME in asset["name"].lower():
return tag, asset["browser_download_url"]
return tag, None
return tag, None
except (requests.RequestException, ValueError) as e:
continue
return None, None
class LocalPendingUpdateCheckThread(QThread):
@@ -1201,6 +1267,8 @@ class ParamSection(QWidget):
widget = QLineEdit()
widget.setValidator(QDoubleValidator())
widget.setText(str(param["default"]))
elif param["type"] == list:
widget = self._create_multiselect_dropdown(None)
else:
widget = QLineEdit()
widget.setText(str(param["default"]))
@@ -1216,6 +1284,81 @@ class ParamSection(QWidget):
"type": param["type"]
}
def _create_multiselect_dropdown(self, items):
combo = FullClickComboBox()
combo.setView(QListView())
model = QStandardItemModel()
combo.setModel(model)
combo.setEditable(True)
combo.lineEdit().setReadOnly(True)
combo.lineEdit().setPlaceholderText("Select...")
dummy_item = QStandardItem("<None Selected>")
dummy_item.setFlags(Qt.ItemIsEnabled)
model.appendRow(dummy_item)
toggle_item = QStandardItem("Toggle Select All")
toggle_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
toggle_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(toggle_item)
if items is not None:
for item in items:
standard_item = QStandardItem(item)
standard_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
standard_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(standard_item)
combo.setInsertPolicy(QComboBox.NoInsert)
def on_view_clicked(index):
item = model.itemFromIndex(index)
if item.isCheckable():
new_state = Qt.Checked if item.checkState() == Qt.Unchecked else Qt.Unchecked
item.setCheckState(new_state)
combo.view().pressed.connect(on_view_clicked)
self._updating_checkstates = False
def on_item_changed(item):
if self._updating_checkstates:
return
self._updating_checkstates = True
normal_items = [model.item(i) for i in range(2, model.rowCount())] # skip dummy and toggle
if item == toggle_item:
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
if all_checked:
for i in normal_items:
i.setCheckState(Qt.Unchecked)
toggle_item.setCheckState(Qt.Unchecked)
else:
for i in normal_items:
i.setCheckState(Qt.Checked)
toggle_item.setCheckState(Qt.Checked)
elif item == dummy_item:
pass
else:
# When normal items change, update toggle item
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
toggle_item.setCheckState(Qt.Checked if all_checked else Qt.Unchecked)
self._updating_checkstates = False
for param_name, info in self.widgets.items():
if info["widget"] == combo:
self.update_dropdown_label(param_name)
break
model.itemChanged.connect(on_item_changed)
combo.setInsertPolicy(QComboBox.NoInsert)
return combo
def show_help_popup(self, text):
msg = QMessageBox(self)
@@ -1232,6 +1375,8 @@ class ParamSection(QWidget):
if expected_type == bool:
values[name] = widget.currentText() == "True"
elif expected_type == list:
values[name] = [x.strip() for x in widget.lineEdit().text().split(",") if x.strip()]
else:
raw_text = widget.text()
try:
@@ -1241,9 +1386,6 @@ class ParamSection(QWidget):
values[name] = float(raw_text)
elif expected_type == str:
values[name] = raw_text
elif expected_type == list:
# Very basic CSV parsing - fix?
values[name] = [x.strip() for x in raw_text.split(",") if x.strip()]
else:
values[name] = raw_text # Fallback
except Exception as e:
@@ -1251,6 +1393,155 @@ class ParamSection(QWidget):
return values
def update_dropdown_items(self, param_name, new_items):
"""
Updates the items in a multi-select dropdown parameter field.
Args:
param_name (str): The parameter name (must match one in self.widgets).
new_items (list): The new items to populate in the dropdown.
"""
widget_info = self.widgets.get(param_name)
print("[ParamSection] Current widget keys:", list(self.widgets.keys()))
if not widget_info:
print(f"[ParamSection] No widget found for param '{param_name}'")
return
widget = widget_info["widget"]
if not isinstance(widget, FullClickComboBox):
print(f"[ParamSection] Widget for param '{param_name}' is not a FullClickComboBox")
return
# Replace the model on the existing widget
new_model = QStandardItemModel()
dummy_item = QStandardItem("<None Selected>")
dummy_item.setFlags(Qt.ItemIsEnabled)
new_model.appendRow(dummy_item)
toggle_item = QStandardItem("Toggle Select All")
toggle_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
toggle_item.setData(Qt.Unchecked, Qt.CheckStateRole)
new_model.appendRow(toggle_item)
for item_text in new_items:
item = QStandardItem(item_text)
item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
item.setData(Qt.Unchecked, Qt.CheckStateRole)
new_model.appendRow(item)
widget.setModel(new_model)
widget.setView(QListView()) # Reset view to refresh properly
def on_view_clicked(index):
item = new_model.itemFromIndex(index)
if item.isCheckable():
new_state = Qt.Checked if item.checkState() == Qt.Unchecked else Qt.Unchecked
item.setCheckState(new_state)
widget.view().pressed.connect(on_view_clicked)
def on_item_changed(item):
if getattr(self, "_updating_checkstates", False):
return
self._updating_checkstates = True
normal_items = [new_model.item(i) for i in range(2, new_model.rowCount())]
if item == toggle_item:
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
for i in normal_items:
i.setCheckState(Qt.Unchecked if all_checked else Qt.Checked)
toggle_item.setCheckState(Qt.Unchecked if all_checked else Qt.Checked)
else:
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
toggle_item.setCheckState(Qt.Checked if all_checked else Qt.Unchecked)
self._updating_checkstates = False
for param_name, info in self.widgets.items():
if info["widget"] == widget:
self.update_dropdown_label(param_name)
break
new_model.itemChanged.connect(on_item_changed)
widget.lineEdit().setText("<None Selected>")
def _get_checked_items(self, combo):
checked = []
model = combo.model()
for i in range(model.rowCount()):
item = model.item(i)
if item.text() in ("<None Selected>", "Toggle Select All"):
continue
if item.checkState() == Qt.Checked:
checked.append(item.text())
return checked
def update_dropdown_label(self, param_name):
widget_info = self.widgets.get(param_name)
if not widget_info:
print(f"[ParamSection] No widget found for param '{param_name}'")
return
widget = widget_info["widget"]
if not isinstance(widget, FullClickComboBox):
print(f"[ParamSection] Widget for param '{param_name}' is not a FullClickComboBox")
return
selected = self._get_checked_items(widget)
if not selected:
widget.lineEdit().setText("<None Selected>")
else:
# You can customize how you display selected items here:
widget.lineEdit().setText(", ".join(selected))
def update_annotation_dropdown_from_folder(self, folder_path):
"""
Reads all EEG files in the given folder, extracts annotations using MNE,
and updates the dropdown for the `target_param` with the set of common annotations.
Args:
folder_param (str): The name of the parameter holding the folder path.
target_param (str): The name of the multi-select dropdown to update.
"""
# folder_path_widget = self.widgets.get(folder_param, {}).get("widget")
# if not folder_path_widget:
# print(f"[ParamSection] Folder path param '{folder_param}' not found.")
# return
# folder_path = folder_path_widget.text().strip()
# if not os.path.isdir(folder_path):
# print(f"[ParamSection] '{folder_path}' is not a valid directory.")
# return
annotation_sets = []
for filename in os.listdir(folder_path):
full_path = os.path.join(folder_path, filename)
try:
raw = read_raw_snirf(full_path, preload=False, verbose="ERROR")
annotations = raw.annotations
if annotations is not None:
labels = set(annotations.description)
annotation_sets.append(labels)
except Exception as e:
print(f"[ParamSection] Skipping file '{filename}' due to error: {e}")
if not annotation_sets:
print(f"[ParamSection] No annotations found in folder '{folder_path}'")
return
# Get common annotations
common_annotations = set.intersection(*annotation_sets) if len(annotation_sets) > 1 else annotation_sets[0]
common_annotations = sorted(list(common_annotations)) # for consistent order
print(f"[ParamSection] Common annotations: {common_annotations}")
# Update the dropdown
self.update_dropdown_items("REMOVE_EVENTS", common_annotations)
class FullClickLineEdit(QLineEdit):
def mousePressEvent(self, event):
@@ -1865,6 +2156,245 @@ class ParticipantBrainViewerWidget(QWidget):
class ParticipantFoldChannelsWidget(QWidget):
def __init__(self, haemo_dict, cha_dict):
super().__init__()
self.setWindowTitle("FLARES Participant Fold Channels Viewer")
self.haemo_dict = haemo_dict
self.cha_dict = cha_dict
# Create mappings: file_path -> participant label and dropdown display text
self.participant_map = {} # file_path -> "Participant 1"
self.participant_dropdown_items = [] # "Participant 1 (filename)"
for i, file_path in enumerate(self.haemo_dict.keys(), start=1):
short_label = f"Participant {i}"
display_label = f"{short_label} ({os.path.basename(file_path)})"
self.participant_map[file_path] = short_label
self.participant_dropdown_items.append(display_label)
self.layout = QVBoxLayout(self)
self.top_bar = QHBoxLayout()
self.layout.addLayout(self.top_bar)
self.participant_dropdown = self._create_multiselect_dropdown(self.participant_dropdown_items)
self.participant_dropdown.currentIndexChanged.connect(self.update_participant_dropdown_label)
self.index_texts = [
"0 (Fold Channels)",
# "1 (second image)",
# "2 (third image)",
# "3 (fourth image)",
]
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
self.image_index_dropdown.currentIndexChanged.connect(self.update_image_index_dropdown_label)
self.submit_button = QPushButton("Submit")
self.submit_button.clicked.connect(self.show_brain_images)
self.top_bar.addWidget(QLabel("Participants:"))
self.top_bar.addWidget(self.participant_dropdown)
self.top_bar.addWidget(QLabel("Fold Type:"))
self.top_bar.addWidget(self.image_index_dropdown)
self.top_bar.addWidget(self.submit_button)
self.scroll = QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll_content = QWidget()
self.grid_layout = QGridLayout(self.scroll_content)
self.scroll.setWidget(self.scroll_content)
self.layout.addWidget(self.scroll)
self.thumb_size = QSize(280, 180)
self.showMaximized()
def _create_multiselect_dropdown(self, items):
combo = FullClickComboBox()
combo.setView(QListView())
model = QStandardItemModel()
combo.setModel(model)
combo.setEditable(True)
combo.lineEdit().setReadOnly(True)
combo.lineEdit().setPlaceholderText("Select...")
dummy_item = QStandardItem("<None Selected>")
dummy_item.setFlags(Qt.ItemIsEnabled)
model.appendRow(dummy_item)
toggle_item = QStandardItem("Toggle Select All")
toggle_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
toggle_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(toggle_item)
for item in items:
standard_item = QStandardItem(item)
standard_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
standard_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(standard_item)
combo.setInsertPolicy(QComboBox.NoInsert)
def on_view_clicked(index):
item = model.itemFromIndex(index)
if item.isCheckable():
new_state = Qt.Checked if item.checkState() == Qt.Unchecked else Qt.Unchecked
item.setCheckState(new_state)
combo.view().pressed.connect(on_view_clicked)
self._updating_checkstates = False
def on_item_changed(item):
if self._updating_checkstates:
return
self._updating_checkstates = True
normal_items = [model.item(i) for i in range(2, model.rowCount())] # skip dummy and toggle
if item == toggle_item:
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
if all_checked:
for i in normal_items:
i.setCheckState(Qt.Unchecked)
toggle_item.setCheckState(Qt.Unchecked)
else:
for i in normal_items:
i.setCheckState(Qt.Checked)
toggle_item.setCheckState(Qt.Checked)
elif item == dummy_item:
pass
else:
# When normal items change, update toggle item
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
toggle_item.setCheckState(Qt.Checked if all_checked else Qt.Unchecked)
# Update label text immediately after change
if combo == self.participant_dropdown:
self.update_participant_dropdown_label()
elif combo == self.image_index_dropdown:
self.update_image_index_dropdown_label()
self._updating_checkstates = False
model.itemChanged.connect(on_item_changed)
combo.setInsertPolicy(QComboBox.NoInsert)
return combo
def _get_checked_items(self, combo):
checked = []
model = combo.model()
for i in range(model.rowCount()):
item = model.item(i)
# Skip dummy and toggle items:
if item.text() in ("<None Selected>", "Toggle Select All"):
continue
if item.checkState() == Qt.Checked:
checked.append(item.text())
return checked
def update_participant_dropdown_label(self):
selected = self._get_checked_items(self.participant_dropdown)
if not selected:
self.participant_dropdown.lineEdit().setText("<None Selected>")
else:
# Extract just "Participant N" from "Participant N (filename)"
selected_short = [s.split(" ")[0] + " " + s.split(" ")[1] for s in selected]
self.participant_dropdown.lineEdit().setText(", ".join(selected_short))
def update_image_index_dropdown_label(self):
selected = self._get_checked_items(self.image_index_dropdown)
if not selected:
self.image_index_dropdown.lineEdit().setText("<None Selected>")
else:
# Only show the index part
index_labels = [s.split(" ")[0] for s in selected]
self.image_index_dropdown.lineEdit().setText(", ".join(index_labels))
def show_brain_images(self):
import flares
selected_display_names = self._get_checked_items(self.participant_dropdown)
selected_file_paths = []
for display_name in selected_display_names:
for fp, short_label in self.participant_map.items():
expected_display = f"{short_label} ({os.path.basename(fp)})"
if display_name == expected_display:
selected_file_paths.append(fp)
break
selected_indexes = [
int(s.split(" ")[0]) for s in self._get_checked_items(self.image_index_dropdown)
]
parameterized_indexes = {
0: [
{
"key": "show_optodes",
"label": "Determine what is rendered above the brain. Valid values are 'sensors', 'labels', 'none', 'all'.",
"default": "all",
"type": str,
},
{
"key": "show_brodmann",
"label": "Show common brodmann areas on the brain.",
"default": "True",
"type": bool,
}
],
}
# Inject full_text from index_texts
for idx, params_list in parameterized_indexes.items():
full_text = self.index_texts[idx] if idx < len(self.index_texts) else f"{idx} (No label found)"
for param_info in params_list:
param_info["full_text"] = full_text
indexes_needing_params = {idx: parameterized_indexes[idx] for idx in selected_indexes if idx in parameterized_indexes}
param_values = {}
if indexes_needing_params:
dialog = ParameterInputDialog(indexes_needing_params, parent=self)
if dialog.exec_() == QDialog.Accepted:
param_values = dialog.get_values()
if param_values is None:
return
else:
return
# Pass the necessary arguments to each method
for file_path in selected_file_paths:
haemo_obj = self.haemo_dict.get(file_path)
if haemo_obj is None:
continue
#cha = self.cha_dict.get(file_path)
for idx in selected_indexes:
if idx == 0:
params = param_values.get(idx, {})
show_optodes = params.get("show_optodes", None)
show_brodmann = params.get("show_brodmann", None)
if show_optodes is None or show_brodmann is None:
print(f"Missing parameters for index {idx}, skipping.")
continue
flares.fold_channels(haemo_obj)
else:
print(f"No method defined for index {idx}")
class ClickableLabel(QLabel):
def __init__(self, full_pixmap: QPixmap, thumbnail_pixmap: QPixmap):
super().__init__()
@@ -3017,18 +3547,22 @@ class ViewerLauncherWidget(QWidget):
btn2 = QPushButton("Open Participant Brain Viewer")
btn2.clicked.connect(lambda: self.open_participant_brain_viewer(haemo_dict, cha_dict))
btn3 = QPushButton("Open Participant Fold Channels Viewer")
btn3.clicked.connect(lambda: self.open_participant_fold_channels_viewer(haemo_dict, cha_dict))
btn3 = QPushButton("Open Inter-Group Viewer")
btn3.clicked.connect(lambda: self.open_group_viewer(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group))
btn4 = QPushButton("Open Cross Group Brain Viewer")
btn4.clicked.connect(lambda: self.open_group_brain_viewer(haemo_dict, df_ind, design_matrix, group, contrast_results_dict))
btn4 = QPushButton("Open Inter-Group Viewer")
btn4.clicked.connect(lambda: self.open_group_viewer(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group))
btn5 = QPushButton("Open Cross Group Brain Viewer")
btn5.clicked.connect(lambda: self.open_group_brain_viewer(haemo_dict, df_ind, design_matrix, group, contrast_results_dict))
layout.addWidget(btn1)
layout.addWidget(btn2)
layout.addWidget(btn3)
layout.addWidget(btn4)
layout.addWidget(btn5)
def open_participant_viewer(self, haemo_dict, fig_bytes_dict):
self.participant_viewer = ParticipantViewerWidget(haemo_dict, fig_bytes_dict)
@@ -3037,6 +3571,10 @@ class ViewerLauncherWidget(QWidget):
def open_participant_brain_viewer(self, haemo_dict, cha_dict):
self.participant_brain_viewer = ParticipantBrainViewerWidget(haemo_dict, cha_dict)
self.participant_brain_viewer.show()
def open_participant_fold_channels_viewer(self, haemo_dict, cha_dict):
self.participant_fold_channels_viewer = ParticipantFoldChannelsWidget(haemo_dict, cha_dict)
self.participant_fold_channels_viewer.show()
def open_group_viewer(self, haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group):
self.participant_brain_viewer = GroupViewerWidget(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group)
@@ -3063,9 +3601,11 @@ class MainApplication(QMainWindow):
self.help = None
self.optodes = None
self.events = None
self.terminal = None
self.bubble_widgets = {}
self.param_sections = []
self.folder_paths = []
self.section_widget = None
self.init_ui()
self.create_menu_bar()
@@ -3304,6 +3844,15 @@ class MainApplication(QMainWindow):
if i == 1 or i == 3: # after the first 2 actions (0,1)
options_menu.addSeparator()
terminal_menu = menu_bar.addMenu("Terminal")
terminal_actions = [
("Cut", "Ctrl+X", self.terminal_gui, resource_path("icons/content_cut_24dp_1F1F1F.svg")),
("Copy", "Ctrl+C", self.terminal_gui, resource_path("icons/content_copy_24dp_1F1F1F.svg")),
("Paste", "Ctrl+V", self.terminal_gui, resource_path("icons/content_paste_24dp_1F1F1F.svg"))
]
for name, shortcut, slot, icon in terminal_actions:
terminal_menu.addAction(make_action(name, shortcut, slot, icon=icon))
self.statusbar = self.statusBar()
self.statusbar.showMessage("Ready")
@@ -3318,10 +3867,10 @@ class MainApplication(QMainWindow):
# Add ParamSection widgets from SECTIONS
for section in SECTIONS:
section_widget = ParamSection(section)
self.rows_layout.addWidget(section_widget)
self.section_widget = ParamSection(section)
self.rows_layout.addWidget(self.section_widget)
self.param_sections.append(section_widget)
self.param_sections.append(self.section_widget)
def clear_all(self):
@@ -3385,6 +3934,11 @@ class MainApplication(QMainWindow):
if self.help is None or not self.help.isVisible():
self.help = UserGuideWindow(self)
self.help.show()
def terminal_gui(self):
if self.terminal is None or not self.terminal.isVisible():
self.terminal = CommandConsole(self)
self.terminal.show()
def update_optode_positions(self):
if self.optodes is None or not self.optodes.isVisible():
@@ -3415,6 +3969,12 @@ class MainApplication(QMainWindow):
if folder_path:
self.selected_path = folder_path # store the folder path
self.show_files_as_bubbles(folder_path)
for section_widget in self.param_sections:
if "REMOVE_EVENTS" in section_widget.widgets:
section_widget.update_annotation_dropdown_from_folder(folder_path)
break
else:
print("[MainWindow] Could not find ParamSection with 'REMOVE_EVENTS' widget")
self.button1.setVisible(True)