diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py index 576bccf..020b33b 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py @@ -435,7 +435,7 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools # X-ray eye alignment entry points # ------------------------------------------------------------------ - def xrayeye_alignment_start(self, keep_shutter_open: bool = False): + def xrayeye_alignment_start(self, keep_shutter_open: bool = False, force: bool = False): """Run the BEC GUI-based X-ray eye alignment procedure. Creates a fresh :class:`XrayEyeAlignGUI` instance, which resets the @@ -445,7 +445,19 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools Args: keep_shutter_open: If True the shutter is left open between angle steps so the sample remains visible in live view. + force: skip the rotation-center-calibration check below without + prompting. """ + if self.client.get_global_var("lamni_center_found_at") is None: + if not self._confirm_sequence_override( + "No rotation-center calibration has been recorded yet " + "(xrayeye_rotation_center_calibration_isolated/extended/" + "smear_experimental()). X-ray-eye alignment is normally done " + "after finding the rotation centre.", + force, + ): + print("Aborting X-ray eye alignment.") + return aligner = XrayEyeAlignGUI(self.client, self) try: aligner.align(keep_shutter_open=keep_shutter_open) @@ -1223,7 +1235,7 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools f.write(" ".join(f"{x:.2f}" for x in x_vals) + "\n") f.write(" ".join(map(str, x_vals)) + "\n") - def tomo_alignment_scan(self) -> None: + def tomo_alignment_scan(self, force: bool = False) -> None: """Perform a laminogram alignment scan: a quick ptychography scan at 12 angles evenly spaced across the full 360 degrees, using whatever tomo_parameters() are currently set (FOV/step/counting time -- @@ -1234,10 +1246,19 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools ~/data/raw/logs/ptychotomoalign_scannum.txt for BEC_ptycho_align, prints them, and creates a scilog entry summarising the alignment scan numbers. + + Args: + force: skip the X-ray-eye-alignment check below without prompting. """ if self.client.get_global_var("tomo_fit_xray_eye") is None: - print("It appears that the xrayeye alignment was not performed or loaded. Aborting.") - return + if not self._confirm_sequence_override( + "No X-ray-eye alignment fit is loaded (or it was invalidated " + "by a rotation-center calibration run since). The alignment " + "scan's per-angle offsets would then all be zero.", + force, + ): + print("Aborting alignment scan.") + return bec = builtins.__dict__.get("bec") dev = builtins.__dict__.get("dev") @@ -1609,13 +1630,16 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools ) return angle, subtomo_number - def tomo_scan(self, subtomo_start=1, start_angle=None, projection_number=None): + def tomo_scan( + self, subtomo_start=1, start_angle=None, projection_number=None, force: bool = False + ): """Start a tomo scan. Args: subtomo_start (int): For tomo_type 1, the sub-tomogram to start from. Defaults to 1. start_angle (float, optional): Override starting angle of the first sub-tomogram. projection_number (int, optional): For tomo_types 2 and 3, resume from this index. + force: skip the fine-alignment check below without prompting. """ self.lamnigui_show_progress() @@ -1632,6 +1656,17 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools or (self.tomo_type == 2 and projection_number is None) or (self.tomo_type == 3 and projection_number is None) ): + if not self.corr_pos_x: + if not self._confirm_sequence_override( + "No fine (ptycho) alignment correction is loaded -- the " + "sample centre will drift across projection angles " + "uncorrected. Fine for a large FOV that doesn't need it; " + "otherwise run tomo_alignment_scan() and " + "read_additional_correction() first.", + force, + ): + print("Aborting tomo scan.") + return # bec.active_account is already a plain str, not bytes -- .decode() # crashes with AttributeError. Also guard against no active # e-account (empty string, e.g. a dev/sim session not logged into @@ -2061,6 +2096,31 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools def _get_val(msg: str, default_value, data_type): return data_type(input(f"{msg} ({default_value}): ") or default_value) + @staticmethod + def _confirm_sequence_override(warning: str, force: bool) -> bool: + """Print *warning* and ask whether to proceed anyway. + + Used by the alignment-sequence gates (xrayeye_alignment_start(), + tomo_alignment_scan(), tomo_scan()) -- these check whether the + expected prior step (rotation-center calibration / X-ray-eye + alignment / fine alignment) is still valid, but never hard-block: + an operator can always choose to continue, or pass force=True to + skip the prompt entirely (needed for non-interactive/queued use, + e.g. tomo-queue command jobs, where input() isn't viable). + + Unlike most confirmation prompts in this codebase (which default to + "yes" on Enter), this defaults to "no" -- skipping a real sequence + check should be a deliberate choice, not an accidental Enter. + + Returns: + bool: True if the caller should proceed. + """ + if force: + return True + print(warning) + answer = input("Continue anyway? [y/N]: ").strip().lower() + return answer in ("y", "yes") + # ------------------------------------------------------------------ # PDF report # ------------------------------------------------------------------