from PySide6.QtWidgets import QComboBox, QInputDialog, QMessageBox, QSlider class ScanSlicerParamsMixin: def set_autotune_profile(self, profile_name): normalized = self.autoslicer.normalize_autotune_profile(profile_name) self.autotune_profile = normalized dropdown = getattr(self, "autotune_profile_dropdown", None) if dropdown is not None and dropdown.currentText() != normalized: dropdown.blockSignals(True) dropdown.setCurrentText(normalized) dropdown.blockSignals(False) def set_show_advanced_parameters(self, checked): self.show_advanced_params = bool(checked) self.update_parameter_visibility() def sync_param_controls_from_model(self): self.updating_param_controls = True for name, p in self.params.items(): control = self.param_controls[name] if isinstance(control, QSlider): control.setValue(p.get()) elif isinstance(control, QComboBox): idx = control.findData(p.get()) if idx >= 0: control.setCurrentIndex(idx) self.updating_param_controls = False self.update_parameter_visibility() def update_parameter_visibility(self): pipeline = self.params.detect_pipeline.get() method = self.params.bw_method.get() morph_op = self.params.morph_op.get() show_advanced = bool(getattr(self, "show_advanced_params", False)) advanced_params = set(getattr(self, "ADVANCED_PARAMS", set())) visibility = { "bw_method": pipeline == 0, "bw_thresh": pipeline == 0 and method == 0, "bw_gauss": pipeline == 0 and method == 1, "bw_adaptive_c": pipeline == 0 and method == 1, "canny_low": pipeline == 1, "canny_high": pipeline == 1, "dilate_kernel": morph_op != 0, } for param_name, block in self.param_blocks.items(): if block is None: continue is_visible = visibility.get(param_name, True) if param_name in advanced_params and not show_advanced: is_visible = False block.setVisible(is_visible) def load_default_parameters(self): mode = self.params.load_defaults() self.sync_param_controls_from_model() self.set_preset_dropdown_custom() if mode == "saved": self.update_statusbar("Loaded saved defaults.") else: self.update_statusbar("Loaded factory defaults (no saved defaults found).") def save_default_parameters(self): try: path = self.params.save_user_defaults() except OSError as exc: QMessageBox.critical(self, "Save defaults failed", str(exc)) self.update_statusbar("Failed to save defaults.") return self.update_statusbar("Saved defaults to " + path) def autotune_parameters(self): if self.processing_busy: return if not self.autoslicer.image_loaded(): QMessageBox.warning(self, "No image loaded", "Load an image first") return self._start_autotune_worker() def clear_autotune_history(self): if self.processing_busy: return entry_count = len(self.autoslicer.feedback_entries) prompt = ( "Clear auto-tune feedback history?\n\n" "This removes learned examples from previous saves and resets auto-tune learning." ) if entry_count > 0: prompt += f"\n\nSaved examples: {entry_count}" choice = QMessageBox.question( self, "Clear auto-tune history", prompt, QMessageBox.Yes | QMessageBox.No, QMessageBox.No, ) if choice != QMessageBox.Yes: return try: result = self.autoslicer.clear_feedback_entries() except OSError as exc: QMessageBox.critical(self, "Clear history failed", str(exc)) self.update_statusbar("Failed to clear auto-tune history.") return self.append_status_log( "Auto-tune feedback cleared: " + f"entries={result['cleared_count']}, path={result['path']}, removed_file={result['removed_file']}" ) self.update_statusbar("Auto-tune history cleared.") def set_preset_dropdown_custom(self): if self.preset_dropdown.currentText() == "Custom": return self.preset_dropdown.blockSignals(True) self.preset_dropdown.setCurrentText("Custom") self.preset_dropdown.blockSignals(False) def refresh_preset_dropdown(self, selected_name=None): if selected_name is None: selected_name = self.preset_dropdown.currentText() self.preset_dropdown.blockSignals(True) self.preset_dropdown.clear() self.preset_dropdown.addItem("Custom") self.preset_dropdown.addItems(self.params.preset_names()) if selected_name and self.preset_dropdown.findText(selected_name) >= 0: self.preset_dropdown.setCurrentText(selected_name) else: self.preset_dropdown.setCurrentText("Custom") self.preset_dropdown.blockSignals(False) def save_current_as_preset(self): if self.processing_busy: return suggested_name = self.preset_dropdown.currentText() if suggested_name == "Custom": suggested_name = "My Preset" preset_name, ok = QInputDialog.getText( self, "Save Preset", "Preset name:", text=suggested_name, ) if not ok: return preset_name = preset_name.strip() if len(preset_name) == 0: QMessageBox.warning(self, "Invalid preset name", "Preset name cannot be empty.") return try: saved_name = self.params.save_named_preset(preset_name) except ValueError as exc: QMessageBox.warning(self, "Invalid preset name", str(exc)) return except OSError as exc: QMessageBox.critical(self, "Save preset failed", str(exc)) return self.refresh_preset_dropdown(selected_name=saved_name) self.update_statusbar("Saved preset: " + saved_name) def delete_current_preset(self): if self.processing_busy: return preset_name = self.preset_dropdown.currentText().strip() if len(preset_name) == 0 or preset_name == "Custom": QMessageBox.warning(self, "No preset selected", "Select a user preset to delete.") return choice = QMessageBox.question( self, "Delete Preset", f"Delete preset '{preset_name}'?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No, ) if choice != QMessageBox.Yes: return try: deleted_name = self.params.delete_named_preset(preset_name) except ValueError as exc: QMessageBox.warning(self, "Delete preset failed", str(exc)) return except OSError as exc: QMessageBox.critical(self, "Delete preset failed", str(exc)) return self.refresh_preset_dropdown(selected_name="Custom") self.update_statusbar("Deleted preset: " + deleted_name) def apply_parameter_preset(self, preset_name): if preset_name == "Custom": return if not self.params.apply_preset(preset_name): return self.sync_param_controls_from_model() self.preset_dropdown.blockSignals(True) self.preset_dropdown.setCurrentText(preset_name) self.preset_dropdown.blockSignals(False) self.update_statusbar("Applied preset: " + preset_name)