feat(LamNI): warn (overridable) when alignment steps are stale or missing
Nothing previously enforced the intended sequence: rotation-center calibration -> X-ray-eye alignment -> fine alignment scan -> real tomogram. Add three soft gates -- xrayeye_alignment_start() (no center found yet), tomo_alignment_scan() (X-ray-eye fit missing/invalidated, softened from a hard abort), and tomo_scan() (no fine alignment loaded) -- each printing a specific warning and prompting to continue, via a new shared _confirm_sequence_override() helper. Not a hard block: each method also gets force: bool = False to skip the check entirely for cases that legitimately don't need it (e.g. a large FOV that doesn't need fine alignment).
This commit is contained in:
@@ -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
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user