general fixes
This commit is contained in:
11
changelog.md
11
changelog.md
@@ -1,3 +1,13 @@
|
|||||||
|
# Version 1.1.4
|
||||||
|
|
||||||
|
- Fixed some display text to now display the correct information
|
||||||
|
- A new option under Analysis has been added to export the data from a specified participant as a csv file
|
||||||
|
- Added 2 new parameters - TIME_WINDOW_START and TIME_WINDOW_END
|
||||||
|
- These parameters affect the visualization of the significance and contrast images but do not change the total time modeled underneath
|
||||||
|
- Fixed the duration of annotations edited from a BORIS file from 0 seconds to their proper duration
|
||||||
|
- Added the annotation information to each participant under their "File information" window
|
||||||
|
|
||||||
|
|
||||||
# Version 1.1.3
|
# Version 1.1.3
|
||||||
|
|
||||||
- Added back the ability to use the fOLD dataset. Fixes [Issue 23](https://git.research.dezeeuw.ca/tyler/flares/issues/23)
|
- Added back the ability to use the fOLD dataset. Fixes [Issue 23](https://git.research.dezeeuw.ca/tyler/flares/issues/23)
|
||||||
@@ -9,6 +19,7 @@
|
|||||||
- Added a terminal to interact with the app in a more command-like form
|
- Added a terminal to interact with the app in a more command-like form
|
||||||
- Currently the terminal has no functionality but some features for batch operations will be coming soon!
|
- Currently the terminal has no functionality but some features for batch operations will be coming soon!
|
||||||
- Inter-Group viewer now has the option to visualize the average response on the brain of all participants in the group. Fixes [Issue 26](https://git.research.dezeeuw.ca/tyler/flares/issues/24)
|
- Inter-Group viewer now has the option to visualize the average response on the brain of all participants in the group. Fixes [Issue 26](https://git.research.dezeeuw.ca/tyler/flares/issues/24)
|
||||||
|
- Fixed the description under "Update events in snirf file..."
|
||||||
|
|
||||||
|
|
||||||
# Version 1.1.2
|
# Version 1.1.2
|
||||||
|
|||||||
11
flares.py
11
flares.py
@@ -134,6 +134,9 @@ SHORT_CHANNEL: bool
|
|||||||
|
|
||||||
REMOVE_EVENTS: list
|
REMOVE_EVENTS: list
|
||||||
|
|
||||||
|
TIME_WINDOW_START: int
|
||||||
|
TIME_WINDOW_END: int
|
||||||
|
|
||||||
VERBOSITY = True
|
VERBOSITY = True
|
||||||
|
|
||||||
# FIXME: Shouldn't need each ordering - just order it before checking
|
# FIXME: Shouldn't need each ordering - just order it before checking
|
||||||
@@ -182,6 +185,8 @@ REQUIRED_KEYS: dict[str, Any] = {
|
|||||||
|
|
||||||
"SHORT_CHANNEL": bool,
|
"SHORT_CHANNEL": bool,
|
||||||
"REMOVE_EVENTS": list,
|
"REMOVE_EVENTS": list,
|
||||||
|
"TIME_WINDOW_START": int,
|
||||||
|
"TIME_WINDOW_END": int
|
||||||
# "REJECT_PAIRS": bool,
|
# "REJECT_PAIRS": bool,
|
||||||
# "FORCE_DROP_ANNOTATIONS": list,
|
# "FORCE_DROP_ANNOTATIONS": list,
|
||||||
# "FILTER_LOW_PASS": float,
|
# "FILTER_LOW_PASS": float,
|
||||||
@@ -3031,7 +3036,11 @@ def process_participant(file_path, progress_callback=None):
|
|||||||
contrast_dict = {}
|
contrast_dict = {}
|
||||||
|
|
||||||
for condition in all_conditions:
|
for condition in all_conditions:
|
||||||
delay_cols = [col for col in all_delay_cols if col.startswith(f"{condition}_delay_")]
|
delay_cols = [
|
||||||
|
col for col in all_delay_cols
|
||||||
|
if col.startswith(f"{condition}_delay_") and
|
||||||
|
TIME_WINDOW_START <= int(col.split("_delay_")[-1]) <= TIME_WINDOW_END
|
||||||
|
]
|
||||||
|
|
||||||
if not delay_cols:
|
if not delay_cols:
|
||||||
continue # skip if no columns found (shouldn't happen?)
|
continue # skip if no columns found (shouldn't happen?)
|
||||||
|
|||||||
318
main.py
318
main.py
@@ -45,7 +45,7 @@ from PySide6.QtGui import QAction, QKeySequence, QIcon, QIntValidator, QDoubleVa
|
|||||||
from PySide6.QtSvgWidgets import QSvgWidget # needed to show svgs when app is not frozen
|
from PySide6.QtSvgWidgets import QSvgWidget # needed to show svgs when app is not frozen
|
||||||
|
|
||||||
|
|
||||||
CURRENT_VERSION = "1.0.1"
|
CURRENT_VERSION = "1.0.0"
|
||||||
|
|
||||||
API_URL = "https://git.research.dezeeuw.ca/api/v1/repos/tyler/flares/releases"
|
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"
|
API_URL_SECONDARY = "https://git.research2.dezeeuw.ca/api/v1/repos/tyler/flares/releases"
|
||||||
@@ -155,6 +155,8 @@ SECTIONS = [
|
|||||||
{
|
{
|
||||||
"title": "General Linear Model",
|
"title": "General Linear Model",
|
||||||
"params": [
|
"params": [
|
||||||
|
{"name": "TIME_WINDOW_START", "default": "0", "type": int, "help": "Where to start averaging the fir model bins. Only affects the significance and contrast images."},
|
||||||
|
{"name": "TIME_WINDOW_END", "default": "15", "type": int, "help": "Where to end averaging the fir model bins. Only affects the significance and contrast images."},
|
||||||
#{"name": "N_JOBS", "default": 1, "type": int, "help": "Number of jobs for GLM processing."},
|
#{"name": "N_JOBS", "default": 1, "type": int, "help": "Number of jobs for GLM processing."},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1016,64 +1018,94 @@ class UpdateEventsWindow(QWidget):
|
|||||||
try:
|
try:
|
||||||
raw = read_raw_snirf(snirf_path, preload=True)
|
raw = read_raw_snirf(snirf_path, preload=True)
|
||||||
|
|
||||||
# Build new Annotations from shifted BORIS events
|
|
||||||
onsets = []
|
onsets = []
|
||||||
durations = []
|
durations = []
|
||||||
descriptions = []
|
descriptions = []
|
||||||
|
|
||||||
|
open_events = {} # label -> list of start times
|
||||||
label_counts = {}
|
label_counts = {}
|
||||||
|
|
||||||
used_times = set()
|
used_times = set()
|
||||||
|
|
||||||
sfreq = raw.info['sfreq'] # sampling frequency in Hz
|
sfreq = raw.info['sfreq'] # sampling frequency in Hz
|
||||||
min_shift = 1.0 / sfreq
|
min_shift = 1.0 / sfreq
|
||||||
|
|
||||||
max_attempts = 10
|
max_attempts = 10
|
||||||
|
|
||||||
for event in boris_events:
|
for event in boris_events:
|
||||||
if not isinstance(event, list) or len(event) < 3:
|
if not isinstance(event, list) or len(event) < 3:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
orig_time = event[0]
|
event_time = event[0]
|
||||||
desc = event[2]
|
label = event[2]
|
||||||
|
|
||||||
# Count occurrences per event label
|
count = label_counts.get(label, 0) + 1
|
||||||
count = label_counts.get(desc, 0)
|
label_counts[label] = count
|
||||||
label_counts[desc] = count + 1
|
|
||||||
|
|
||||||
# Only use 1st, 3rd, 5th... (odd occurrences)
|
if label not in open_events:
|
||||||
if (count % 2) == 0:
|
open_events[label] = []
|
||||||
shifted_time = orig_time + time_shift
|
|
||||||
|
|
||||||
# Ensure unique timestamp by checking and adjusting slightly
|
if count % 2 == 1:
|
||||||
adjusted_time = shifted_time
|
# Odd occurrence = start event
|
||||||
|
open_events[label].append(event_time)
|
||||||
|
else:
|
||||||
|
# Even occurrence = end event
|
||||||
|
if open_events[label]:
|
||||||
|
matched_start = open_events[label].pop(0)
|
||||||
|
duration = event_time - matched_start
|
||||||
|
|
||||||
# Try to find a unique timestamp
|
if duration <= 0:
|
||||||
|
print(f"Warning: Duration for {label} is non-positive ({duration}). Skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
shifted_start = matched_start + time_shift
|
||||||
|
|
||||||
|
adjusted_time = shifted_start
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while round(adjusted_time, 6) in used_times and attempts < max_attempts:
|
while round(adjusted_time, 6) in used_times and attempts < max_attempts:
|
||||||
adjusted_time += min_shift
|
adjusted_time += min_shift
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|
||||||
if attempts == max_attempts:
|
if attempts == max_attempts:
|
||||||
print(f"Warning: Could not find unique timestamp for event '{desc}' at original time {orig_time:.3f}s. Skipping.")
|
print(f"Warning: Couldn't find unique time for {label} @ {matched_start}s. Skipping.")
|
||||||
continue # Skip problematic event
|
continue
|
||||||
|
|
||||||
adjusted_time = round(adjusted_time, 6)
|
adjusted_time = round(adjusted_time, 6)
|
||||||
used_times.add(adjusted_time)
|
used_times.add(adjusted_time)
|
||||||
|
|
||||||
print(f"Applying event: {desc} @ {adjusted_time:.3f}s (original: {orig_time:.3f}s)")
|
print(f"Adding event: {label} @ {adjusted_time:.3f}s for {duration:.3f}s")
|
||||||
|
|
||||||
|
onsets.append(adjusted_time)
|
||||||
|
durations.append(duration)
|
||||||
|
descriptions.append(label)
|
||||||
|
else:
|
||||||
|
print(f"Warning: Unmatched end for label '{label}' at {event_time:.3f}s. Skipping.")
|
||||||
|
|
||||||
|
# Optionally warn about any unmatched starts left open
|
||||||
|
for label, starts in open_events.items():
|
||||||
|
for start_time in starts:
|
||||||
|
shifted_start = start_time + time_shift
|
||||||
|
|
||||||
|
adjusted_time = shifted_start
|
||||||
|
attempts = 0
|
||||||
|
while round(adjusted_time, 6) in used_times and attempts < max_attempts:
|
||||||
|
adjusted_time += min_shift
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
if attempts == max_attempts:
|
||||||
|
print(f"Warning: Couldn't find unique time for unmatched start {label} @ {start_time}s. Skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
adjusted_time = round(adjusted_time, 6)
|
||||||
|
used_times.add(adjusted_time)
|
||||||
|
|
||||||
|
print(f"Warning: Unmatched start for label '{label}' at {start_time:.3f}s. Adding with duration 0.")
|
||||||
onsets.append(adjusted_time)
|
onsets.append(adjusted_time)
|
||||||
durations.append(0.0)
|
durations.append(0.0)
|
||||||
descriptions.append(desc)
|
descriptions.append(label)
|
||||||
|
|
||||||
new_annotations = Annotations(onset=onsets, duration=durations, description=descriptions)
|
new_annotations = Annotations(onset=onsets, duration=durations, description=descriptions)
|
||||||
|
|
||||||
# Replace annotations in raw object
|
|
||||||
raw.set_annotations(new_annotations)
|
raw.set_annotations(new_annotations)
|
||||||
|
write_raw_snirf(raw, save_path)
|
||||||
# Write a new SNIRF file
|
|
||||||
write_raw_snirf(raw, suggested_name)
|
|
||||||
|
|
||||||
QMessageBox.information(self, "Success", "SNIRF file updated with aligned BORIS events.")
|
QMessageBox.information(self, "Success", "SNIRF file updated with aligned BORIS events.")
|
||||||
|
|
||||||
@@ -2197,7 +2229,7 @@ class ParticipantFoldChannelsWidget(QWidget):
|
|||||||
self.top_bar.addWidget(self.participant_dropdown)
|
self.top_bar.addWidget(self.participant_dropdown)
|
||||||
self.top_bar.addWidget(QLabel("Fold Type:"))
|
self.top_bar.addWidget(QLabel("Fold Type:"))
|
||||||
self.top_bar.addWidget(self.image_index_dropdown)
|
self.top_bar.addWidget(self.image_index_dropdown)
|
||||||
self.top_bar.addWidget(QLabel("This will cause the app to hang for ~30s!"))
|
self.top_bar.addWidget(QLabel("This will cause the app to hang for ~30s/Participant!"))
|
||||||
self.top_bar.addWidget(self.submit_button)
|
self.top_bar.addWidget(self.submit_button)
|
||||||
|
|
||||||
self.scroll = QScrollArea()
|
self.scroll = QScrollArea()
|
||||||
@@ -2352,6 +2384,226 @@ class ParticipantFoldChannelsWidget(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ExportDataAsCSVViewerWidget(QWidget):
|
||||||
|
def __init__(self, haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("FLARES Export Data As CSV Viewer")
|
||||||
|
self.haemo_dict = haemo_dict
|
||||||
|
self.cha_dict = cha_dict
|
||||||
|
self.df_ind = df_ind
|
||||||
|
self.design_matrix = design_matrix
|
||||||
|
self.group = group
|
||||||
|
self.contrast_results_dict = contrast_results_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 (Export Data to CSV)",
|
||||||
|
# "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.generate_and_save_csv)
|
||||||
|
|
||||||
|
self.top_bar.addWidget(QLabel("Participants:"))
|
||||||
|
self.top_bar.addWidget(self.participant_dropdown)
|
||||||
|
self.top_bar.addWidget(QLabel("Export 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 generate_and_save_csv(self):
|
||||||
|
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
try:
|
||||||
|
suggested_name = f"{file_path}.csv"
|
||||||
|
|
||||||
|
# Open save dialog
|
||||||
|
save_path, _ = QFileDialog.getSaveFileName(
|
||||||
|
self,
|
||||||
|
"Save SNIRF File As",
|
||||||
|
suggested_name,
|
||||||
|
"CSV Files (*.csv)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not save_path:
|
||||||
|
print("Save cancelled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not save_path.lower().endswith(".csv"):
|
||||||
|
save_path += ".csv"
|
||||||
|
# Save the CSV here
|
||||||
|
|
||||||
|
cha.to_csv(save_path)
|
||||||
|
QMessageBox.information(self, "Success", "CSV file has been saved.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}")
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"No method defined for index {idx}")
|
||||||
|
|
||||||
|
|
||||||
class ClickableLabel(QLabel):
|
class ClickableLabel(QLabel):
|
||||||
def __init__(self, full_pixmap: QPixmap, thumbnail_pixmap: QPixmap):
|
def __init__(self, full_pixmap: QPixmap, thumbnail_pixmap: QPixmap):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -3559,12 +3811,16 @@ class ViewerLauncherWidget(QWidget):
|
|||||||
btn5 = QPushButton("Open Cross Group Brain Viewer")
|
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))
|
btn5.clicked.connect(lambda: self.open_group_brain_viewer(haemo_dict, df_ind, design_matrix, group, contrast_results_dict))
|
||||||
|
|
||||||
|
btn6 = QPushButton("Open Export Data As CSV Viewer")
|
||||||
|
btn6.clicked.connect(lambda: self.open_export_data_as_csv_viewer(haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict))
|
||||||
|
|
||||||
|
|
||||||
layout.addWidget(btn1)
|
layout.addWidget(btn1)
|
||||||
layout.addWidget(btn2)
|
layout.addWidget(btn2)
|
||||||
layout.addWidget(btn3)
|
layout.addWidget(btn3)
|
||||||
layout.addWidget(btn4)
|
layout.addWidget(btn4)
|
||||||
layout.addWidget(btn5)
|
layout.addWidget(btn5)
|
||||||
|
layout.addWidget(btn6)
|
||||||
|
|
||||||
def open_participant_viewer(self, haemo_dict, fig_bytes_dict):
|
def open_participant_viewer(self, haemo_dict, fig_bytes_dict):
|
||||||
self.participant_viewer = ParticipantViewerWidget(haemo_dict, fig_bytes_dict)
|
self.participant_viewer = ParticipantViewerWidget(haemo_dict, fig_bytes_dict)
|
||||||
@@ -3586,6 +3842,10 @@ class ViewerLauncherWidget(QWidget):
|
|||||||
self.participant_brain_viewer = GroupBrainViewerWidget(haemo_dict, df_ind, design_matrix, group, contrast_results_dict)
|
self.participant_brain_viewer = GroupBrainViewerWidget(haemo_dict, df_ind, design_matrix, group, contrast_results_dict)
|
||||||
self.participant_brain_viewer.show()
|
self.participant_brain_viewer.show()
|
||||||
|
|
||||||
|
def open_export_data_as_csv_viewer(self, haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict):
|
||||||
|
self.export_data_as_csv_viewer = ExportDataAsCSVViewerWidget(haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict)
|
||||||
|
self.export_data_as_csv_viewer.show()
|
||||||
|
|
||||||
|
|
||||||
class MainApplication(QMainWindow):
|
class MainApplication(QMainWindow):
|
||||||
"""
|
"""
|
||||||
@@ -4655,6 +4915,16 @@ class MainApplication(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
snirf_info['Digitization Points'] = "Not found"
|
snirf_info['Digitization Points'] = "Not found"
|
||||||
|
|
||||||
|
if raw.annotations is not None and len(raw.annotations) > 0:
|
||||||
|
annot_info = []
|
||||||
|
for onset, duration, desc in zip(raw.annotations.onset,
|
||||||
|
raw.annotations.duration,
|
||||||
|
raw.annotations.description):
|
||||||
|
annot_info.append(f"Onset: {onset:.2f}s, Duration: {duration:.2f}s, Description: {desc}")
|
||||||
|
snirf_info['Annotations'] = annot_info
|
||||||
|
else:
|
||||||
|
snirf_info['Annotations'] = "No annotations found"
|
||||||
|
|
||||||
return snirf_info
|
return snirf_info
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user