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
49 changes: 31 additions & 18 deletions probeflow/gui/dialogs/image_viewer_build_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,18 +312,20 @@ def _sidebar_tab(key: str, label: str, tip: str = "") -> tuple[QWidget, QVBoxLay
"processing", "Process",
"Line corrections, background subtraction, filters and FFT tools.",
)
_roi_tab, roi_lay = _sidebar_tab(
"roi", "ROI",
"Create, edit and combine regions of interest.",
)
_masks_tab, masks_lay = _sidebar_tab(
"masks", "Masks",
"Active mask layer: edge-detection output, cleanup, and conversion to ROIs.",
)
_measurements_tab, measurements_lay = _sidebar_tab(
"measurements", "Measure",
"Distances, angles, ROI statistics, features and results.",
)
# ROI and Masks share one tab; each is a collapsible section (built when
# the panels are created, below).
_roimask_tab, roimask_lay = _sidebar_tab(
"roi", "ROI/Mask",
"Regions of interest and the active mask layer (edge-detection "
"output, cleanup, conversion to ROIs).",
)
# Legacy alias so any "masks" navigation lands on the merged tab; no extra
# rail entry (the rail is driven by _sidebar_tab_meta only).
self._sidebar_tab_indices["masks"] = self._sidebar_tab_indices["roi"]
_export_tab, export_lay = _sidebar_tab(
"export", "Export",
"Save images (PNG/PDF/SXM/GWY), provenance and hand-off to tools.",
Expand Down Expand Up @@ -787,9 +789,8 @@ def _summary_row(row: int, name: str, attr: str, *, elide: bool = False) -> QLab
roi_hint_lbl.setFont(ui_font(8))
roi_hint_lbl.setWordWrap(True)
roi_hint_lbl.setStyleSheet("color: palette(mid);")
roi_lay.addWidget(roi_hint_lbl)
# The ROI manager panel itself is added to ``roi_lay`` after it is
# constructed below (it needs the ROI-set getter and callbacks).
# The hint and the ROI manager panel are added to the ROI collapsible
# section of the "ROI/Mask" tab after the panel is constructed below.

# The Measure tab is driven entirely by the ImageMeasurementsPanel's own
# tool menu (added to ``measurements_lay`` after it is constructed below);
Expand Down Expand Up @@ -831,8 +832,8 @@ def _summary_row(row: int, name: str, attr: str, *, elide: bool = False) -> QLab
rail_lay.addWidget(rail_sep)

_rail_abbrev = {
"View": "View", "Process": "Proc", "ROI": "ROI",
"Measure": "Meas", "Export": "Exp",
"View": "View", "Process": "Proc", "Measure": "Meas",
"ROI/Mask": "R/M", "Export": "Exp",
}
for _key, _label, _tip in self._sidebar_tab_meta:
rail_btn = QToolButton()
Expand Down Expand Up @@ -956,18 +957,30 @@ def _summary_row(row: int, name: str, attr: str, *, elide: bool = False) -> QLab
)
self._mask_panel.setObjectName("imageViewerMaskManagerPanel")

# ROI and Masks share the "ROI/Mask" tab as two collapsible sections
# (same pattern as the View tab's "Spectroscopy overlay"). ROI is open by
# default; Masks starts collapsed.
_roi_btn, _roi_body, roi_section_lay = _collapsible_section(
roimask_lay, "Regions of interest", expanded=True
)
roi_section_lay.addWidget(roi_hint_lbl)
roi_section_lay.addWidget(self._roi_panel, 1)

self._mask_section_btn, _mask_body, mask_section_lay = _collapsible_section(
roimask_lay, "Masks", expanded=False
)
mask_hint = QLabel(
"Masks come from Advanced Edge Detection (Process tab). The active "
"mask (●) restricts statistics and can become ROI(s)."
)
mask_hint.setWordWrap(True)
mask_hint.setFont(ui_font(8))
masks_lay.addWidget(mask_hint)
masks_lay.addWidget(self._mask_panel, 1)
mask_section_lay.addWidget(mask_hint)
mask_section_lay.addWidget(self._mask_panel, 1)
roimask_lay.addStretch(1)

