diff --git a/sras_viewer.py b/sras_viewer.py index ea9a663..df1bb0b 100644 --- a/sras_viewer.py +++ b/sras_viewer.py @@ -2540,16 +2540,19 @@ class SrasViewerWindow(QMainWindow): ref_idx = 0 threshold_mv = self.spin_threshold_mv.value() seed: dict[int, ManualAngleParams] = {} - if (self._alignment_result is not None - and self._alignment_result.ref_angle_idx == ref_idx): - seed = {a: ManualAngleParams(t.rotation_deg, t.shift_mm) - for a, t in self._alignment_result.per_angle.items()} - threshold_mv = self._alignment_result.dc_threshold_mv - else: - sidecar = load_manual_alignment(self._sras) - if sidecar is not None and sidecar.ref_angle_idx == ref_idx: - seed = dict(sidecar.per_angle) - threshold_mv = sidecar.dc_threshold_mv + # Seed only from a previously *saved manual* alignment (this dialog's + # own Save also writes this sidecar) -- never from self._alignment_result + # when it holds the automatic Fusion -> Angle Alignment's output. That + # path's translation comes from FFT phase correlation, which is the + # very thing manual mode exists to work around; inheriting it here + # would silently reintroduce the same bad translations under a + # "manual" label, on top of the (correct) analytic rotation, which is + # exactly what makes manual mode look like it "still does the same + # thing" the automatic one does. + sidecar = load_manual_alignment(self._sras) + if sidecar is not None and sidecar.ref_angle_idx == ref_idx: + seed = dict(sidecar.per_angle) + threshold_mv = sidecar.dc_threshold_mv cached_dc4 = {a: img for (a, ch), img in self._dc_cache.items() if ch == CH4_IDX} dlg = ManualAlignmentDialog( diff --git a/tools/test_gui.py b/tools/test_gui.py index 83a0366..4fd705f 100644 --- a/tools/test_gui.py +++ b/tools/test_gui.py @@ -278,20 +278,21 @@ def main(): -(float(s.angles_deg[a]) - float(s.angles_deg[0]))) for a in range(s.n_angles))) - # --- Open, seeding from the still-live automatic AlignmentResult -------- + # --- Open: must NOT seed from the still-live automatic AlignmentResult -- + # The automatic result's translation comes from FFT phase correlation -- + # the very thing manual mode exists to work around -- so manual mode + # must start from identity (centroids coincide, zero shift) regardless + # of whatever the automatic run last computed. Only a previously *saved + # manual* alignment (sidecar) should ever seed this dialog. win._on_manual_alignment() check("dialog opened", win._manual_align_dialog is not None) dlg = win._manual_align_dialog check("mask prep needed no background worker (already DC-cached)", not win._job_running("manual_align_masks")) - r = win._alignment_result # still the automatic result from the block above - if r is not None: - check("seeded rotation matches the automatic result", - all(dlg._angle_params[a].rotation_deg == r.per_angle[a].rotation_deg - for a in range(s.n_angles))) - check("seeded shift matches the automatic result", - all(dlg._angle_params[a].shift_mm == r.per_angle[a].shift_mm - for a in range(s.n_angles))) + check("no manual sidecar yet -> dialog starts at identity, not the " + "automatic result", + all(dlg._angle_params[a] == compute.ManualAngleParams() + for a in range(s.n_angles))) # --- Reference angle is locked ------------------------------------------- dlg.combo_active_angle.setCurrentIndex(dlg._ref_angle_idx)