style: compact the control panels

Vertical-space pass: samcam merges exposure+gain onto one row, ABR and
monochromator tighten their grids (energy now displayed in keV, DAQ API
stays eV), beam-mark's clear button shares the readings row, the data
collection pages move Run/Abort under the last configurable row and the
tab area becomes QTabBar+QStackedWidget, the raster grid table gets a
fixed height, and the automation panel drops its own title since the
dock title bar already says it. Control panels start expanded via
default_collapsed=False.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 10:35:46 +02:00
co-authored by Claude Fable 5
parent 6e1305cb92
commit 5f44be2a62
12 changed files with 285 additions and 193 deletions
+19 -13
View File
@@ -92,32 +92,38 @@ class AbrTweakWidget(QWidget):
super().__init__(parent)
grid_layout = QGridLayout(self)
grid_layout.setColumnStretch(0, 0)
grid_layout.setColumnStretch(1, 0)
grid_layout.setColumnStretch(2, 1)
# Span all 4 grid columns (the ABR buttons row uses 4), otherwise the
# banner renders narrower than the neighboring panels.
grid_layout.addWidget(TitleLabel("ABR meas. pos.", self, collapsible=True), 0, 0, 1, 4)
grid_layout.addWidget(TitleLabel("ABR meas. pos.", self, collapsible=True, default_collapsed=False), 0, 0, 1, 2)
self._abr_buttons = AbrTweakButtons(DEFAULT_ABR_STEP_UM / 1000, parent=self)
grid_layout.addWidget(self._abr_buttons, 1, 0, 1, 4)
grid_layout.addWidget(self._abr_buttons, 1, 0)
self._abr_buttons.abr_tweak.connect(self.abr_button_pressed)
grid_layout.addWidget(QLabel("Step", parent=self), 2, 0)
self._step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=self)
grid_layout.addWidget(self._step_um, 2, 1)
grid_layout.addWidget(QLabel("μm", parent=self), 2, 2)
# Step + actions as a column BESIDE the GM rows instead of below —
# the rows left plenty of dead width.
side = QWidget(self)
side_grid = QGridLayout(side)
side_grid.setContentsMargins(0, 0, 0, 0)
side_grid.addWidget(QLabel("Step", parent=side), 0, 0)
self._step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=side)
side_grid.addWidget(self._step_um, 0, 1)
side_grid.addWidget(QLabel("μm", parent=side), 0, 2)
self._step_um.newValue.connect(self._abr_buttons.set_step)
save_button = QPushButton("Save ABR pos.")
grid_layout.addWidget(save_button, 3, 0, 1, 3)
side_grid.addWidget(save_button, 1, 0, 1, 3)
save_button.pressed.connect(self.save_button_pressed)
goto_button = QPushButton("Go to meas.")
grid_layout.addWidget(goto_button, 4, 0, 1, 3)
side_grid.addWidget(goto_button, 2, 0, 1, 3)
goto_button.pressed.connect(self.goto_button_pressed)
# ~80% of the width the grid handed it; the freed space goes to the
# GM rows (column 0 takes all stretch).
side.setMaximumWidth(150)
grid_layout.setColumnStretch(0, 1)
grid_layout.addWidget(side, 1, 1)
@Slot()
def goto_button_pressed(self):
self.abr_goto_meas.emit()
+44 -34
View File
@@ -1,18 +1,16 @@
from __future__ import annotations
import copy
import time
from datetime import datetime
from aarecommon.config.logger import setup_logger
from aarecommon.models.automation import AutomationProgress, StepStatus, WorkflowStateKind
from PySide6.QtCore import QTimer, Slot
from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget
from PySide6.QtCore import Qt, QTimer, Slot
from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
from aare.gui.constants import LOGGER_NAME
from aare.gui.styles import (
AUTOMATION_HINT_TEXT,
AUTOMATION_TITLE_TEXT,
CARD_BORDER,
FAINT_TEXT,
FONT_BODY,
@@ -49,7 +47,6 @@ class AutomationProgressWidget(QWidget):
self._progress: AutomationProgress | None = None
self._gui_samples_in_queue: int | None = None
self._labels: dict[WorkflowStateKind, QLabel] = {}
self._title_label: QLabel | None = None
self._stats_label: QLabel | None = None
self._is_paused = False
self._refresh_timer = QTimer(self)
@@ -59,36 +56,45 @@ class AutomationProgressWidget(QWidget):
self.clear()
def _setup_ui(self) -> None:
# No title label: the dock's title bar already says it.
layout = QVBoxLayout(self)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(10)
self._title_label = QLabel("Automation progress")
self._title_label.setStyleSheet(
f"font-size: {FONT_TITLE}; font-weight: 700; color: {AUTOMATION_TITLE_TEXT}; margin-bottom: 2px;"
)
layout.addWidget(self._title_label)
self._stats_label = QLabel()
self._stats_label.setStyleSheet(
f"color: {AUTOMATION_HINT_TEXT}; font-size: {FONT_LABEL}; font-weight: 700; "
f"background-color: {SURFACE}; border: 1px solid {CARD_BORDER}; "
"border-radius: 8px; padding: 10px;"
"padding: 10px;"
)
self._stats_label.setWordWrap(True)
layout.addWidget(self._stats_label)
# Stats left, automation run-state card right ("||" paused / "▶"
# running — ASCII bars: fancier pause glyphs are missing from the
# beamline console fonts).
self._state_label = QLabel()
self._state_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
top_row = QHBoxLayout()
top_row.setSpacing(10)
top_row.addWidget(self._stats_label, 2)
top_row.addWidget(self._state_label, 1)
layout.addLayout(top_row)
self._update_state_label()
steps_row = QHBoxLayout()
steps_row.setSpacing(6)
for step in (
WorkflowStateKind.MOUNT,
WorkflowStateKind.LOOP_CENTRE,
WorkflowStateKind.RASTER,
WorkflowStateKind.DATA_COLLECTION,
WorkflowStateKind.FINAL,
):
label = QLabel()
label.setWordWrap(True)
layout.addWidget(label)
steps_row.addWidget(label, 1)
self._labels[step] = label
layout.addLayout(steps_row)
layout.addStretch()
@@ -142,7 +148,7 @@ class AutomationProgressWidget(QWidget):
@staticmethod
def _style_for_status(status: StepStatus) -> str:
base = (
"padding: 10px 12px; border-radius: 10px; "
"padding: 10px 12px; "
f"font-size: {FONT_BODY}; border: 1px solid transparent;"
)
@@ -219,32 +225,38 @@ class AutomationProgressWidget(QWidget):
return
self.set_progress(self._progress)
def _update_state_label(self) -> None:
if self._is_paused:
colors = (
f"background-color: {STEP_PAUSED_BG}; color: {STEP_PAUSED_TEXT}; "
f"border: 1px solid {STEP_PAUSED_BORDER};"
)
text = "|| paused"
else:
colors = (
f"background-color: {STEP_ACTIVE_BG}; color: {STEP_ACTIVE_TEXT}; "
f"border: 1px solid {STEP_ACTIVE_BORDER};"
)
text = "▶ running"
self._state_label.setText(text)
self._state_label.setStyleSheet(
f"font-size: {FONT_TITLE}; font-weight: 700; padding: 10px; " + colors
)
@Slot(bool)
def set_running(self, running: bool) -> None:
self._is_paused = not running
self._update_state_label()
if self._progress is None:
return
progress = copy.deepcopy(self._progress)
final_step = next(
(step for step in progress.steps if step.step == WorkflowStateKind.FINAL), None
)
if self._is_paused:
self._refresh_timer.stop()
if final_step is not None:
final_step.status = StepStatus.PAUSED
final_step.message = "Automation paused"
else:
if final_step is not None and final_step.status == StepStatus.PAUSED:
final_step.status = StepStatus.PENDING
final_step.message = ""
if self._has_live_timing(progress):
self._refresh_timer.start()
elif self._has_live_timing(self._progress):
self._refresh_timer.start()
self.set_progress(progress)
self.set_progress(self._progress)
@Slot(int)
def set_samples_in_queue(self, count: int) -> None:
@@ -318,5 +330,3 @@ class AutomationProgressWidget(QWidget):
label.setText(f"{icon} <b>{title}</b>{duration_str}{message}{error_str}")
label.setStyleSheet(self._style_for_status(step_state.status))
if self._title_label is not None:
self._title_label.setText("Automation progress")
+5 -2
View File
@@ -12,8 +12,9 @@ class BeamMarkWidget(QWidget):
super().__init__(parent)
grid_layout = QGridLayout(self)
grid_layout.setVerticalSpacing(2)
grid_layout.addWidget(section_title("Beam mark (image)", self), 0, 0, 1, 5)
grid_layout.addWidget(section_title("Beam mark (image)", self), 0, 0, 1, 6)
self.x = QLabel("0")
self.y = QLabel("0")
@@ -24,8 +25,10 @@ class BeamMarkWidget(QWidget):
grid_layout.addWidget(self.y, 1, 3)
grid_layout.addWidget(QLabel("pxl"), 1, 4)
# Shares the readings row instead of a full-width row below.
clear_button = QPushButton("Clear marks")
grid_layout.addWidget(clear_button, 2, 0, 1, 5)
clear_button.setFixedWidth(90)
grid_layout.addWidget(clear_button, 1, 5)
clear_button.pressed.connect(self.clear_button_pressed)
@Slot()
+78 -21
View File
@@ -2,7 +2,16 @@ from aarecommon.math.diffraction_geometry import DiffractionGeometry
from aarecommon.math.sample_geometry import SampleGeometryModel
from aarecommon.models.models import DAQStatusModel
from PySide6.QtCore import Signal, Slot
from PySide6.QtWidgets import QFrame, QPushButton, QTabWidget, QVBoxLayout, QWidget
from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
QPushButton,
QSizePolicy,
QStackedWidget,
QTabBar,
QVBoxLayout,
QWidget,
)
from aare.gui.panels.file_path_panel import FilePathPanel
from aare.gui.panels.fluorescence_data_collection import FluorescenceDataCollectionPanel
@@ -11,7 +20,6 @@ from aare.gui.panels.raster_data_collection import RasterDataCollectionPanel
from aare.gui.panels.rotation_data_collection import RotationDataCollectionPanel
from aare.gui.panels.smart_rotation_panel import SimpleRotationSettingsPanel
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager
from aare.gui.styles import ABORT_TEXT
from aare.gui.widgets.title_label import TitleLabel, tighten_column
@@ -42,39 +50,72 @@ class DataCollectionSettings(QFrame):
self.manual_sample_panel = ManualSamplePanel(self)
v_layout.addWidget(self.manual_sample_panel)
self._tab_widget = QTabWidget()
# QTabBar + QStackedWidget instead of QTabWidget: the loop-centering
# button row must sit BETWEEN the tab bar and the pages, which a
# QTabWidget cannot host.
self._tab_bar = QTabBar(self)
self._stack = QStackedWidget(self)
self.raster = RasterDataCollectionPanel(
parent=self, raster_mgr=raster_mgr, diffraction=diffraction
)
self._tab_widget.addTab(self.raster, "Raster scan")
self.screening = RotationDataCollectionPanel(parent=self, diffraction=diffraction)
self._tab_widget.addTab(self.screening, "Rotation")
self.simple = SimpleRotationSettingsPanel(parent=self)
self._tab_widget.addTab(self.simple, "Simple")
self.fluo = FluorescenceDataCollectionPanel(parent=self)
self._tab_widget.addTab(self.fluo, "XRF")
for panel, label in (
(self.raster, "Raster scan"),
(self.screening, "Rotation"),
(self.simple, "Simple"),
(self.fluo, "XRF"),
):
self._stack.addWidget(panel)
self._tab_bar.addTab(label)
# Ex-"Loop centering" panel buttons; always visible, whatever the tab.
self.find_tip = QPushButton("ML Loop Centring", parent=self)
self.bounding_box = QPushButton("Make Raster Grid", parent=self)
centering_row = QWidget(self)
centering_layout = QHBoxLayout(centering_row)
centering_layout.setContentsMargins(0, 0, 0, 0)
centering_layout.addWidget(self.find_tip)
centering_layout.addWidget(self.bounding_box)
# Pane frame carries the border QTabWidget::pane used to draw
# (#expConfigPane rule in styles.py).
pane = QFrame(self)
pane.setObjectName("expConfigPane")
pane_layout = QVBoxLayout(pane)
# No bottom padding: the pages' own bottom margins breathe inside the
# border, and the Abort button should hug the pane.
pane_layout.setContentsMargins(6, 6, 6, 0)
pane_layout.addWidget(centering_row)
pane_layout.addWidget(self._stack)
# Own container: TitleLabel collapse hides its siblings, so without it
# "Exp. Config." would also swallow the dataset path and abort button.
exp_config = QWidget(self)
exp_config_layout = QVBoxLayout(exp_config)
exp_config_layout.setContentsMargins(0, 0, 0, 0)
exp_config_layout.addWidget(TitleLabel("Exp. Config.", exp_config, collapsible=True))
exp_config_layout.addWidget(self._tab_widget)
exp_config_layout.setSpacing(0) # tab bar flush on the pane, like QTabWidget
exp_config_layout.addWidget(
TitleLabel(
"Experiment configuration", exp_config, collapsible=True, default_collapsed=False
)
)
exp_config_layout.addWidget(self._tab_bar)
exp_config_layout.addWidget(pane)
v_layout.addWidget(exp_config)
abort_button = QPushButton("Abort measurement", parent=self)
abort_button.setStyleSheet(f"color: {ABORT_TEXT};")
abort_button.clicked.connect(self.cancel_button_clicked)
v_layout.addWidget(abort_button)
# Stretch after the button: abort sits snug under the tabs instead of
# being pinned to the bottom of the fixed-height column.
# Abort lives inside each tab now, under that tab's action buttons;
# all four feed the same cancel signal.
for panel in (self.raster, self.screening, self.simple, self.fluo):
panel.abort_button.clicked.connect(self.cancel_button_clicked)
v_layout.addStretch()
tighten_column(v_layout)
# Abort hugs the pane: undo the uniform bottom margin tighten_column
# just gave the Exp. Config. group.
m = exp_config_layout.contentsMargins()
exp_config_layout.setContentsMargins(m.left(), m.top(), m.right(), 0)
raster_mgr.update_filename(self.file_path_panel.filename)
self.screening.update_filename(self.file_path_panel.filename)
@@ -86,11 +127,27 @@ class DataCollectionSettings(QFrame):
self.file_path_panel.path_updated.connect(self.simple.update_filename)
self._sample_id = None
self._tab_widget.currentChanged.connect(self._on_tab_changed)
self._tab_bar.currentChanged.connect(self._stack.setCurrentIndex)
self._tab_bar.currentChanged.connect(self._on_tab_changed)
self._tab_bar.currentChanged.connect(self._sync_stack_height)
self._sync_stack_height(self._tab_bar.currentIndex())
@Slot(int)
def _sync_stack_height(self, idx: int):
# QStackedWidget's sizeHint is its TALLEST page, which left a dead gap
# above the Abort button on shorter tabs. Ignored vertical policy on
# hidden pages makes the stack track only the current page's height.
for i in range(self._stack.count()):
page = self._stack.widget(i)
vertical = (
QSizePolicy.Policy.Preferred if i == idx else QSizePolicy.Policy.Ignored
)
page.setSizePolicy(QSizePolicy.Policy.Preferred, vertical)
self._stack.adjustSize()
@Slot()
def switch_to_raster(self):
self._tab_widget.setCurrentIndex(0)
self._tab_bar.setCurrentIndex(0)
@Slot()
def cancel_button_clicked(self):
@@ -103,7 +160,7 @@ class DataCollectionSettings(QFrame):
self.simple.update_daq_status(s)
if s.sample is not None and s.sample.db_id != self._sample_id:
self._sample_id = s.sample.db_id
self._tab_widget.setCurrentIndex(0)
self._tab_bar.setCurrentIndex(0)
@Slot(int)
def _on_tab_changed(self, idx: int):
@@ -2,7 +2,7 @@ from aarecommon.models.models import FluorescenceSpectrumParameterModel
from PySide6.QtCore import Signal, Slot
from PySide6.QtWidgets import QCheckBox, QGridLayout, QLabel, QPushButton, QWidget
from aare.gui.styles import GO_TEXT
from aare.gui.styles import ABORT_TEXT, GO_TEXT
from aare.gui.widgets.number_line_edit import NumberLineEdit
@@ -38,6 +38,11 @@ class FluorescenceDataCollectionPanel(QWidget):
self.run_btn.setStyleSheet(f"color: {GO_TEXT};")
lay.addWidget(self.run_btn, 4, 0, 1, 3)
# Per-tab Abort (DataCollectionSettings wires it to the DAQ cancel).
self.abort_button = QPushButton("Abort measurement", self)
self.abort_button.setStyleSheet(f"color: {ABORT_TEXT};")
lay.addWidget(self.abort_button, 5, 0, 1, 3)
self.run_btn.clicked.connect(self._emit_params)
@Slot()
+11 -9
View File
@@ -13,29 +13,31 @@ class MonochromatorPanel(QWidget):
super().__init__(parent)
grid_layout = QGridLayout(self)
grid_layout.addWidget(TitleLabel("Monochromator", self, collapsible=True), 0, 0, 1, 2)
grid_layout.addWidget(TitleLabel("Monochromator", self, collapsible=True, default_collapsed=False), 0, 0, 1, 3)
self.mono_pitch_scan_button = QPushButton("Mono Pitch Scan", parent=self)
self.mono_pitch_scan_button.clicked.connect(self.mono_pitch_scan.emit)
grid_layout.addWidget(self.mono_pitch_scan_button, 1, 0, 1, 2)
grid_layout.addWidget(self.mono_pitch_scan_button, 1, 0, 1, 3)
# One row (label | value | button) instead of three — vertical space.
# Display in keV; the DAQ API stays in eV (converted on emit).
grid_layout.addWidget(QLabel("Energy", parent=self), 2, 0)
self.energy_spin = QDoubleSpinBox(parent=self)
self.energy_spin.setDecimals(3)
self.energy_spin.setRange(1000.0, 30000.0)
self.energy_spin.setSingleStep(100.0)
self.energy_spin.setSuffix(" eV")
self.energy_spin.setValue(12000.0)
grid_layout.addWidget(self.energy_spin, 3, 0)
self.energy_spin.setRange(1.0, 30.0)
self.energy_spin.setSingleStep(0.1)
self.energy_spin.setSuffix(" keV")
self.energy_spin.setValue(12.0)
grid_layout.addWidget(self.energy_spin, 2, 1)
self.change_energy_button = QPushButton("Change Energy", parent=self)
self.change_energy_button.clicked.connect(self._emit_change_energy)
grid_layout.addWidget(self.change_energy_button, 3, 1)
grid_layout.addWidget(self.change_energy_button, 2, 2)
@Slot()
def _emit_change_energy(self):
self.change_energy.emit(float(self.energy_spin.value()))
self.change_energy.emit(float(self.energy_spin.value()) * 1000.0)
@Slot(DAQStatusModel)
def update_daq_status(self, _status: DAQStatusModel):
@@ -7,15 +7,13 @@ from PySide6.QtWidgets import (
QLabel,
QMessageBox,
QPushButton,
QSizePolicy,
QSlider,
QSpacerItem,
)
from aare.gui.constants import LOGGER_NAME
from aare.gui.panels.scan_settings_panel import ScanSettingsPanel
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager, RasterGridMetric
from aare.gui.styles import GO_TEXT
from aare.gui.styles import ABORT_TEXT, GO_TEXT
from aare.gui.widgets.number_line_edit import DbOverrideLineEdit
from aare.gui.widgets.raster_grid_table import RasterGridTable
@@ -131,11 +129,6 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
self._table = RasterGridTable(raster_mgr)
self._layout.addWidget(self._table, 9, 0, 1, 5)
horizontal_spacer = QSpacerItem(
40, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)
self._layout.addItem(horizontal_spacer, 10, 0, 1, 5)
self._layout.addWidget(QLabel("Measurement time", parent=self), 11, 0)
self.total_time = QLabel(f"{self._total_time} min 0 s")
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
@@ -152,6 +145,11 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
self.auto_button.setStyleSheet(f"color: {GO_TEXT};")
self.auto_button.clicked.connect(self._on_evaluate_auto_clicked)
self._layout.addWidget(self.auto_button, 13, 0, 1, 5)
# Per-tab Abort (DataCollectionSettings wires it to the DAQ cancel).
self.abort_button = QPushButton("Abort measurement")
self.abort_button.setStyleSheet(f"color: {ABORT_TEXT};")
self._layout.addWidget(self.abort_button, 14, 0, 1, 5)
self._reset_to_defaults()
self.update_grid_scan_size()
@@ -10,7 +10,7 @@ from PySide6.QtWidgets import QComboBox, QLabel, QMessageBox, QPushButton
from aare.gui.constants import LOGGER_NAME
from aare.gui.panels.scan_settings_panel import ScanSettingsPanel
from aare.gui.styles import GO_TEXT
from aare.gui.styles import ABORT_TEXT, GO_TEXT
from aare.gui.widgets.number_line_edit import DbOverrideLineEdit, NumberLineEdit
logger = setup_logger(LOGGER_NAME)
@@ -164,6 +164,11 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
self.measurement_button.setStyleSheet(f"color: {GO_TEXT};")
self.measurement_button.clicked.connect(self.run_measurement)
self._layout.addWidget(self.measurement_button, 16, 0, 1, 6)
# Per-tab Abort (DataCollectionSettings wires it to the DAQ cancel).
self.abort_button = QPushButton("Abort measurement")
self.abort_button.setStyleSheet(f"color: {ABORT_TEXT};")
self._layout.addWidget(self.abort_button, 17, 0, 1, 6)
self._reset_to_defaults()
@Slot()
+31 -41
View File
@@ -4,6 +4,7 @@ from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
QDoubleSpinBox,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
@@ -36,32 +37,28 @@ class SamcamPanel(QWidget):
# Create layout
layout = QVBoxLayout()
layout.addWidget(TitleLabel("Sample camera", self, collapsible=True))
layout.addWidget(TitleLabel("Sample camera", self, collapsible=True, default_collapsed=False))
# Exposure control
exposure_layout = QHBoxLayout()
exposure_label = QLabel("Exposure (s):")
# Exposure + gain share one row to save vertical space.
exposure_gain_layout = QHBoxLayout()
self.exposure_spinbox = QDoubleSpinBox()
self.exposure_spinbox.setRange(0, 1.0) # Adjust range as needed
self.exposure_spinbox.setSingleStep(0.001)
self.exposure_spinbox.setStyleSheet(f"QDoubleSpinBox {{ background-color: {INPUT_BG}; }}")
self.exposure_spinbox.setDecimals(3)
self.exposure_spinbox.valueChanged.connect(self._changed)
exposure_layout.addWidget(exposure_label)
exposure_layout.addWidget(self.exposure_spinbox)
# Gain control
gain_layout = QHBoxLayout()
gain_label = QLabel("Gain:")
self.gain_spinbox = QDoubleSpinBox()
self.gain_spinbox.setRange(0, 1000) # Adjust range as needed
self.gain_spinbox.setSingleStep(1)
self.gain_spinbox.setDecimals(1)
self.gain_spinbox.setStyleSheet(f"QDoubleSpinBox {{ background-color: {INPUT_BG}; }}")
self.gain_spinbox.valueChanged.connect(self._changed)
gain_layout.addWidget(gain_label)
gain_layout.addWidget(self.gain_spinbox)
exposure_gain_layout.addWidget(QLabel("Exposure (s):"))
exposure_gain_layout.addWidget(self.exposure_spinbox)
exposure_gain_layout.addWidget(QLabel("Gain:"))
exposure_gain_layout.addWidget(self.gain_spinbox)
# Persist the current gain/exposure as the beam-location preset for the
# current zoom (only meaningful in beam-location mode).
@@ -86,56 +83,53 @@ class SamcamPanel(QWidget):
screenshot_message_layout.addWidget(screenshot_message_label)
screenshot_message_layout.addWidget(self.screenshot_message_edit)
self.screenshot_button = QPushButton("Take screenshot")
self.screenshot_button = QPushButton("Save samcam image")
self.screenshot_button.clicked.connect(self._request_screenshot)
# Show detections checkbox
detections_layout = QHBoxLayout()
# Overlay checkboxes, two columns to save vertical space; related
# toggles share a row.
self.show_detections_checkbox = QCheckBox("Show ML detections")
self.show_detections_checkbox.setChecked(True) # Default to checked
self.show_detections_checkbox.toggled.connect(self.show_detections_changed.emit)
detections_layout.addWidget(self.show_detections_checkbox)
# Show detection polygons checkbox
detection_polygons_layout = QHBoxLayout()
self.show_detection_polygons_checkbox = QCheckBox("Show ML polygons")
self.show_detection_polygons_checkbox.setChecked(True)
self.show_detection_polygons_checkbox.toggled.connect(
self.show_detection_polygons_changed.emit
)
detection_polygons_layout.addWidget(self.show_detection_polygons_checkbox)
# Show target point checkbox
target_point_layout = QHBoxLayout()
self.show_target_point_checkbox = QCheckBox("Show target point")
self.show_target_point_checkbox.setChecked(True)
self.show_target_point_checkbox.toggled.connect(self.show_target_point_changed.emit)
target_point_layout.addWidget(self.show_target_point_checkbox)
# Show target coordinates checkbox
target_coords_layout = QHBoxLayout()
self.show_target_coordinates_checkbox = QCheckBox("Show target coordinates")
self.show_target_coordinates_checkbox.setChecked(True)
self.show_target_coordinates_checkbox.toggled.connect(
self.show_target_coordinates_changed.emit
)
target_coords_layout.addWidget(self.show_target_coordinates_checkbox)
# Show legend checkbox
legend_layout = QHBoxLayout()
self.show_overlay_legend_checkbox = QCheckBox("Show overlay legend")
self.show_overlay_legend_checkbox.setChecked(True)
self.show_overlay_legend_checkbox.toggled.connect(self.show_overlay_legend_changed.emit)
legend_layout.addWidget(self.show_overlay_legend_checkbox)
# Compact legend checkbox
compact_legend_layout = QHBoxLayout()
self.compact_overlay_legend_checkbox = QCheckBox("Compact legend")
self.compact_overlay_legend_checkbox.setChecked(False)
self.compact_overlay_legend_checkbox.toggled.connect(
self.compact_overlay_legend_changed.emit
)
compact_legend_layout.addWidget(self.compact_overlay_legend_checkbox)
checkbox_grid = QGridLayout()
for i, checkbox in enumerate(
(
self.show_detections_checkbox,
self.show_detection_polygons_checkbox,
self.show_target_point_checkbox,
self.show_target_coordinates_checkbox,
self.show_overlay_legend_checkbox,
self.compact_overlay_legend_checkbox,
)
):
checkbox_grid.addWidget(checkbox, i // 2, i % 2)
# Target color
target_color_layout = QHBoxLayout()
@@ -148,18 +142,14 @@ class SamcamPanel(QWidget):
target_color_layout.addWidget(self.target_color_combo)
# Add controls to main layout
layout.addLayout(exposure_layout)
layout.addLayout(gain_layout)
layout.addWidget(self.save_beam_location_button)
layout.addLayout(exposure_gain_layout)
samcam_buttons_layout = QHBoxLayout()
samcam_buttons_layout.addWidget(self.save_beam_location_button)
samcam_buttons_layout.addWidget(self.screenshot_button)
layout.addLayout(samcam_buttons_layout)
layout.addLayout(screenshot_filename_layout)
layout.addLayout(screenshot_message_layout)
layout.addWidget(self.screenshot_button)
layout.addLayout(detections_layout)
layout.addLayout(detection_polygons_layout)
layout.addLayout(target_point_layout)
layout.addLayout(target_coords_layout)
layout.addLayout(legend_layout)
layout.addLayout(compact_legend_layout)
layout.addLayout(checkbox_grid)
layout.addLayout(target_color_layout)
self.setLayout(layout)
@@ -63,10 +63,15 @@ class ScanSettingsPanel(QWidget):
# before, so they are unaffected by the wrapping.
outer = QVBoxLayout(self)
outer.setContentsMargins(0, 0, 0, 0)
outer.setSpacing(0)
outer.addWidget(self._build_source_toggle())
grid_host = QWidget(self)
self._layout = QGridLayout(grid_host)
# Toggle-to-grid gap = one grid row gap (top). Bottom 3 + the column's
# 3px spacing = one row gap between the last button and Abort too.
m = self._layout.contentsMargins()
self._layout.setContentsMargins(m.left(), 6, m.right(), 3)
outer.addWidget(grid_host)
self._layout.addWidget(QLabel("High resolution", parent=self), 0, 0)
+70 -61
View File
@@ -8,7 +8,7 @@ from PySide6.QtWidgets import QGridLayout, QLabel, QPushButton, QSizePolicy, QSp
from aare.gui.constants import LOGGER_NAME
from aare.gui.panels.rotation_data_collection import add_data_to_path
from aare.gui.styles import GO_TEXT, STATUS_ALERT
from aare.gui.styles import ABORT_TEXT, GO_TEXT, STATUS_ALERT
from aare.gui.widgets.number_line_edit import NumberLineEdit
logger = setup_logger(LOGGER_NAME)
@@ -44,6 +44,10 @@ class SimpleRotationSettingsPanel(QWidget):
self._prev_params = SimpleScanParameters()
self._layout = QGridLayout(self)
# Top margin 0 like the Raster/Rotation pages (their toggle row sits
# at margin 0), so the gap under the ML-centring row matches.
m = self._layout.contentsMargins()
self._layout.setContentsMargins(m.left(), 0, m.right(), 3)
# Visible resolution (entry)
self._layout.addWidget(QLabel("Visible resolution", parent=self), 0, 0)
@@ -83,133 +87,138 @@ class SimpleRotationSettingsPanel(QWidget):
self._layout.addWidget(QLabel("K", parent=self), 4, 4)
self.temp_enter.newValue.connect(self.set_temperature)
# Run + Abort directly under the last configurable row; the read-only
# block below is reference info, not something to scroll past to act.
self.run_rotation_button = QPushButton("Run rotation", parent=self)
self.run_rotation_button.setStyleSheet(f"color: {GO_TEXT};")
self.run_rotation_button.clicked.connect(self.run_measurement)
self._layout.addWidget(self.run_rotation_button, 5, 0, 1, 6)
self.abort_button = QPushButton("Abort measurement", parent=self)
self.abort_button.setStyleSheet(f"color: {ABORT_TEXT};")
self._layout.addWidget(self.abort_button, 6, 0, 1, 6)
# Calculated labels
self._layout.addWidget(QLabel("Target resolution", parent=self), 5, 0)
self._layout.addWidget(QLabel("Target resolution", parent=self), 7, 0)
self.target_res_label = QLabel("--", parent=self)
self.target_res_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.target_res_label, 5, 1, 1, 3)
self._layout.addWidget(QLabel("Å", parent=self), 5, 4)
self._layout.addWidget(self.target_res_label, 7, 1, 1, 3)
self._layout.addWidget(QLabel("Å", parent=self), 7, 4)
self._layout.addWidget(QLabel("Image time", parent=self), 6, 0)
self._layout.addWidget(QLabel("Image time", parent=self), 8, 0)
self.image_time_label = QLabel("--", parent=self)
self.image_time_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.image_time_label, 6, 1, 1, 3)
self._layout.addWidget(QLabel("s", parent=self), 6, 4)
self._layout.addWidget(self.image_time_label, 8, 1, 1, 3)
self._layout.addWidget(QLabel("s", parent=self), 8, 4)
self._layout.addWidget(QLabel("Transmission", parent=self), 7, 0)
self._layout.addWidget(QLabel("Transmission", parent=self), 9, 0)
self.transmission_label = QLabel("--", parent=self)
self.transmission_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.transmission_label, 7, 1, 1, 3)
self._layout.addWidget(QLabel("%", parent=self), 7, 4)
self._layout.addWidget(self.transmission_label, 9, 1, 1, 3)
self._layout.addWidget(QLabel("%", parent=self), 9, 4)
self._layout.addWidget(QLabel("Detector distance", parent=self), 8, 0)
self._layout.addWidget(QLabel("Detector distance", parent=self), 10, 0)
self.dtz_label = QLabel("--", parent=self)
self.dtz_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.dtz_label, 8, 1, 1, 3)
self._layout.addWidget(QLabel("mm", parent=self), 8, 4)
self._layout.addWidget(self.dtz_label, 10, 1, 1, 3)
self._layout.addWidget(QLabel("mm", parent=self), 10, 4)
self._layout.addWidget(QLabel("Target Dose", parent=self), 9, 0)
self._layout.addWidget(QLabel("Target Dose", parent=self), 11, 0)
self.target_dose_label = QLabel("--", parent=self)
self.target_dose_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.target_dose_label, 9, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 9, 4)
self._layout.addWidget(self.target_dose_label, 11, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 11, 4)
self._layout.addWidget(QLabel("Calculated Dose Rate", parent=self), 10, 0)
self._layout.addWidget(QLabel("Calculated Dose Rate", parent=self), 12, 0)
self.calculated_dose_rate_label = QLabel("--", parent=self)
self.calculated_dose_rate_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.calculated_dose_rate_label, 10, 1, 1, 3)
self._layout.addWidget(QLabel("MGy s<sup>-1</sup>", parent=self), 10, 4)
self._layout.addWidget(self.calculated_dose_rate_label, 12, 1, 1, 3)
self._layout.addWidget(QLabel("MGy s<sup>-1</sup>", parent=self), 12, 4)
self._layout.addWidget(QLabel("Wilson B Factor", parent=self), 11, 0)
self._layout.addWidget(QLabel("Wilson B Factor", parent=self), 13, 0)
self.wilson_b_label = QLabel("--", parent=self)
self.wilson_b_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.wilson_b_label, 11, 1, 1, 3)
self._layout.addWidget(QLabel("Å<sup>2</sup>", parent=self), 11, 4)
self._layout.addWidget(self.wilson_b_label, 13, 1, 1, 3)
self._layout.addWidget(QLabel("Å<sup>2</sup>", parent=self), 13, 4)
self._layout.addWidget(QLabel("Crystal Size x", parent=self), 12, 0)
self._layout.addWidget(QLabel("Crystal Size x", parent=self), 14, 0)
self.xtal_x_label = QLabel("--", parent=self)
self.xtal_x_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_x_label, 12, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 12, 4)
self._layout.addWidget(QLabel("Crystal Size y", parent=self), 13, 0)
self.xtal_y_label = QLabel("--", parent=self)
self.xtal_y_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_y_label, 13, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 13, 4)
self._layout.addWidget(QLabel("Crystal Size z", parent=self), 14, 0)
self.xtal_z_label = QLabel("--", parent=self)
self.xtal_z_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_z_label, 14, 1, 1, 3)
self._layout.addWidget(self.xtal_x_label, 14, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 14, 4)
self._layout.addWidget(QLabel("Calculated Dose (xtal size)", parent=self), 15, 0)
self._layout.addWidget(QLabel("Crystal Size y", parent=self), 15, 0)
self.xtal_y_label = QLabel("--", parent=self)
self.xtal_y_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_y_label, 15, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 15, 4)
self._layout.addWidget(QLabel("Crystal Size z", parent=self), 16, 0)
self.xtal_z_label = QLabel("--", parent=self)
self.xtal_z_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_z_label, 16, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 16, 4)
self._layout.addWidget(QLabel("Calculated Dose (xtal size)", parent=self), 17, 0)
self.xtal_size_dose_label = QLabel("--", parent=self)
self.xtal_size_dose_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.xtal_size_dose_label, 15, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 15, 4)
self._layout.addWidget(self.xtal_size_dose_label, 17, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 17, 4)
self._layout.addWidget(QLabel("X-ray Wavelength", parent=self), 16, 0)
self._layout.addWidget(QLabel("X-ray Wavelength", parent=self), 18, 0)
self.wavelength_label = QLabel("--", parent=self)
self.wavelength_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.wavelength_label, 16, 1, 1, 3)
self._layout.addWidget(QLabel("Å", parent=self), 16, 4)
self._layout.addWidget(self.wavelength_label, 18, 1, 1, 3)
self._layout.addWidget(QLabel("Å", parent=self), 18, 4)
self._layout.addWidget(QLabel("Flux", parent=self), 17, 0)
self._layout.addWidget(QLabel("Flux", parent=self), 19, 0)
self.flux_label = QLabel("--", parent=self)
self.flux_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.flux_label, 17, 1, 1, 3)
self._layout.addWidget(QLabel("x 10<sup>9</sup> ph s<sup>-1</sup>", parent=self), 17, 4)
self._layout.addWidget(self.flux_label, 19, 1, 1, 3)
self._layout.addWidget(QLabel("x 10<sup>9</sup> ph s<sup>-1</sup>", parent=self), 19, 4)
self._layout.addWidget(QLabel("Beam Size", parent=self), 18, 0)
self._layout.addWidget(QLabel("Beam Size", parent=self), 20, 0)
self.beam_size_label = QLabel("--", parent=self)
self.beam_size_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.beam_size_label, 18, 1, 1, 3)
self._layout.addWidget(QLabel("um<sup>2</sup>", parent=self), 18, 4)
self._layout.addWidget(self.beam_size_label, 20, 1, 1, 3)
self._layout.addWidget(QLabel("um<sup>2</sup>", parent=self), 20, 4)
self._layout.addWidget(QLabel("Calculated Dose", parent=self), 19, 0)
self._layout.addWidget(QLabel("Calculated Dose", parent=self), 21, 0)
self.calculated_dose_label = QLabel("--", parent=self)
self.calculated_dose_label.setAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
self._layout.addWidget(self.calculated_dose_label, 19, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 19, 4)
self._layout.addWidget(self.calculated_dose_label, 21, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 21, 4)
self._layout.addWidget(QLabel("Total measurement time", parent=self), 20, 0)
self._layout.addWidget(QLabel("Total measurement time", parent=self), 22, 0)
self.total_time = QLabel(f"{self.total_time_s} min 0 s")
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.total_time, 20, 1, 1, 3)
self._layout.addWidget(self.total_time, 22, 1, 1, 3)
# add vertical stretch between detector distance and the run button
# Vertical stretch below everything (surplus space sink).
self._layout.addItem(
QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding), 21, 0, 1, 6
QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding), 23, 0, 1, 6
)
# Run rotation button
self.run_rotation_button = QPushButton("Run rotation", parent=self)
self.run_rotation_button.setStyleSheet(f"color: {GO_TEXT};")
self.run_rotation_button.clicked.connect(self.run_measurement)
self._layout.addWidget(self.run_rotation_button, 22, 0, 1, 6)
@Slot(DAQStatusModel)
def update_daq_status(self, s: DAQStatusModel):
self._d = s
+4 -2
View File
@@ -33,8 +33,10 @@ class RasterGridTable(QTableWidget):
header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch)
# Set minimum height
self.setMinimumHeight(100)
# Exactly 5 rows of space; more grids scroll inside the table.
header_height = self.horizontalHeader().sizeHint().height()
row_height = self.verticalHeader().defaultSectionSize()
self.setFixedHeight(header_height + 3 * row_height + 1 * self.frameWidth())
# Connect to raster manager signals
self._raster_mgr.completed_grid_updated.connect(self.refresh_table)