feat(lamni): alignment scan includes 360 deg; lsamrot limits -2..362
CI for csaxs_bec / test (push) Successful in 3m3s
CI for csaxs_bec / test (push) Successful in 3m3s
The alignment scan now measures 13 angles, 0 to 360 inclusive; 360 repeats 0 as a check that nothing drifted during the run. The angle list lives in alignment_scan_angles(), shared with write_alignment_scan_numbers(). lsamrot soft limits set to -2/362 (were 0/0, i.e. none). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ALDnH16dC6iyHjhe5Ey4w9
This commit is contained in:
1 parent
e5c5d64307
commit
9c7b6dfd3b
4 files changed
+32
-24
No files matched your search
@@ -1481,6 +1481,13 @@ class LamNI(
|
||||
if _reconstruct:
|
||||
self.tomo_reconstruct()
|
||||
|
||||
@staticmethod
|
||||
def alignment_scan_angles() -> list[float]:
|
||||
"""Angles of tomo_alignment_scan(): every 30 degrees from 0 to 360
|
||||
inclusive. 360 is the same orientation as 0; it is measured anyway as
|
||||
a last data point confirming the sample did not drift during the run."""
|
||||
return list(np.linspace(0, 360, num=13))
|
||||
|
||||
def write_alignment_scan_numbers(
|
||||
self, first_scan: int, scans: list[int] | None = None
|
||||
) -> None:
|
||||
@@ -1491,19 +1498,19 @@ class LamNI(
|
||||
(2*lamni_stitch_x+1)*(2*lamni_stitch_y+1) scans).
|
||||
|
||||
``scans`` (optional): the actual first scan number of the angles
|
||||
acquired so far (possibly fewer than all 12), used to rewrite the
|
||||
acquired so far (possibly fewer than all 13), 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
|
||||
adapted to lamni's own alignment-scan angle set (13 points, 0 to 360
|
||||
degrees inclusive, see alignment_scan_angles()) 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))
|
||||
angles = self.alignment_scan_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):
|
||||
@@ -1526,7 +1533,8 @@ class LamNI(
|
||||
|
||||
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
|
||||
13 angles, every 30 degrees from 0 to 360 inclusive (see
|
||||
alignment_scan_angles()), using whatever
|
||||
tomo_parameters() are currently set (FOV/step/counting time --
|
||||
tomo_type and requested projection count are ignored, same as
|
||||
Flomni.tomo_alignment_scan()). Collects all scan numbers acquired
|
||||
@@ -1566,7 +1574,7 @@ class LamNI(
|
||||
|
||||
self.write_alignment_scan_numbers(bec.queue.next_scan_number)
|
||||
|
||||
angles = list(np.linspace(0, 360, num=12, endpoint=False))
|
||||
angles = self.alignment_scan_angles()
|
||||
alignment_scan_numbers = []
|
||||
|
||||
self.alignment_scan_progress.reset()
|
||||
|
||||
@@ -133,8 +133,8 @@ lsamrot:
|
||||
axis_Id: C
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
- -2
|
||||
- 362
|
||||
port: 8081
|
||||
sign: 1
|
||||
tolerance: 1
|
||||
|
||||
@@ -168,7 +168,7 @@ The correction tables are read from the files written by `BEC_ptycho_align`, wit
|
||||
|
||||
The alignment scan itself already uses the x-ray eye correction and any correction tables that are loaded. So the ptychographic alignment measures what is left over after those corrections, which is why a second iteration with `read_additional_correction_2()` is useful.
|
||||
|
||||
* `lamni.tomo_parameters()` adjust the ptychographic scan parameters for the alignment scan (FOV/step size/counting time) — `tomo_type` and number of projections are ignored, since the alignment scan always runs its own fixed 12 points spread evenly across the full 360 degrees, independent of the main tomogram's settings.
|
||||
* `lamni.tomo_parameters()` adjust the ptychographic scan parameters for the alignment scan (FOV/step size/counting time) — `tomo_type` and number of projections are ignored, since the alignment scan always runs its own fixed 13 points, every 30 degrees from 0 to 360 inclusive (360 repeats 0 as a drift check), independent of the main tomogram's settings.
|
||||
* `lamni.tomo_alignment_scan()` perform the alignment scan. Requires x-ray-eye alignment to have already been done — it will abort with a message otherwise. Scan numbers, angles and the existing x-ray-eye-fit offset at each angle are written to `~/data/raw/logs/ptychotomoalign_scannum.txt` and also printed at the end.
|
||||
* Run `BEC_ptycho_align` (in Matlab, **force ptycho=1**, and **correct scan numbers**) using the printed/logged scan numbers.
|
||||
* Click the sample position in the Matlab GUI and then load the generated file by, for example
|
||||
|
||||
@@ -93,7 +93,7 @@ def test_tomo_alignment_scan_aborts_without_xray_eye_fit(monkeypatch):
|
||||
assert lamni._scilog_calls == []
|
||||
|
||||
|
||||
def test_tomo_alignment_scan_runs_12_projections_across_360(monkeypatch):
|
||||
def test_tomo_alignment_scan_runs_13_projections_0_to_360(monkeypatch):
|
||||
lamni = make_lamni(monkeypatch, xray_eye_fit=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])
|
||||
calls = []
|
||||
|
||||
@@ -105,12 +105,12 @@ def test_tomo_alignment_scan_runs_12_projections_across_360(monkeypatch):
|
||||
|
||||
lamni.tomo_alignment_scan()
|
||||
|
||||
expected_angles = list(np.linspace(0, 360, num=12, endpoint=False))
|
||||
expected_angles = list(np.linspace(0, 360, num=13))
|
||||
assert calls == expected_angles
|
||||
assert len(lamni._scilog_calls) == 1
|
||||
content, tags = lamni._scilog_calls[0]
|
||||
assert tags == ["alignmentscan"]
|
||||
assert "Number of alignment scans: 12" in content, content
|
||||
assert "Number of alignment scans: 13" in content, content
|
||||
|
||||
|
||||
def test_tomo_alignment_scan_rotates_back_to_zero(monkeypatch):
|
||||
@@ -138,10 +138,10 @@ def test_write_alignment_scan_numbers_format(tmp_path, monkeypatch):
|
||||
assert len(lines) == 4
|
||||
scans = [int(s) for s in lines[0].split()]
|
||||
angles = [float(a) for a in lines[1].split()]
|
||||
assert scans == list(range(100, 112))
|
||||
assert angles == list(np.linspace(0, 360, num=12, endpoint=False))
|
||||
assert len(lines[2].split()) == 12
|
||||
assert len(lines[3].split()) == 12
|
||||
assert scans == list(range(100, 113))
|
||||
assert angles == list(np.linspace(0, 360, num=13))
|
||||
assert len(lines[2].split()) == 13
|
||||
assert len(lines[3].split()) == 13
|
||||
|
||||
|
||||
def _timeout_alarm():
|
||||
@@ -176,8 +176,8 @@ def test_tomo_alignment_scan_retries_timed_out_angle_and_records_real_scan_numbe
|
||||
|
||||
lamni.tomo_alignment_scan()
|
||||
|
||||
expected_angles = list(np.linspace(0, 360, num=12, endpoint=False))
|
||||
assert len(calls) == 13, "the timed-out angle must be re-acquired"
|
||||
expected_angles = list(np.linspace(0, 360, num=13))
|
||||
assert len(calls) == 14, "the timed-out angle must be re-acquired"
|
||||
assert calls[2] == calls[3] == expected_angles[2]
|
||||
|
||||
log_file = tmp_path / "data/raw/logs/ptychotomoalign_scannum.txt"
|
||||
@@ -185,10 +185,10 @@ def test_tomo_alignment_scan_retries_timed_out_angle_and_records_real_scan_numbe
|
||||
scans = [int(s) for s in lines[0].split()]
|
||||
# scans 100, 101 = angles 0, 1; 102 = the failed attempt at angle 2 (must be
|
||||
# skipped); 103.. = the retry and the remaining angles
|
||||
assert scans == [100, 101] + list(range(103, 113))
|
||||
assert scans == [100, 101] + list(range(103, 114))
|
||||
assert [float(a) for a in lines[1].split()] == expected_angles
|
||||
content, _tags = lamni._scilog_calls[0]
|
||||
assert "Number of alignment scans: 12" in content
|
||||
assert "Number of alignment scans: 13" in content
|
||||
|
||||
|
||||
def test_tomo_alignment_scan_projections_are_not_queued_for_reconstruction(monkeypatch):
|
||||
@@ -201,7 +201,7 @@ def test_tomo_alignment_scan_projections_are_not_queued_for_reconstruction(monke
|
||||
|
||||
lamni.tomo_alignment_scan()
|
||||
|
||||
assert len(kwargs_seen) == 12
|
||||
assert len(kwargs_seen) == 13
|
||||
assert all(kw.get("_reconstruct") is False for kw in kwargs_seen)
|
||||
|
||||
|
||||
@@ -219,13 +219,13 @@ def test_write_alignment_scan_numbers_steps_by_stitch_tiles(tmp_path, monkeypatc
|
||||
|
||||
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)
|
||||
range(100, 217, 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)
|
||||
range(110, 218, 9)
|
||||
)
|
||||
|
||||
|
||||
@@ -247,4 +247,4 @@ def test_tomo_alignment_scan_with_stitching_records_first_scan_per_angle(tmp_pat
|
||||
|
||||
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))
|
||||
assert scans == list(range(100, 217, 9))
|
||||
Reference in new issue
Block a user