From e746fa4df60c92c0d6ce6ff70eb8985dfa034b2e Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 22 Feb 2026 15:30:12 +0000 Subject: [PATCH] feat(controller_params): implement class ScanSlicerParamsMixin --- scanslicer/controller_params.py | 225 ++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/scanslicer/controller_params.py b/scanslicer/controller_params.py index 5a04a97..5c7c33f 100644 --- a/scanslicer/controller_params.py +++ b/scanslicer/controller_params.py @@ -2,3 +2,228 @@ from PySide6.QtWidgets import QComboBox, QInputDialog, QMessageBox, QSlider # ScanSlicerParamsMixin: class definition for ScanSlicer behavior. +class ScanSlicerParamsMixin: + # set_autotune_profile: function definition for ScanSlicer behavior. + 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) + + # set_show_advanced_parameters: function definition for ScanSlicer behavior. + def set_show_advanced_parameters(self, checked): + self.show_advanced_params = bool(checked) + self.update_parameter_visibility() + + # sync_param_controls_from_model: function definition for ScanSlicer behavior. + 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() + + # update_parameter_visibility: function definition for ScanSlicer behavior. + 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) + + # load_default_parameters: function definition for ScanSlicer behavior. + 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).") + + # save_default_parameters: function definition for ScanSlicer behavior. + 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) + + # autotune_parameters: function definition for ScanSlicer behavior. + 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() + + # clear_autotune_history: function definition for ScanSlicer behavior. + 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.") + + # set_preset_dropdown_custom: function definition for ScanSlicer behavior. + 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) + + # refresh_preset_dropdown: function definition for ScanSlicer behavior. + 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) + + # save_current_as_preset: function definition for ScanSlicer behavior. + 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) + + # delete_current_preset: function definition for ScanSlicer behavior. + 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) + + # apply_parameter_preset: function definition for ScanSlicer behavior. + 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)