# ROI manager and measurements now live in their sidebar tabs (built
# above) rather than in separate floating docks.
roi_lay.addWidget(self._roi_panel, 1)
# Measurements now lives in its sidebar tab (built above) rather than in a
# separate floating dock.
measurements_lay.addWidget(self._measurement_panel, 1)

# Manager for floating, dismissible tool panels over the canvas.
Expand Down
6 changes: 6 additions & 0 deletions probeflow/gui/dialogs/image_viewer_chrome_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,12 @@ def _show_sidebar_tab(self, key: str) -> None:
idx = self._sidebar_tab_indices.get(key)
if idx is not None:
self._sidebar_tabs.setCurrentIndex(idx)
# ROI and Masks share one tab; a request to show "masks" expands that
# collapsible section so the mask controls are revealed.
if key == "masks":
btn = getattr(self, "_mask_section_btn", None)
if btn is not None:
btn.setChecked(True)

def _show_sidebar_tool(self, title: str, widget, on_close=None) -> None:
"""Host an interactive tool's controls in the sidebar column (page 1).
Expand Down
4 changes: 2 additions & 2 deletions probeflow/gui/viewer/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class ViewerCommand:
),
ViewerCommand("panel.view", "Histogram / Contrast", "View", ("Ctrl+1",), "Show the View sidebar tab.", aliases=("display", "contrast", "histogram")),
ViewerCommand("panel.process", "Processing panel", "View", ("Ctrl+2",), "Show the Process sidebar tab.", aliases=("process", "processing")),
ViewerCommand("panel.roi", "ROI panel", "View", ("Ctrl+3",), "Show the ROI sidebar tab.", aliases=("roi", "selection")),
ViewerCommand("panel.measure", "Measurements panel", "View", ("Ctrl+4",), "Show the Measure sidebar tab.", aliases=("measure", "measurement")),
ViewerCommand("panel.measure", "Measurements panel", "View", ("Ctrl+3",), "Show the Measure sidebar tab.", aliases=("measure", "measurement")),
ViewerCommand("panel.roi", "ROI / Mask panel", "View", ("Ctrl+4",), "Show the ROI/Mask sidebar tab.", aliases=("roi", "mask", "selection")),
ViewerCommand("panel.export", "Export panel", "View", ("Ctrl+5",), "Show the Export sidebar tab.", aliases=("save", "write")),
ViewerCommand("view.fit", "Fit image to window", "View", ("Ctrl+0",), "Fit the image to the visible canvas.", aliases=("zoom", "fit")),
ViewerCommand("view.one_to_one", "View at 1:1", "View", ("Ctrl+Shift+0",), "View the image at native raster size.", aliases=("native", "actual size")),
Expand Down
25 changes: 25 additions & 0 deletions tests/test_gui_processing_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,31 @@ def test_viewer_full_panel_round_trips_standard_processing_state(qapp):
)


def test_sidebar_merges_roi_mask_and_orders_tabs(qapp, monkeypatch):
from probeflow.gui import ImageViewerDialog, SxmFile, THEMES

monkeypatch.setattr(ImageViewerDialog, "_load_current", lambda self: None)
entry = SxmFile(path=Path("/tmp/example.sxm"), stem="example", Nx=8, Ny=8)
dlg = ImageViewerDialog(entry, [entry], "gray", THEMES["dark"])
try:
tabs = dlg._sidebar_tabs
assert [tabs.tabText(i) for i in range(tabs.count())] == [
"View", "Process", "Measure", "ROI/Mask", "Export",
]
# The old "masks" key aliases onto the merged tab (no separate tab).
assert dlg._sidebar_tab_indices["masks"] == dlg._sidebar_tab_indices["roi"]
# ROI section open, Masks section collapsed by default.
assert dlg._mask_section_btn.isChecked() is False
# Navigating to "masks" selects the merged tab and reveals the section.
dlg._show_sidebar_tab("masks")
assert tabs.tabText(tabs.currentIndex()) == "ROI/Mask"
assert dlg._mask_section_btn.isChecked() is True
finally:
dlg.close()
dlg.deleteLater()
qapp.processEvents()


def test_format_gaussian_readout_pure():
from probeflow.gui.processing import format_gaussian_readout

Expand Down
Loading