Dedup viewer Qt boilerplate after the split
- One _apply_alignment_result() replaces the five copies of the cache-clear / generation-bump / Aligned-View-checkbox dance, and _on_manual_alignment_saved/_cleared collapse into a shared _manual_alignment_changed. - QSignalBlocker context managers replace every hand-rolled blockSignals(True)/set/blockSignals(False) triple (11 sites). - _make_dspin() replaces the 11 four-to-seven-line QDoubleSpinBox constructions; _axes_extent() replaces the duplicated imshow-extent formula; _is_fft_mode() replaces the repeated CH1-mode guard. - Jobs constants replace the stringly-typed background-job keys. - The two 160-line widget builders split along their section banners: _build_left_panel -> file/info/view/roi groups, the manual-alignment _build_ui -> six per-group builders + _connect_controls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+49
-78
@@ -5,11 +5,11 @@ from typing import TYPE_CHECKING
|
||||
import matplotlib as mpl
|
||||
import numpy as np
|
||||
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtCore import QSignalBlocker, pyqtSignal
|
||||
from PyQt6.QtWidgets import (
|
||||
QButtonGroup, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox,
|
||||
QGroupBox, QHBoxLayout, QLabel, QMessageBox, QPushButton, QRadioButton,
|
||||
QSpinBox, QVBoxLayout, QWidget,
|
||||
QButtonGroup, QComboBox, QDialog, QDialogButtonBox, QGroupBox,
|
||||
QHBoxLayout, QLabel, QMessageBox, QPushButton, QRadioButton, QSpinBox,
|
||||
QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
import sras_compute as compute
|
||||
@@ -22,7 +22,7 @@ from sras_workers import Ch4MaskWorker, CrossCorrelateWorker
|
||||
|
||||
from .canvases import ManualAlignOverlayCanvas
|
||||
from .common import (
|
||||
_CSS_HINT, _CSS_MUTED, _CSS_WARN, _SPIN_MIN_W, _form, _group,
|
||||
_CSS_HINT, _CSS_MUTED, _CSS_WARN, Jobs, _axes_extent, _form, _group, _make_dspin,
|
||||
_scroll_panel, _wrap_label,
|
||||
)
|
||||
|
||||
@@ -268,8 +268,20 @@ class ManualAlignmentDialog(QDialog):
|
||||
panel_l = QVBoxLayout(panel)
|
||||
panel_l.setContentsMargins(0, 0, 0, 0)
|
||||
panel_l.setSpacing(8)
|
||||
panel_l.addWidget(self._build_angle_group())
|
||||
panel_l.addWidget(self._build_adjust_group())
|
||||
panel_l.addWidget(self._build_step_group())
|
||||
panel_l.addWidget(self._build_threshold_group(dc_threshold_mv))
|
||||
panel_l.addWidget(self._build_correlate_group())
|
||||
panel_l.addWidget(self._build_actions_group())
|
||||
self.lbl_status = _wrap_label("", _CSS_MUTED)
|
||||
panel_l.addWidget(self.lbl_status)
|
||||
panel_l.addStretch()
|
||||
|
||||
# ---- Active Angle -------------------------------------------------
|
||||
root.addWidget(_scroll_panel(panel, 320))
|
||||
self._connect_controls()
|
||||
|
||||
def _build_angle_group(self) -> QWidget:
|
||||
grp_angle, al = _group("Active Angle")
|
||||
self.combo_active_angle = QComboBox()
|
||||
for a in range(self._sras.n_angles):
|
||||
@@ -280,80 +292,52 @@ class ManualAlignmentDialog(QDialog):
|
||||
al.addWidget(self.combo_active_angle)
|
||||
self.lbl_active_note = _wrap_label("", _CSS_WARN)
|
||||
al.addWidget(self.lbl_active_note)
|
||||
panel_l.addWidget(grp_angle)
|
||||
return grp_angle
|
||||
|
||||
# ---- Manual Adjustment ---------------------------------------------
|
||||
def _build_adjust_group(self) -> QWidget:
|
||||
self.grp_manual_adjust, mform_box = _group("Manual Adjustment")
|
||||
mform = _form()
|
||||
self.spin_active_rotation_deg = QDoubleSpinBox()
|
||||
self.spin_active_rotation_deg.setRange(-3600.0, 3600.0)
|
||||
self.spin_active_rotation_deg.setDecimals(3)
|
||||
self.spin_active_rotation_deg.setSuffix(" °")
|
||||
self.spin_active_rotation_deg.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_active_rotation_deg = _make_dspin(-3600.0, 3600.0, 3, suffix=" °")
|
||||
mform.addRow("Rotation:", self.spin_active_rotation_deg)
|
||||
|
||||
self.spin_active_shift_x_mm = QDoubleSpinBox()
|
||||
self.spin_active_shift_x_mm.setRange(-1e5, 1e5)
|
||||
self.spin_active_shift_x_mm.setDecimals(4)
|
||||
self.spin_active_shift_x_mm.setSuffix(" mm")
|
||||
self.spin_active_shift_x_mm.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_active_shift_x_mm = _make_dspin(-1e5, 1e5, 4, suffix=" mm")
|
||||
mform.addRow("Shift X:", self.spin_active_shift_x_mm)
|
||||
|
||||
self.spin_active_shift_y_mm = QDoubleSpinBox()
|
||||
self.spin_active_shift_y_mm.setRange(-1e5, 1e5)
|
||||
self.spin_active_shift_y_mm.setDecimals(4)
|
||||
self.spin_active_shift_y_mm.setSuffix(" mm")
|
||||
self.spin_active_shift_y_mm.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_active_shift_y_mm = _make_dspin(-1e5, 1e5, 4, suffix=" mm")
|
||||
mform.addRow("Shift Y:", self.spin_active_shift_y_mm)
|
||||
mform_box.addLayout(mform)
|
||||
panel_l.addWidget(self.grp_manual_adjust)
|
||||
return self.grp_manual_adjust
|
||||
|
||||
# ---- Nudge Step Sizes ------------------------------------------------
|
||||
def _build_step_group(self) -> QWidget:
|
||||
self.grp_step_sizes, sl = _group("Nudge Step Sizes")
|
||||
sform = _form()
|
||||
self.spin_step_translate_mm = QDoubleSpinBox()
|
||||
self.spin_step_translate_mm.setRange(0.0001, 1000.0)
|
||||
self.spin_step_translate_mm.setDecimals(4)
|
||||
self.spin_step_translate_mm.setSuffix(" mm")
|
||||
self.spin_step_translate_mm.setValue(0.01)
|
||||
self.spin_step_translate_mm.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_step_translate_mm = _make_dspin(0.0001, 1000.0, 4,
|
||||
suffix=" mm", value=0.01)
|
||||
sform.addRow("Translate step:", self.spin_step_translate_mm)
|
||||
|
||||
self.spin_step_rotate_deg = QDoubleSpinBox()
|
||||
self.spin_step_rotate_deg.setRange(0.001, 90.0)
|
||||
self.spin_step_rotate_deg.setDecimals(3)
|
||||
self.spin_step_rotate_deg.setSuffix(" °")
|
||||
self.spin_step_rotate_deg.setValue(0.1)
|
||||
self.spin_step_rotate_deg.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_step_rotate_deg = _make_dspin(0.001, 90.0, 3,
|
||||
suffix=" °", value=0.1)
|
||||
sform.addRow("Rotate step:", self.spin_step_rotate_deg)
|
||||
|
||||
self.spin_step_multiplier = QDoubleSpinBox()
|
||||
self.spin_step_multiplier.setRange(1.0, 1000.0)
|
||||
self.spin_step_multiplier.setDecimals(1)
|
||||
self.spin_step_multiplier.setValue(10.0)
|
||||
self.spin_step_multiplier.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_step_multiplier = _make_dspin(1.0, 1000.0, 1, value=10.0)
|
||||
sform.addRow("Coarse × (Shift):", self.spin_step_multiplier)
|
||||
sl.addLayout(sform)
|
||||
sl.addWidget(_wrap_label(
|
||||
"Arrow keys nudge X/Y translation; Q/E nudge rotation (CCW/CW). "
|
||||
"Hold Shift for the coarse step. Click the image once so it has "
|
||||
"keyboard focus.", _CSS_HINT))
|
||||
panel_l.addWidget(self.grp_step_sizes)
|
||||
return self.grp_step_sizes
|
||||
|
||||
# ---- Mask Threshold ---------------------------------------------------
|
||||
def _build_threshold_group(self, dc_threshold_mv: float) -> QWidget:
|
||||
self.grp_mask_threshold, tl = _group("Mask Threshold")
|
||||
tform = _form()
|
||||
self.spin_mask_threshold_mv = QDoubleSpinBox()
|
||||
self.spin_mask_threshold_mv.setRange(-500.0, 500.0)
|
||||
self.spin_mask_threshold_mv.setDecimals(3)
|
||||
self.spin_mask_threshold_mv.setSuffix(" mV")
|
||||
self.spin_mask_threshold_mv.setValue(dc_threshold_mv)
|
||||
self.spin_mask_threshold_mv.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_mask_threshold_mv = _make_dspin(-500.0, 500.0, 3,
|
||||
suffix=" mV", value=dc_threshold_mv)
|
||||
tform.addRow("DC threshold:", self.spin_mask_threshold_mv)
|
||||
tl.addLayout(tform)
|
||||
panel_l.addWidget(self.grp_mask_threshold)
|
||||
return self.grp_mask_threshold
|
||||
|
||||
# ---- Cross-Correlate (FFT) -----------------------------------------
|
||||
def _build_correlate_group(self) -> QWidget:
|
||||
self.grp_correlate, cl = _group("Cross-Correlate (FFT)")
|
||||
cform = _form()
|
||||
self.combo_correlate_source = QComboBox()
|
||||
@@ -361,13 +345,8 @@ class ManualAlignmentDialog(QDialog):
|
||||
self.combo_correlate_source.addItem(label, sources)
|
||||
cform.addRow("Correlate on:", self.combo_correlate_source)
|
||||
|
||||
self.spin_correlate_search_deg = QDoubleSpinBox()
|
||||
self.spin_correlate_search_deg.setRange(0.0, 180.0)
|
||||
self.spin_correlate_search_deg.setSingleStep(1.0)
|
||||
self.spin_correlate_search_deg.setDecimals(1)
|
||||
self.spin_correlate_search_deg.setSuffix(" °")
|
||||
self.spin_correlate_search_deg.setValue(6.0)
|
||||
self.spin_correlate_search_deg.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_correlate_search_deg = _make_dspin(0.0, 180.0, 1, suffix=" °",
|
||||
value=6.0, step=1.0)
|
||||
cform.addRow("Rotation search (±):", self.spin_correlate_search_deg)
|
||||
cl.addLayout(cform)
|
||||
self.btn_auto_correlate = QPushButton("Auto Cross-Correlate (vs Reference)")
|
||||
@@ -378,9 +357,9 @@ class ManualAlignmentDialog(QDialog):
|
||||
"reported angle is only the starting point of the search, and both "
|
||||
"of its signs are tried. Run this first, then nudge only for small "
|
||||
"corrections.", _CSS_HINT))
|
||||
panel_l.addWidget(self.grp_correlate)
|
||||
return self.grp_correlate
|
||||
|
||||
# ---- Actions ------------------------------------------------------
|
||||
def _build_actions_group(self) -> QWidget:
|
||||
grp_actions, acl = _group("Actions")
|
||||
self.btn_auto_derotate = QPushButton("Auto De-rotate (use known angles)")
|
||||
self.btn_save = QPushButton("Save Alignment")
|
||||
@@ -388,14 +367,9 @@ class ManualAlignmentDialog(QDialog):
|
||||
self.btn_close = QPushButton("Close")
|
||||
for btn in (self.btn_auto_derotate, self.btn_save, self.btn_clear, self.btn_close):
|
||||
acl.addWidget(btn)
|
||||
panel_l.addWidget(grp_actions)
|
||||
|
||||
self.lbl_status = _wrap_label("", _CSS_MUTED)
|
||||
panel_l.addWidget(self.lbl_status)
|
||||
panel_l.addStretch()
|
||||
|
||||
root.addWidget(_scroll_panel(panel, 320))
|
||||
return grp_actions
|
||||
|
||||
def _connect_controls(self):
|
||||
self.combo_active_angle.currentIndexChanged.connect(self._on_active_angle_changed)
|
||||
self.spin_active_rotation_deg.editingFinished.connect(self._on_rotation_spin_edited)
|
||||
self.spin_active_shift_x_mm.editingFinished.connect(self._on_shift_spin_edited)
|
||||
@@ -409,9 +383,8 @@ class ManualAlignmentDialog(QDialog):
|
||||
self.canvas.nudge_translate.connect(self._on_nudge_translate)
|
||||
self.canvas.nudge_rotate.connect(self._on_nudge_rotate)
|
||||
|
||||
self.combo_active_angle.blockSignals(True)
|
||||
self.combo_active_angle.setCurrentIndex(self._active_angle)
|
||||
self.combo_active_angle.blockSignals(False)
|
||||
with QSignalBlocker(self.combo_active_angle):
|
||||
self.combo_active_angle.setCurrentIndex(self._active_angle)
|
||||
self._on_active_angle_changed(self._active_angle)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -426,7 +399,7 @@ class ManualAlignmentDialog(QDialog):
|
||||
return
|
||||
self.lbl_status.setText(f"Preparing masks: 0/{len(missing)} angle(s) needed…")
|
||||
started = self._parent._run_worker(
|
||||
"manual_align_masks", Ch4MaskWorker(self._sras, missing),
|
||||
Jobs.MANUAL_ALIGN_MASKS, Ch4MaskWorker(self._sras, missing),
|
||||
connect=(
|
||||
("angle_done", self._on_mask_angle_done),
|
||||
("error", lambda msg: self.lbl_status.setText(f"Mask prep error: {msg}")),
|
||||
@@ -541,8 +514,7 @@ class ManualAlignmentDialog(QDialog):
|
||||
dx, dy = self._preview_pitch_mm
|
||||
x_axis = x0 + np.arange(n_cols) * dx
|
||||
y_axis = y0 + np.arange(n_rows) * dy
|
||||
extent = [x_axis[0] - dx / 2, x_axis[-1] + dx / 2,
|
||||
y_axis[-1] + dy / 2, y_axis[0] - dy / 2]
|
||||
extent = _axes_extent(x_axis, y_axis, dx, dy)
|
||||
title = (f"Angle {self._active_angle} active "
|
||||
f"({self._sras.angles_deg[self._active_angle]:.1f}°)")
|
||||
self.canvas.show_overlay(rgba, extent, title)
|
||||
@@ -565,9 +537,8 @@ class ManualAlignmentDialog(QDialog):
|
||||
for spin, val in ((self.spin_active_rotation_deg, p.rotation_deg),
|
||||
(self.spin_active_shift_x_mm, p.shift_mm[0]),
|
||||
(self.spin_active_shift_y_mm, p.shift_mm[1])):
|
||||
spin.blockSignals(True)
|
||||
spin.setValue(val)
|
||||
spin.blockSignals(False)
|
||||
with QSignalBlocker(spin):
|
||||
spin.setValue(val)
|
||||
|
||||
def _on_nudge_translate(self, dir_x: int, dir_y: int, coarse: bool):
|
||||
if not self._masks_ready or self._active_angle == self._ref_angle_idx:
|
||||
@@ -655,7 +626,7 @@ class ManualAlignmentDialog(QDialog):
|
||||
self._set_controls_enabled(False)
|
||||
self.lbl_status.setText(f"Cross-correlating: 0/{self._correlate_total} angle(s)…")
|
||||
started = self._parent._run_worker(
|
||||
"manual_align_correlate", worker,
|
||||
Jobs.MANUAL_ALIGN_CORRELATE, worker,
|
||||
connect=(
|
||||
("angle_done", self._on_correlate_angle_done),
|
||||
("error", self._on_correlate_error),
|
||||
|
||||
Reference in New Issue
Block a user