diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py index 6a0f8002..8794401b 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py @@ -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() diff --git a/csaxs_bec/device_configs/ptycho_lamni.yaml b/csaxs_bec/device_configs/ptycho_lamni.yaml index 8c0169ec..59c95567 100644 --- a/csaxs_bec/device_configs/ptycho_lamni.yaml +++ b/csaxs_bec/device_configs/ptycho_lamni.yaml @@ -133,8 +133,8 @@ lsamrot: axis_Id: C host: mpc2680.psi.ch limits: - - 0 - - 0 + - -2 + - 362 port: 8081 sign: 1 tolerance: 1 diff --git a/docs/user/ptychography/lamni.md b/docs/user/ptychography/lamni.md index af91b2be..682581a9 100644 --- a/docs/user/ptychography/lamni.md +++ b/docs/user/ptychography/lamni.md @@ -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 diff --git a/tests/tests_bec_ipython_client/test_lamni_tomo_alignment_scan.py b/tests/tests_bec_ipython_client/test_lamni_tomo_alignment_scan.py index 3807f730..944de437 100644 --- a/tests/tests_bec_ipython_client/test_lamni_tomo_alignment_scan.py +++ b/tests/tests_bec_ipython_client/test_lamni_tomo_alignment_scan.py @@ -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))