fix for xlsx and upgrade dependencies

This commit is contained in:
2026-03-16 14:08:21 -07:00
parent 3cfc6f01a2
commit 276e1b1876
3 changed files with 65 additions and 9 deletions

View File

@@ -4,6 +4,8 @@
- The BLAZES option will assign events that are exported directly from the software [BLAZES](https://git.research.dezeeuw.ca/tyler/blazes) - The BLAZES option will assign events that are exported directly from the software [BLAZES](https://git.research.dezeeuw.ca/tyler/blazes)
- Moved the updating logic to a seperate file for better reusability and generalization - Moved the updating logic to a seperate file for better reusability and generalization
- Fixed 'Toggle Status Bar' having no effect on the visibility of the status bar - Fixed 'Toggle Status Bar' having no effect on the visibility of the status bar
- Fixed a bug when updating optode positions that would prevent .txt files from being selected. Fixes [Issue 54](https://git.research.dezeeuw.ca/tyler/flares/issues/54)
- Fixed a missing dependency in the standalone application when attempting to use an .xlsx file to update optode positions
# Version 1.2.1 # Version 1.2.1

72
main.py
View File

@@ -190,8 +190,7 @@ SECTIONS = [
{"name": "DRIFT_MODEL", "default": ["cosine"], "type": list, "options": ["cosine", "polynomial"], "help": "Specifies the desired drift model."}, {"name": "DRIFT_MODEL", "default": ["cosine"], "type": list, "options": ["cosine", "polynomial"], "help": "Specifies the desired drift model."},
{"name": "HIGH_PASS", "default": 0.01, "type": float, "help": "High-pass frequency in case of a cosine model (in Hz)."}, {"name": "HIGH_PASS", "default": 0.01, "type": float, "help": "High-pass frequency in case of a cosine model (in Hz)."},
{"name": "DRIFT_ORDER", "default": 1, "type": int, "help": "Order of the drift model (in case it is polynomial)"}, {"name": "DRIFT_ORDER", "default": 1, "type": int, "help": "Order of the drift model (in case it is polynomial)"},
{"name": "FIR_DELAYS", "default": "None", "type": range, "depends_on": "HRF_MODEL", "depends_value": "fir", "help": "In case of FIR design, yields the array of delays used in the FIR model (in scans)."}, {"name": "FIR_DELAYS", "default": 15, "type": range, "depends_on": "HRF_MODEL", "depends_value": "fir", "help": "In case of FIR design, yields the array of delays used in the FIR model (in scans)."}, {"name": "MIN_ONSET", "default": -24, "type": int, "help": "Minimal onset relative to frame times (in seconds)"},
{"name": "MIN_ONSET", "default": -24, "type": int, "help": "Minimal onset relative to frame times (in seconds)"},
{"name": "OVERSAMPLING", "default": 50, "type": int, "help": "Oversampling factor used in temporal convolutions."}, {"name": "OVERSAMPLING", "default": 50, "type": int, "help": "Oversampling factor used in temporal convolutions."},
{"name": "REMOVE_EVENTS", "default": "None", "type": list, "help": "Remove events matching the names provided before generating the Design Matrix"}, {"name": "REMOVE_EVENTS", "default": "None", "type": list, "help": "Remove events matching the names provided before generating the Design Matrix"},
{"name": "SHORT_CHANNEL_REGRESSION", "default": True, "type": bool, "depends_on": "SHORT_CHANNEL", "help": "Should short channel regression be used to create the design matrix? This will use the 'signal' from the short channel and regress it out of all other channels."}, {"name": "SHORT_CHANNEL_REGRESSION", "default": True, "type": bool, "depends_on": "SHORT_CHANNEL", "help": "Should short channel regression be used to create the design matrix? This will use the 'signal' from the short channel and regress it out of all other channels."},
@@ -543,7 +542,7 @@ class UpdateOptodesWindow(QWidget):
self.line_edit_file_a.setText(file_path) self.line_edit_file_a.setText(file_path)
def browse_file_b(self): def browse_file_b(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "Text Files (*.txt), Excel Files (*.xlsx)") file_path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "Text Files (*.txt);;Excel Files (*.xlsx)")
if file_path: if file_path:
self.line_edit_file_b.setText(file_path) self.line_edit_file_b.setText(file_path)
@@ -1730,26 +1729,31 @@ class ParamSection(QWidget):
h_layout.addWidget(label) h_layout.addWidget(label)
h_layout.setStretch(1, 3) # Set the stretch factor for label (40%) h_layout.setStretch(1, 3) # Set the stretch factor for label (40%)
default_val = param["default"]
# Create input widget based on type # Create input widget based on type
if param["type"] == bool: if param["type"] == bool:
widget = QComboBox() widget = QComboBox()
widget.addItems(["True", "False"]) widget.addItems(["True", "False"])
widget.setCurrentText(str(param["default"])) widget.setCurrentText(str(default_val))
widget.currentTextChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val))
widget.currentTextChanged.connect(self.notify_global_update) widget.currentTextChanged.connect(self.notify_global_update)
elif param["type"] == int: elif param["type"] == int:
widget = QLineEdit() widget = QLineEdit()
widget.setValidator(QIntValidator()) widget.setValidator(QIntValidator())
widget.setText(str(param["default"])) widget.setText(str(default_val))
widget.textChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val))
elif param["type"] == float: elif param["type"] == float:
widget = QLineEdit() widget = QLineEdit()
widget.setValidator(QDoubleValidator()) widget.setValidator(QDoubleValidator())
widget.setText(str(param["default"])) widget.setText(str(default_val))
widget.textChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val))
elif param["type"] == list: elif param["type"] == list:
if param.get("exclusive", True): if param.get("exclusive", True):
widget = QComboBox() widget = QComboBox()
widget.addItems(param.get("options", [])) widget.addItems(param.get("options", []))
widget.setCurrentText(str(param.get("default", "<None Selected>"))) widget.setCurrentText(str(default_val))
widget.currentTextChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val))
widget.currentTextChanged.connect(self.notify_global_update) widget.currentTextChanged.connect(self.notify_global_update)
else: else:
widget = self._create_multiselect_dropdown(None) widget = self._create_multiselect_dropdown(None)
@@ -1757,16 +1761,16 @@ class ParamSection(QWidget):
widget = QSpinBox() widget = QSpinBox()
widget.setRange(0, 999) # Set a sensible maximum widget.setRange(0, 999) # Set a sensible maximum
# If default is "None" or range(15), handle it gracefully: # If default is "None" or range(15), handle it gracefully:
default_val = param["default"]
if isinstance(default_val, range): if isinstance(default_val, range):
widget.setValue(default_val.stop) widget.setValue(default_val.stop)
elif str(default_val).isdigit(): elif str(default_val).isdigit():
widget.setValue(int(default_val)) widget.setValue(int(default_val))
else: else:
widget.setValue(15) # Default fallback widget.setValue(15) # Default fallback
widget.valueChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val))
else: else:
widget = QLineEdit() widget = QLineEdit()
widget.setText(str(param["default"])) widget.setText(str(default_val))
if "depends_on" in param: if "depends_on" in param:
self.dependencies.append({ self.dependencies.append({
@@ -1783,12 +1787,62 @@ class ParamSection(QWidget):
layout.addLayout(h_layout) layout.addLayout(h_layout)
self.widgets[param["name"]] = { self.widgets[param["name"]] = {
"widget": widget, "widget": widget,
"label": label,
"default": default_val,
"type": param["type"], "type": param["type"],
"h_layout": h_layout "h_layout": h_layout
} }
self.update_dependencies() self.update_dependencies()
def check_if_changed(self, param_name, current_value):
"""Toggles bold font on the label if the value differs from default."""
info = self.widgets.get(param_name)
if not info:
return
label = info["label"]
default = info["default"]
is_changed = False
if info["type"] == list:
# If it's an exclusive ComboBox, current_value is a string.
# We wrap it in a list to compare it to the default list.
if isinstance(current_value, str):
normalized_current = [current_value]
else:
normalized_current = current_value # Already a list from multi-select
# Ensure default is a list for comparison
normalized_default = default if isinstance(default, list) else [default]
# Use sorted to ensure order doesn't matter
is_changed = sorted(normalized_current) != sorted(normalized_default)
# 2. Handle Range (SpinBox)
elif info["type"] == range:
ref = default.stop if isinstance(default, range) else default
try:
is_changed = int(current_value) != int(ref)
except (ValueError, TypeError):
is_changed = True
# 3. Standard Comparison (bool, int, float, str)
else:
is_changed = str(current_value) != str(default)
# Update Font
font = label.font()
font.setBold(is_changed)
label.setFont(font)
# Optional: Change color to make it even more obvious
if is_changed:
label.setStyleSheet("color: #3498db; font-weight: bold;") # Nice Blue
else:
label.setStyleSheet("color: none; font-weight: normal;")
def notify_global_update(self): def notify_global_update(self):
""" """
Since dependencies can cross sections, we need to tell Since dependencies can cross sections, we need to tell

Binary file not shown.