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 f7fb80c..120538d 100644 Binary files a/requirements.txt and b/requirements.txt differ