diff --git a/probeflow/gui/dialogs/image_viewer_chrome_mixin.py b/probeflow/gui/dialogs/image_viewer_chrome_mixin.py index e0398d1..e3df784 100644 --- a/probeflow/gui/dialogs/image_viewer_chrome_mixin.py +++ b/probeflow/gui/dialogs/image_viewer_chrome_mixin.py @@ -241,7 +241,7 @@ def _build_viewer_menu_bar(self) -> None: processing_menu.addSeparator() self._add_combo_menu( processing_menu, "Align rows", self._processing_panel._align_combo, - ["None", "Median", "Mean"], + ["None", "Median", "Mean", "Linear"], ) self._add_combo_menu( processing_menu, "Bad line correction", self._processing_panel._bad_lines_combo, diff --git a/probeflow/gui/processing.py b/probeflow/gui/processing.py index b0f2d9a..9dbd438 100644 --- a/probeflow/gui/processing.py +++ b/probeflow/gui/processing.py @@ -129,10 +129,11 @@ def _col_lbl(text: str, target): line_lbl.setAlignment(Qt.AlignCenter) lay.addWidget(line_lbl) - self._align_combo = _combo_row("Align rows:", ["None", "Median", "Mean"]) + self._align_combo = _combo_row("Align rows:", ["None", "Median", "Mean", "Linear"]) self._align_combo.setToolTip( "Level each scan line by subtracting its median or mean, removing " - "row-to-row offsets and slow tilt along the slow-scan direction." + "row-to-row offsets and slow tilt along the slow-scan direction. " + "'Linear' also fits and removes a straight slope within each row." ) self._bad_lines_combo = _combo_row( @@ -340,7 +341,7 @@ def _col_lbl(text: str, target): lay.addWidget(self._filter_section) def state(self) -> dict: - align_map = {0: None, 1: "median", 2: "mean"} + align_map = {0: None, 1: "median", 2: "mean", 3: "linear"} bad_map = {0: None, 1: "step", 2: "mad"} cfg = { "align_rows": align_map[self._align_combo.currentIndex()], @@ -382,7 +383,7 @@ def set_state(self, state: dict | None) -> None: state = state or {} old_block = self._align_combo.blockSignals(True) self._align_combo.setCurrentIndex( - {None: 0, "median": 1, "mean": 2}.get(state.get("align_rows"), 0)) + {None: 0, "median": 1, "mean": 2, "linear": 3}.get(state.get("align_rows"), 0)) self._align_combo.blockSignals(old_block) self._bad_lines_combo.setCurrentIndex( {None: 0, "step": 1, "step_segments": 1, diff --git a/tests/test_gui_processing_panel.py b/tests/test_gui_processing_panel.py index 8f446ae..91a92dd 100644 --- a/tests/test_gui_processing_panel.py +++ b/tests/test_gui_processing_panel.py @@ -145,6 +145,20 @@ def test_browse_quick_panel_emits_only_thumbnail_corrections(qapp): assert panel.state() == {"align_rows": "median", "remove_bad_lines": None} +def test_align_rows_exposes_linear_option(qapp): + # The backend align_rows supports median/mean/linear; the GUI must offer all + # three so the documented 'linear' option is actually reachable. + from probeflow.gui import ProcessingControlPanel + + panel = ProcessingControlPanel("viewer_full") + items = [panel._align_combo.itemText(i) for i in range(panel._align_combo.count())] + assert items == ["None", "Median", "Mean", "Linear"] + + panel.set_state({"align_rows": "linear"}) + assert panel._align_combo.currentIndex() == 3 + assert panel.state()["align_rows"] == "linear" + + def test_viewer_full_panel_round_trips_standard_processing_state(qapp): from PySide6.QtWidgets import QCheckBox, QLabel, QPushButton from probeflow.gui import ProcessingControlPanel