Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 449 additions & 1 deletion probeflow/gui/dialogs/definitions.py

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion probeflow/gui/dialogs/image_viewer_chrome_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -506,6 +506,11 @@ def _build_viewer_menu_bar(self) -> None:
self._show_viewer_definitions,
)
help_menu.insertAction(github_action, definitions_action)
measurements_help_action = self._viewer_action(
"help.measurements",
self._show_viewer_measurements,
)
help_menu.insertAction(github_action, measurements_help_action)
roi_reference_help_action = self._viewer_action(
"help.roi_reference",
self._show_viewer_roi_reference,
Expand Down
9 changes: 5 additions & 4 deletions probeflow/gui/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()],
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions probeflow/gui/viewer/image_viewer_toolbar_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ def _show_viewer_definitions(self) -> None:
dlg.raise_()
dlg.activateWindow()

def _show_viewer_measurements(self) -> None:
dlg = getattr(self, "_definitions_dialog", None)
if dlg is None:
dlg = _DefinitionsDialog(self._t, self, initial_tab="measurements")
self._definitions_dialog = dlg
else:
dlg.set_reference_tab("measurements")
dlg.show()
dlg.raise_()
dlg.activateWindow()

def _show_viewer_roi_reference(self) -> None:
dlg = getattr(self, "_definitions_dialog", None)
if dlg is None:
Expand Down
5 changes: 5 additions & 0 deletions probeflow/gui/viewer/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ class ViewerCommand:
ViewerCommand("help.shortcuts", "Image viewer shortcuts", "Help", status_tip="Show image-viewer shortcut help.", aliases=("keyboard", "keys")),
ViewerCommand("help.howto", "How-to guides", "Help", status_tip="Show step-by-step how-to walkthroughs for common tasks.", aliases=("tutorial", "guide", "walkthrough", "getting started")),
ViewerCommand("help.definitions", "Definitions", "Help", status_tip="Show processing definitions and equations.", aliases=("reference", "math", "algorithms")),
ViewerCommand(
"help.measurements", "Measurements Reference", "Help",
status_tip="Show what each measurement computes (distance, step height, roughness, …).",
aliases=("measure", "measurement", "stats", "step height", "roughness"),
),
ViewerCommand(
"help.roi_reference", "ROI Reference", "Help",
status_tip="Show ROI actions, selection rules, and tool interactions.",
Expand Down
43 changes: 42 additions & 1 deletion tests/test_definitions_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_definitions_reference_has_equations_and_light_theme_contrast():
"STM background subtraction",
"Gaussian high-pass",
"Periodic notch filtering",
"Advanced edge detection (Canny",
"Manual zero reference",
"Image arithmetic",
"Thresholding and bit-depth conversion",
Expand All @@ -46,6 +47,9 @@ def test_definitions_reference_has_equations_and_light_theme_contrast():
"Forward/backward scan blending",
):
assert operation in html
# Advanced edge detection explains hysteresis and the mask/ROI outputs.
assert "hysteresis" in html
assert "Sobel / Scharr" in html

assert "color: #111827" in html
assert "#cdd6f4" not in lowered
Expand Down Expand Up @@ -120,25 +124,62 @@ def test_howto_reference_has_numbered_steps_and_key_workflows():
assert expected in html, expected


def test_measurements_reference_has_entries_and_formulas():
from probeflow.gui.dialogs.definitions import (
_MEASUREMENT_ENTRIES,
render_measurements_html,
)
from probeflow.gui.styling import THEMES

html = render_measurements_html(THEMES["light"])

# Every measurement entry renders an equation block and an "In practice" lead.
assert html.count('class="equation"') >= len(_MEASUREMENT_ENTRIES)
assert html.count("In practice:") >= len(_MEASUREMENT_ENTRIES)

for expected in (
"Measurements Reference",
"Distance",
"Angle",
"Line profile",
"Line periodicity",
"ROI statistics",
"Step height",
"Feature maxima",
"Pair correlation",
"Feature → lattice",
# A couple of formulas must match the implementation.
"rms_roughness = sqrt(mean((z - mean(z))^2))",
"height_difference = mean_b - mean_a",
# The line tool's drawing/editing is cross-referenced to the ROI tab.
"Line ROI actions",
):
assert expected in html, expected


def test_definitions_dialog_tabs_can_focus_howto_processing_and_roi(qapp):
from probeflow.gui.dialogs.definitions import _DefinitionsDialog
from probeflow.gui.styling import THEMES

default = _DefinitionsDialog(THEMES["light"])
roi_first = _DefinitionsDialog(THEMES["light"], initial_tab="roi")
howto_first = _DefinitionsDialog(THEMES["light"], initial_tab="howto")
measure_first = _DefinitionsDialog(THEMES["light"], initial_tab="measurements")
try:
assert default.current_reference_tab() == "processing"
default.set_reference_tab("roi")
assert default.current_reference_tab() == "roi"
default.set_reference_tab("measurements")
assert default.current_reference_tab() == "measurements"
default.set_reference_tab("howto")
assert default.current_reference_tab() == "howto"
default.set_reference_tab("processing")
assert default.current_reference_tab() == "processing"
assert roi_first.current_reference_tab() == "roi"
assert howto_first.current_reference_tab() == "howto"
assert measure_first.current_reference_tab() == "measurements"
finally:
for dlg in (default, roi_first, howto_first):
for dlg in (default, roi_first, howto_first, measure_first):
dlg.close()
dlg.deleteLater()
qapp.processEvents()
Expand Down
14 changes: 14 additions & 0 deletions tests/test_gui_processing_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions tests/test_viewer_shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def test_command_finder_shortcut_and_visible_commands_are_high_level():
assert "fft.periodic_filter" in finder_ids
assert "measure.clear_lattice_grid" in finder_ids
assert "help.definitions" in finder_ids
assert "help.measurements" in finder_ids
assert "help.roi_reference" in finder_ids
assert viewer_command("help.roi_reference").shortcuts == ()
assert not any(command_id.startswith("roi.tool.") for command_id in finder_ids)
Expand Down
Loading