From 276e1b18764f940409c9f54a7f94147e26ebfa14 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 16 Mar 2026 14:08:21 -0700 Subject: [PATCH] fix for xlsx and upgrade dependencies --- changelog.md | 2 ++ main.py | 72 +++++++++++++++++++++++++++++++++++++++++------ requirements.txt | Bin 2342 -> 2140 bytes 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index 5d522c5..adbbf56 100644 --- a/changelog.md +++ b/changelog.md @@ -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) - 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 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 diff --git a/main.py b/main.py index e0b22fa..efb2366 100644 --- a/main.py +++ b/main.py @@ -190,8 +190,7 @@ SECTIONS = [ {"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": "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": "MIN_ONSET", "default": -24, "type": int, "help": "Minimal onset relative to frame times (in seconds)"}, + {"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": "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": "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) 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: self.line_edit_file_b.setText(file_path) @@ -1730,26 +1729,31 @@ class ParamSection(QWidget): h_layout.addWidget(label) h_layout.setStretch(1, 3) # Set the stretch factor for label (40%) + default_val = param["default"] # Create input widget based on type if param["type"] == bool: widget = QComboBox() 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) elif param["type"] == int: widget = QLineEdit() 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: widget = QLineEdit() 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: if param.get("exclusive", True): widget = QComboBox() widget.addItems(param.get("options", [])) - widget.setCurrentText(str(param.get("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) else: widget = self._create_multiselect_dropdown(None) @@ -1757,16 +1761,16 @@ class ParamSection(QWidget): widget = QSpinBox() widget.setRange(0, 999) # Set a sensible maximum # If default is "None" or range(15), handle it gracefully: - default_val = param["default"] if isinstance(default_val, range): widget.setValue(default_val.stop) elif str(default_val).isdigit(): widget.setValue(int(default_val)) else: widget.setValue(15) # Default fallback + widget.valueChanged.connect(lambda val, p=param["name"]: self.check_if_changed(p, val)) else: widget = QLineEdit() - widget.setText(str(param["default"])) + widget.setText(str(default_val)) if "depends_on" in param: self.dependencies.append({ @@ -1783,12 +1787,62 @@ class ParamSection(QWidget): layout.addLayout(h_layout) self.widgets[param["name"]] = { "widget": widget, + "label": label, + "default": default_val, "type": param["type"], "h_layout": h_layout } 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): """ Since dependencies can cross sections, we need to tell diff --git a/requirements.txt b/requirements.txt index f7fb80c75df640ce5fd07f65bdd1a0a849b2ecde..120538dba724cb091736298c46ee8cadbc41c83a 100644 GIT binary patch delta 216 zcmZ1`bVq>c|G$lDER2e&3?&Tl3>6Hy3^@#G44FVOmBAJWjTrP83_y6YDx)f+;bd>d zPDbO&R~a`lT1;+dx(sATGt06XgH_Zriz}M|S%wU}3|tKP3Y5Qb-R9}z)Nn;=0{1VQlRZZDg}!ph3R!dfiiamK?CkGm5|l{)nlsVub* zR+bk20t*pr{3GhSi-?shyDYo!KKsnPFWxV2eSP+5Ko#|=pdRIPLHigX?NUS?teO^R zo@O`$FlPnicYb{ZVZqtjnY>6|ivQ;SmoDrzCq#Z=Um z6}JhNb_1-5tH%+}WBXN3BfK0^2`Qsy7=@*@%^y}O{3jWpge#0o5CTfD8-i4TdG^Oe zTh=c}?%am0K|egX4s%PMz2j}8a${v;NE=v%lN>^&MVT&{!ed$TlTFhtWyMM)Gvr1# wWlD?4$j37E4>wX!u5nvg&A|_=YkR(jR48wL###>Qw`lH1z3Ca*P