From cc7cfeef3ef42f392d066dfd3ce70e260b92f18d Mon Sep 17 00:00:00 2001 From: x12sa Date: Fri, 3 Jul 2026 13:28:02 +0200 Subject: [PATCH] option for random offset in single point acquisitions --- .../plugins/flomni/flomni.py | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index 2e1c766..d34af24 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -1,6 +1,7 @@ import builtins import datetime import os +import random import subprocess import time from pathlib import Path @@ -1597,6 +1598,25 @@ class Flomni( def manual_shift_y(self, val: float): self.client.set_global_var("manual_shift_y", val) + @property + def single_point_random_shift_max(self): + """Maximum absolute random shift [um] applied independently in x and y + at each single-point acquisition (tomo_acquire_at_angle). At each + angle a fresh random offset drawn uniformly from + [-single_point_random_shift_max, +single_point_random_shift_max] is + added to both x and y. 0 disables the random shift. Only takes effect + when single_point_instead_of_fermat_scan is active.""" + val = self.client.get_global_var("single_point_random_shift_max") + if val is None: + return 0.0 + return val + + @single_point_random_shift_max.setter + def single_point_random_shift_max(self, val: float): + if not 0 <= val <= 10: + raise ValueError("single_point_random_shift_max must be between 0 and 10 um.") + self.client.set_global_var("single_point_random_shift_max", val) + @property def fovx(self): val = self.client.get_global_var("fovx") @@ -2443,6 +2463,8 @@ class Flomni( self, base_path="~/data/raw/logs/reconstruction_queue", probe_propagation: float | None = None, + random_offset_x: float | None = None, + random_offset_y: float | None = None, ): """write the tomo reconstruct file for the reconstruction queue""" bec = builtins.__dict__.get("bec") @@ -2451,6 +2473,8 @@ class Flomni( next_scan_number=bec.queue.next_scan_number, base_path=base_path, probe_file_propagation=probe_propagation, + random_offset_x=random_offset_x, + random_offset_y=random_offset_y, ) def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None: @@ -2458,7 +2482,7 @@ class Flomni( with open(tomo_scan_numbers_file, "a+") as out_file: # pylint: disable=undefined-variable out_file.write( - f"{scan_number} {angle} {dev.fsamroy.read()['fsamroy']['value']:.3f} {self.tomo_id} {subtomo_number} {0} {self.sample_name}\n" + f"{scan_number} {angle} {dev.fsamroy.read()['fsamroy']['value']:.5f} {self.tomo_id} {subtomo_number} {0} {self.sample_name}\n" ) def tomo_scan_projection(self, angle: float): @@ -2550,17 +2574,30 @@ class Flomni( else: print("No rotation required") + # --- optional random shift in x and y (single-point acquisitions) --- + # A fresh offset is drawn per angle from a uniform distribution over + # [-single_point_random_shift_max, +single_point_random_shift_max] and + # added independently to x and y. 0 (the default) disables it. + random_shift_max = self.single_point_random_shift_max + if random_shift_max > 0: + random_shift_x = random.uniform(-random_shift_max, random_shift_max) + random_shift_y = random.uniform(-random_shift_max, random_shift_max) + else: + random_shift_x = 0.0 + random_shift_y = 0.0 + # --- alignment offset (same as tomo_scan_projection, no stitching) --- offsets = self.get_alignment_offset(angle) - sum_offset_x = offsets[0] + sum_offset_x = offsets[0] + random_shift_x sum_offset_y = ( offsets[1] - self.compute_additional_correction_y(angle) - self.compute_additional_correction_y_2(angle) + self.manual_shift_y + + random_shift_y ) sum_offset_z = offsets[2] - + # TODO this fix is while the tracker z is broken probe_propagation = -sum_offset_z * 1e-6 sum_offset_z = 0 @@ -2585,7 +2622,11 @@ class Flomni( n_frames = frames_per_trigger if frames_per_trigger is not None else self.frames_per_trigger self._current_scan_list = [bec.queue.next_scan_number] scans.acquire(exp_time=self.tomo_countingtime, frames_per_trigger=n_frames) - self.tomo_reconstruct(probe_propagation=probe_propagation) + self.tomo_reconstruct( + probe_propagation=probe_propagation, + random_offset_x=random_shift_x, + random_offset_y=random_shift_y, + ) def _tomo_type1_actual_grid(self) -> tuple[int, float, int]: """Compute the actual (achievable) tomo_type==1 grid from the @@ -2623,6 +2664,8 @@ class Flomni( print(f" _manual_shift_y = {self.manual_shift_y}") print(f"Frames per trigger (burst) = {self.frames_per_trigger}") print(f"Single point instead of fermat = {self.single_point_instead_of_fermat_scan}") + if self.single_point_instead_of_fermat_scan: + print(f"Single point random shift max = {self.single_point_random_shift_max}") print("") if self.tomo_type == 1: @@ -2710,6 +2753,12 @@ class Flomni( ) self.stitch_x = 0 self.stitch_y = 0 + if self.single_point_instead_of_fermat_scan: + self.single_point_random_shift_max = self._get_val( + " um (0 = off)", + self.single_point_random_shift_max, + float, + ) print("Tomography type:") print(" 1: 8 equally spaced sub-tomograms") @@ -2822,6 +2871,7 @@ class Flomni( "manual_shift_y", "frames_per_trigger", "single_point_instead_of_fermat_scan", + "single_point_random_shift_max", "tomo_type", "tomo_angle_range", "tomo_angle_stepsize",