fix(lamni): alignment scan-number file lists first scan of each stitch series
CI for csaxs_bec / test (push) Successful in 3m9s

The file written before the alignment scan predicted first_scan + k per
angle, which is wrong with stitching ((2*sx+1)*(2*sy+1) scans per angle).
Predict with that stride, and rewrite the file after every angle so an
interrupted run still leaves correct numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ALDnH16dC6iyHjhe5Ey4w9
This commit is contained in:
x12saandClaude Opus 5.5 committed 2026-10-15 15:27:24 +02:00
1 parent c75a170af3
commit de14963f3e
2 files changed
+68 -18

No files matched your search

@@ -1486,22 +1486,28 @@ class LamNI(
) -> None:
"""Write the scan-number/angle/offset log consumed by BEC_ptycho_align.
``scans`` (optional): the actual scan number of each alignment point,
used to rewrite the file after the scan when a retried angle consumed
extra scan numbers. Defaults to the contiguous ``first_scan + k``
prediction written before the scan starts.
One scan number per alignment angle: the first scan of that angle's
stitch series (with stitching, each projection is
(2*lamni_stitch_x+1)*(2*lamni_stitch_y+1) scans).
Mirrors Flomni.write_alignment_scan_numbers() exactly (same 4-line
format), adapted to lamni's own alignment-scan angle set (12 points
across the full 360 degrees, see tomo_alignment_scan()) and its own
x-ray-eye-fit offset source (lamni_compute_additional_correction_xeye_mu()
instead of get_alignment_offset()). Assumes exactly one scan per
alignment point (no stitching) -- same assumption flomni's own
version makes.
``scans`` (optional): the actual first scan number of the angles
acquired so far (possibly fewer than all 12), used to rewrite the
file during/after the scan when a retried angle consumed extra scan
numbers. Angles not yet acquired are predicted from the last known
one, stepping by the number of stitch tiles per projection. Without
``scans`` the whole list is predicted from ``first_scan``.
Mirrors Flomni.write_alignment_scan_numbers() (same 4-line format),
adapted to lamni's own alignment-scan angle set (12 points across the
full 360 degrees, see tomo_alignment_scan()) and its own x-ray-eye-fit
offset source (lamni_compute_additional_correction_xeye_mu() instead
of get_alignment_offset()).
"""
angles = list(np.linspace(0, 360, num=12, endpoint=False))
if scans is None:
scans = [first_scan + k for k in range(len(angles))]
scans_per_angle = (2 * self.lamni_stitch_x + 1) * (2 * self.lamni_stitch_y + 1)
scans = list(scans) if scans else [first_scan]
while len(scans) < len(angles):
scans.append(scans[-1] + scans_per_angle)
x_vals = []
for angle in angles:
@@ -1593,14 +1599,13 @@ class LamNI(
first_scan_per_angle.append(start_scan_number)
alignment_scan_numbers.extend(range(start_scan_number, end_scan_number))
# Keep the file current after every angle, so an interrupted run
# still leaves correct first-of-stitch-series numbers behind.
self.write_alignment_scan_numbers(first_scan_per_angle[0], scans=first_scan_per_angle)
self.alignment_scan_progress.update(angle_index=idx + 1, angle=angle)
self._lamnigui_update_alignment_progress()
# The file written up front assumes first_scan + k per angle; a retried
# angle consumes extra scan numbers, so rewrite it with what was
# actually acquired.
self.write_alignment_scan_numbers(first_scan_per_angle[0], scans=first_scan_per_angle)
umv(dev.lsamrot, 0)
self.OMNYTools.printgreenbold(
"\n\nAlignment scan finished. Please run BEC_ptycho_align and load the new fit"
@@ -203,3 +203,48 @@ def test_tomo_alignment_scan_projections_are_not_queued_for_reconstruction(monke
assert len(kwargs_seen) == 12
assert all(kw.get("_reconstruct") is False for kw in kwargs_seen)
def test_write_alignment_scan_numbers_steps_by_stitch_tiles(tmp_path, monkeypatch):
"""With stitching each angle is (2*sx+1)*(2*sy+1) scans; the file must list
the first scan of each angle's stitch series."""
lamni = make_lamni(monkeypatch, xray_eye_fit=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])
lamni.client.set_global_var("lamni_stitch_x", 1)
lamni.client.set_global_var("lamni_stitch_y", 1)
monkeypatch.setattr(
"os.path.expanduser",
lambda p: p.replace("~", str(tmp_path)) if p.startswith("~") else p,
)
log_file = tmp_path / "data/raw/logs/ptychotomoalign_scannum.txt"
lamni.write_alignment_scan_numbers(100)
assert [int(s) for s in log_file.read_text().split("\n")[0].split()] == list(
range(100, 208, 9)
)
# partial actual list (angle 1 retried, +1 scan): rest predicted from it
lamni.write_alignment_scan_numbers(100, scans=[100, 110])
assert [int(s) for s in log_file.read_text().split("\n")[0].split()] == [100] + list(
range(110, 209, 9)
)
def test_tomo_alignment_scan_with_stitching_records_first_scan_per_angle(tmp_path, monkeypatch):
lamni = make_lamni(monkeypatch, xray_eye_fit=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])
lamni.client.set_global_var("lamni_stitch_x", 1)
lamni.client.set_global_var("lamni_stitch_y", 1)
monkeypatch.setattr(
"os.path.expanduser",
lambda p: p.replace("~", str(tmp_path)) if p.startswith("~") else p,
)
def _projection(angle, _internal=False, **kw):
builtins.__dict__["bec"].queue.next_scan_number += 9
lamni.tomo_scan_projection = _projection
lamni.tomo_alignment_scan()
log_file = tmp_path / "data/raw/logs/ptychotomoalign_scannum.txt"
scans = [int(s) for s in log_file.read_text().splitlines()[0].split()]
assert scans == list(range(100, 208, 9))