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:
+85
-106
@@ -5,11 +5,11 @@ from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT
|
||||
from PyQt6.QtCore import QObject, QSettings, Qt, QThread
|
||||
from PyQt6.QtCore import QObject, QSettings, QSignalBlocker, Qt, QThread
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QCheckBox, QComboBox, QDialog, QDoubleSpinBox, QFileDialog,
|
||||
QFrame, QHBoxLayout, QLabel, QMainWindow, QProgressDialog, QPushButton,
|
||||
QApplication, QCheckBox, QComboBox, QDialog, QFileDialog, QFrame,
|
||||
QHBoxLayout, QLabel, QMainWindow, QProgressDialog, QPushButton,
|
||||
QSizePolicy, QSpinBox, QSplitter, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
@@ -30,8 +30,8 @@ from sras_workers import (
|
||||
from .canvases import ImageCanvas, WaveformCanvas
|
||||
from .common import (
|
||||
CH1_DERIVED_MODES, CH_LABELS, CMAPS, VELOCITY_MODE_IDX, _CHANNEL_DISPLAY,
|
||||
_CSS_BUSY, _CSS_HINT, _CSS_INFO, _CSS_MUTED, _CSS_WARN, _LEFT_PANEL_W,
|
||||
_RIGHT_PANEL_W, _SPIN_MIN_W, _form, _group, _scroll_panel, _wrap_label,
|
||||
_CSS_BUSY, _axes_extent, _CSS_HINT, _CSS_INFO, _CSS_MUTED, _CSS_WARN, _LEFT_PANEL_W,
|
||||
_RIGHT_PANEL_W, Jobs, _form, _group, _make_dspin, _scroll_panel, _wrap_label,
|
||||
)
|
||||
from .dialogs import FftOptionsDialog, ManualAlignmentDialog
|
||||
|
||||
@@ -175,17 +175,23 @@ class SrasViewerWindow(QMainWindow):
|
||||
panel_layout = QVBoxLayout(panel)
|
||||
panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
panel_layout.setSpacing(8)
|
||||
panel_layout.addWidget(self._build_file_group())
|
||||
panel_layout.addWidget(self._build_info_group())
|
||||
panel_layout.addWidget(self._build_view_group())
|
||||
panel_layout.addWidget(self._build_roi_group())
|
||||
panel_layout.addStretch()
|
||||
return _scroll_panel(panel, _LEFT_PANEL_W)
|
||||
|
||||
# ---- File -------------------------------------------------------
|
||||
def _build_file_group(self) -> QWidget:
|
||||
grp_file, fl = _group("File")
|
||||
self.btn_open = QPushButton("Open .sras…")
|
||||
self.btn_open.clicked.connect(self._on_open)
|
||||
self.lbl_filename = _wrap_label("No file loaded", _CSS_MUTED)
|
||||
fl.addWidget(self.btn_open)
|
||||
fl.addWidget(self.lbl_filename)
|
||||
panel_layout.addWidget(grp_file)
|
||||
return grp_file
|
||||
|
||||
# ---- Scan info --------------------------------------------------
|
||||
def _build_info_group(self) -> QWidget:
|
||||
grp_info, il = _group("Scan Info")
|
||||
il.setSpacing(3)
|
||||
self._info = {}
|
||||
@@ -202,9 +208,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
# background DC-precompute progress
|
||||
self.lbl_dc_precompute = _wrap_label("", _CSS_BUSY)
|
||||
il.addWidget(self.lbl_dc_precompute)
|
||||
panel_layout.addWidget(grp_info)
|
||||
return grp_info
|
||||
|
||||
# ---- View settings ----------------------------------------------
|
||||
def _build_view_group(self) -> QWidget:
|
||||
grp_view, vl = _group("View Settings")
|
||||
|
||||
view_form = _form()
|
||||
@@ -244,14 +250,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
# DC threshold (for RF / CH1 masking)
|
||||
self.grp_threshold, tl = _group("RF Mask Threshold (CH1 only)")
|
||||
thr_form = _form()
|
||||
self.spin_threshold_mv = QDoubleSpinBox()
|
||||
self.spin_threshold_mv.setRange(-500.0, 500.0)
|
||||
self.spin_threshold_mv.setDecimals(3)
|
||||
self.spin_threshold_mv.setSingleStep(0.025)
|
||||
self.spin_threshold_mv.setSuffix(" mV")
|
||||
self.spin_threshold_mv.setValue(50.0)
|
||||
self.spin_threshold_mv = _make_dspin(-500.0, 500.0, 3, suffix=" mV",
|
||||
value=50.0, step=0.025)
|
||||
self.spin_threshold_mv.setEnabled(False)
|
||||
self.spin_threshold_mv.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_threshold_mv.editingFinished.connect(self._on_threshold_changed)
|
||||
thr_form.addRow("DC threshold:", self.spin_threshold_mv)
|
||||
tl.addLayout(thr_form)
|
||||
@@ -289,10 +290,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
"Save the current CH1 image (one scan row per CSV line).")
|
||||
self.btn_export_csv.clicked.connect(self._on_export_csv)
|
||||
vl.addWidget(self.btn_export_csv)
|
||||
return grp_view
|
||||
|
||||
panel_layout.addWidget(grp_view)
|
||||
|
||||
# ---- ROI ---------------------------------------------------------
|
||||
def _build_roi_group(self) -> QWidget:
|
||||
grp_roi, rl = _group("ROI (Region of Interest)")
|
||||
|
||||
self.btn_draw_roi = QPushButton("Draw ROI")
|
||||
@@ -327,10 +327,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
self.lbl_roi_npix = _wrap_label("pixels inside: —", _CSS_HINT)
|
||||
for lbl in (self.lbl_roi_center, self.lbl_roi_size, self.lbl_roi_npix):
|
||||
rl.addWidget(lbl)
|
||||
|
||||
panel_layout.addWidget(grp_roi)
|
||||
panel_layout.addStretch()
|
||||
return _scroll_panel(panel, _LEFT_PANEL_W)
|
||||
return grp_roi
|
||||
|
||||
def _build_canvases(self) -> QWidget:
|
||||
splitter = QSplitter(Qt.Orientation.Vertical)
|
||||
@@ -372,14 +369,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
# Velocity settings (visible only in velocity mode)
|
||||
self.grp_velocity, vel_l = _group("Velocity Settings (CH1 only)")
|
||||
vel_form = _form()
|
||||
self.spin_grating_um = QDoubleSpinBox()
|
||||
self.spin_grating_um.setRange(0.1, 1000.0)
|
||||
self.spin_grating_um.setDecimals(2)
|
||||
self.spin_grating_um.setSingleStep(0.5)
|
||||
self.spin_grating_um.setSuffix(" µm")
|
||||
self.spin_grating_um.setValue(25)
|
||||
self.spin_grating_um = _make_dspin(0.1, 1000.0, 2, suffix=" µm",
|
||||
value=25, step=0.5)
|
||||
self.spin_grating_um.setEnabled(False)
|
||||
self.spin_grating_um.setMinimumWidth(_SPIN_MIN_W)
|
||||
self.spin_grating_um.editingFinished.connect(self._on_grating_changed)
|
||||
vel_form.addRow("Grating size:", self.spin_grating_um)
|
||||
vel_l.addLayout(vel_form)
|
||||
@@ -407,11 +399,8 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
range_form = _form()
|
||||
for label, attr in (("min:", "spin_vmin"), ("max:", "spin_vmax")):
|
||||
spin = QDoubleSpinBox()
|
||||
spin.setRange(-1e9, 1e9)
|
||||
spin.setDecimals(4)
|
||||
spin = _make_dspin(-1e9, 1e9, 4)
|
||||
spin.setEnabled(False)
|
||||
spin.setMinimumWidth(_SPIN_MIN_W)
|
||||
spin.editingFinished.connect(self._on_manual_range_changed)
|
||||
setattr(self, attr, spin)
|
||||
range_form.addRow(label, spin)
|
||||
@@ -494,7 +483,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
def _load_file(self, path: str):
|
||||
started = self._run_worker(
|
||||
"load", LoadWorker(path),
|
||||
Jobs.LOAD, LoadWorker(path),
|
||||
connect=(
|
||||
("finished", self._on_load_done),
|
||||
("error", lambda msg: self.statusBar().showMessage(f"Error: {msg}")),
|
||||
@@ -530,25 +519,18 @@ class SrasViewerWindow(QMainWindow):
|
||||
self._dc_generation += 1
|
||||
self.lbl_dc_precompute.setText("")
|
||||
|
||||
self._alignment_result = None
|
||||
self._aligned_cache = {}
|
||||
self._alignment_generation += 1
|
||||
self.chk_aligned_view.blockSignals(True)
|
||||
self.chk_aligned_view.setChecked(False)
|
||||
self.chk_aligned_view.setEnabled(False)
|
||||
self.chk_aligned_view.blockSignals(False)
|
||||
self._apply_alignment_result(None, view_checked=False)
|
||||
|
||||
# Silently restore a previously-saved manual alignment, if any, so
|
||||
# the work survives closing and reopening the file.
|
||||
sidecar = load_manual_alignment(sras)
|
||||
if sidecar is not None:
|
||||
try:
|
||||
self._alignment_result = build_manual_alignment(
|
||||
sras, sidecar.ref_angle_idx, sidecar.dc_threshold_mv,
|
||||
sidecar.per_angle)
|
||||
self.chk_aligned_view.blockSignals(True)
|
||||
self.chk_aligned_view.setChecked(True)
|
||||
self.chk_aligned_view.blockSignals(False)
|
||||
self._apply_alignment_result(
|
||||
build_manual_alignment(
|
||||
sras, sidecar.ref_angle_idx, sidecar.dc_threshold_mv,
|
||||
sidecar.per_angle),
|
||||
view_checked=True)
|
||||
self.statusBar().showMessage(
|
||||
f"Restored saved manual alignment from "
|
||||
f"{sidecar_path(sras.path).name}")
|
||||
@@ -564,17 +546,15 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
self.lbl_filename.setText(sras.path.name)
|
||||
|
||||
self.spin_angle.blockSignals(True)
|
||||
self.spin_angle.setRange(0, max(0, sras.n_angles - 1))
|
||||
self.spin_angle.setValue(0)
|
||||
self.spin_angle.blockSignals(False)
|
||||
with QSignalBlocker(self.spin_angle):
|
||||
self.spin_angle.setRange(0, max(0, sras.n_angles - 1))
|
||||
self.spin_angle.setValue(0)
|
||||
|
||||
# DC channels are cheap and give an instant, fluid overview of a
|
||||
# scan; CH1/Velocity require an FFT per pixel that can take minutes
|
||||
# on a large scan, so don't default to it.
|
||||
self.combo_channel.blockSignals(True)
|
||||
self.combo_channel.setCurrentIndex(CH4_IDX)
|
||||
self.combo_channel.blockSignals(False)
|
||||
with QSignalBlocker(self.combo_channel):
|
||||
self.combo_channel.setCurrentIndex(CH4_IDX)
|
||||
|
||||
self._update_controls_enabled(True)
|
||||
self._on_threshold_changed() # refresh ADC label with file calibration
|
||||
@@ -657,14 +637,14 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
# Batch Convert actions pick their own files, independent of
|
||||
# whatever's currently open — only gated on no batch already running.
|
||||
can_batch = not self._job_running("batch")
|
||||
can_batch = not self._job_running(Jobs.BATCH)
|
||||
self._batch_dc_act.setEnabled(can_batch)
|
||||
self._batch_fft_act.setEnabled(can_batch)
|
||||
|
||||
self._alignment_act.setEnabled(
|
||||
has_file and s.n_angles > 1 and not self._job_running("align"))
|
||||
has_file and s.n_angles > 1 and not self._job_running(Jobs.ALIGN))
|
||||
self._manual_align_act.setEnabled(
|
||||
has_file and s.n_angles > 1 and not self._job_running("align"))
|
||||
has_file and s.n_angles > 1 and not self._job_running(Jobs.ALIGN))
|
||||
self.chk_aligned_view.setEnabled(enabled and self._alignment_result is not None)
|
||||
self._update_roi_ui()
|
||||
|
||||
@@ -676,7 +656,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# Background subtraction changes the FFT input, so it genuinely
|
||||
# invalidates the cached raw FFT (the cache key includes it) —
|
||||
# _refresh_display() recomputes only on a miss for the new state.
|
||||
if self._sras is not None and self.combo_channel.currentIndex() in CH1_DERIVED_MODES:
|
||||
if self._is_fft_mode():
|
||||
self._refresh_display()
|
||||
|
||||
def _on_grating_changed(self):
|
||||
@@ -693,7 +673,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# Threshold decides which pixels get an FFT at all, so changing it is
|
||||
# a genuine cache-key change — but the recompute reuses the cached DC4
|
||||
# image to skip masked-out pixels.
|
||||
if self._sras is not None and self.combo_channel.currentIndex() in CH1_DERIVED_MODES:
|
||||
if self._is_fft_mode():
|
||||
self._refresh_display()
|
||||
|
||||
def _on_autoscale_toggled(self, checked: bool):
|
||||
@@ -810,9 +790,8 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
def _on_draw_mode_changed(self, active: bool):
|
||||
# Keep the toggle button's visual state in sync with the canvas.
|
||||
self.btn_draw_roi.blockSignals(True)
|
||||
self.btn_draw_roi.setChecked(active)
|
||||
self.btn_draw_roi.blockSignals(False)
|
||||
with QSignalBlocker(self.btn_draw_roi):
|
||||
self.btn_draw_roi.setChecked(active)
|
||||
|
||||
def _on_clear_roi(self):
|
||||
self.image_canvas.clear_roi()
|
||||
@@ -859,6 +838,11 @@ class SrasViewerWindow(QMainWindow):
|
||||
return None
|
||||
return self._sras.samples_per_frame * self._fft_pad_factor
|
||||
|
||||
def _is_fft_mode(self) -> bool:
|
||||
"""Is the selected channel an FFT-derived (CH1/Velocity) mode?"""
|
||||
return (self._sras is not None
|
||||
and self.combo_channel.currentIndex() in CH1_DERIVED_MODES)
|
||||
|
||||
def _scale_for_display(self, freq_mhz: np.ndarray, ch_idx: int) -> np.ndarray:
|
||||
"""Velocity is a pure post-multiply of the (already DC-masked)
|
||||
cached frequency image — never worth a recompute on its own."""
|
||||
@@ -947,15 +931,13 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
dx = x_axis[1] - x_axis[0] if len(x_axis) > 1 else s.pixel_x_mm
|
||||
dy = float(y_axis[1] - y_axis[0]) if len(y_axis) > 1 else 1.0
|
||||
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)
|
||||
|
||||
if self.chk_auto.isChecked():
|
||||
vmin, vmax = float(display_img.min()), float(display_img.max())
|
||||
for spin, val in ((self.spin_vmin, vmin), (self.spin_vmax, vmax)):
|
||||
spin.blockSignals(True)
|
||||
spin.setValue(val)
|
||||
spin.blockSignals(False)
|
||||
with QSignalBlocker(spin):
|
||||
spin.setValue(val)
|
||||
else:
|
||||
vmin, vmax = self.spin_vmin.value(), self.spin_vmax.value()
|
||||
|
||||
@@ -988,7 +970,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _start_compute(self):
|
||||
if self._sras is None or self._job_running("compute"):
|
||||
if self._sras is None or self._job_running(Jobs.COMPUTE):
|
||||
return # re-checked when the running compute finishes
|
||||
|
||||
angle_idx = self.spin_angle.value()
|
||||
@@ -1013,7 +995,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
is_fft_mode=is_fft,
|
||||
)
|
||||
if not self._run_worker(
|
||||
"compute", worker,
|
||||
Jobs.COMPUTE, worker,
|
||||
connect=(
|
||||
("finished", self._on_compute_done),
|
||||
("error", lambda msg: self.statusBar().showMessage(
|
||||
@@ -1073,7 +1055,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
|
||||
worker = DcPrecomputeWorker(self._sras)
|
||||
self._run_worker(
|
||||
"dc_precompute", worker,
|
||||
Jobs.DC_PRECOMPUTE, worker,
|
||||
connect=(
|
||||
("angle_done", lambda a, dc3, dc4, g=generation:
|
||||
self._on_dc_precompute_angle_done(g, a, dc3, dc4, n_angles)),
|
||||
@@ -1101,7 +1083,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# caught up and are still waiting), show it now.
|
||||
current_ch = self.combo_channel.currentIndex()
|
||||
if (angle_idx == self.spin_angle.value()
|
||||
and not self._job_running("compute")
|
||||
and not self._job_running(Jobs.COMPUTE)
|
||||
and current_ch in (CH3_IDX, CH4_IDX)
|
||||
and (self._current_angle != angle_idx or self._current_ch != current_ch)):
|
||||
self._refresh_display()
|
||||
@@ -1171,7 +1153,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _on_batch_compute(self, mode: str):
|
||||
if self._job_running("batch"):
|
||||
if self._job_running(Jobs.BATCH):
|
||||
return
|
||||
label = "DC" if mode == "dc" else "FFT"
|
||||
paths, _ = QFileDialog.getOpenFileNames(
|
||||
@@ -1183,9 +1165,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
self._batch_errors = []
|
||||
worker = BatchCacheWorker(paths, mode, self.chk_bg_sub.isChecked())
|
||||
started = self._run_worker(
|
||||
"batch", worker,
|
||||
Jobs.BATCH, worker,
|
||||
connect=(
|
||||
("progress", lambda pct: self._set_progress("batch", pct)),
|
||||
("progress", lambda pct: self._set_progress(Jobs.BATCH, pct)),
|
||||
("file_done", self._on_batch_file_done),
|
||||
("finished", lambda p=paths: self._on_batch_finished(p)),
|
||||
),
|
||||
@@ -1197,16 +1179,16 @@ class SrasViewerWindow(QMainWindow):
|
||||
self._batch_dc_act.setEnabled(False)
|
||||
self._batch_fft_act.setEnabled(False)
|
||||
self._show_progress(
|
||||
"batch", f"Batch computing {label} for {len(paths)} file(s)…",
|
||||
Jobs.BATCH, f"Batch computing {label} for {len(paths)} file(s)…",
|
||||
maximum=100)
|
||||
|
||||
def _on_batch_file_done(self, path: str, err: str):
|
||||
if err:
|
||||
self._batch_errors.append(f"{Path(path).name} — {err}")
|
||||
self._show_progress("batch", f"Processed {Path(path).name}…")
|
||||
self._show_progress(Jobs.BATCH, f"Processed {Path(path).name}…")
|
||||
|
||||
def _on_batch_finished(self, paths: list[str]):
|
||||
self._close_progress("batch")
|
||||
self._close_progress(Jobs.BATCH)
|
||||
|
||||
n_total = len(paths)
|
||||
n_failed = len(self._batch_errors)
|
||||
@@ -1240,7 +1222,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
generation = self._alignment_generation
|
||||
|
||||
started = self._run_worker(
|
||||
"align", AngleAlignmentWorker(self._sras, ref_idx, threshold_mv),
|
||||
Jobs.ALIGN, AngleAlignmentWorker(self._sras, ref_idx, threshold_mv),
|
||||
connect=(
|
||||
("progress", lambda pct: self._set_progress("main", pct)),
|
||||
("finished", lambda result, err, g=generation:
|
||||
@@ -1265,12 +1247,9 @@ class SrasViewerWindow(QMainWindow):
|
||||
if error_msg:
|
||||
self.statusBar().showMessage(f"Angle alignment failed: {error_msg}")
|
||||
return
|
||||
self._alignment_result = result
|
||||
self._aligned_cache = {}
|
||||
self.chk_aligned_view.setEnabled(True)
|
||||
self.chk_aligned_view.blockSignals(True)
|
||||
self.chk_aligned_view.setChecked(True)
|
||||
self.chk_aligned_view.blockSignals(False)
|
||||
# No generation bump: this result *is* the current generation's.
|
||||
self._apply_alignment_result(result, view_checked=True,
|
||||
bump_generation=False)
|
||||
nr, nc = result.canvas_shape
|
||||
self.statusBar().showMessage(
|
||||
f"Angle alignment computed ({self._sras.n_angles} angles, "
|
||||
@@ -1320,32 +1299,32 @@ class SrasViewerWindow(QMainWindow):
|
||||
def _on_manual_align_dialog_closed(self, _result_code: int):
|
||||
self._manual_align_dialog = None
|
||||
|
||||
def _on_manual_alignment_saved(self, result, sidecar_path_str: str):
|
||||
def _apply_alignment_result(self, result, *, view_checked: bool,
|
||||
bump_generation: bool = True):
|
||||
"""Install (or clear, with result=None) the active alignment: reset
|
||||
the aligned-image cache and set the Aligned View checkbox without
|
||||
firing its change signal."""
|
||||
self._alignment_result = result
|
||||
self._aligned_cache = {}
|
||||
self._alignment_generation += 1
|
||||
self.chk_aligned_view.setEnabled(True)
|
||||
self.chk_aligned_view.blockSignals(True)
|
||||
self.chk_aligned_view.setChecked(True)
|
||||
self.chk_aligned_view.blockSignals(False)
|
||||
if bump_generation:
|
||||
self._alignment_generation += 1
|
||||
with QSignalBlocker(self.chk_aligned_view):
|
||||
self.chk_aligned_view.setChecked(view_checked)
|
||||
self.chk_aligned_view.setEnabled(result is not None)
|
||||
|
||||
def _manual_alignment_changed(self, result, message: str):
|
||||
self._apply_alignment_result(result, view_checked=result is not None)
|
||||
self._update_controls_enabled(self._sras is not None)
|
||||
self.statusBar().showMessage(
|
||||
f"Manual alignment saved to {Path(sidecar_path_str).name}")
|
||||
self.statusBar().showMessage(message)
|
||||
if self._current_image is not None:
|
||||
self._refresh_display()
|
||||
|
||||
def _on_manual_alignment_saved(self, result, sidecar_path_str: str):
|
||||
self._manual_alignment_changed(
|
||||
result, f"Manual alignment saved to {Path(sidecar_path_str).name}")
|
||||
|
||||
def _on_manual_alignment_cleared(self):
|
||||
self._alignment_result = None
|
||||
self._aligned_cache = {}
|
||||
self._alignment_generation += 1
|
||||
self.chk_aligned_view.blockSignals(True)
|
||||
self.chk_aligned_view.setChecked(False)
|
||||
self.chk_aligned_view.setEnabled(False)
|
||||
self.chk_aligned_view.blockSignals(False)
|
||||
self._update_controls_enabled(self._sras is not None)
|
||||
self.statusBar().showMessage("Manual alignment cleared.")
|
||||
if self._current_image is not None:
|
||||
self._refresh_display()
|
||||
self._manual_alignment_changed(None, "Manual alignment cleared.")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# FFT Options
|
||||
@@ -1369,7 +1348,7 @@ class SrasViewerWindow(QMainWindow):
|
||||
# Pad factor changes the FFT bin count, so it genuinely invalidates
|
||||
# the cached raw FFT (part of the cache key) — _refresh_display()
|
||||
# recomputes only on a cache miss.
|
||||
if self._sras is not None and self.combo_channel.currentIndex() in CH1_DERIVED_MODES:
|
||||
if self._is_fft_mode():
|
||||
self._refresh_display()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user