feat(LamNI): add ring-progress display for tomo_alignment_scan()

tomo_alignment_scan() had no GUI feedback at all -- just console
prints -- unlike the real tomogram, which already drives a
RingProgressBar via lamnigui_show_progress(). Add the same kind of
single-ring display for the 12-angle alignment loop
(lamnigui_show_alignment_progress()/_lamnigui_update_alignment_progress()),
backed by its own alignment_scan_progress global var rather than
tomo_progress, so anything watching tomo_progress for the real
tomogram (heartbeat/idle-time tracking) never sees alignment-scan
writes mixed in.
This commit is contained in:
x01dc
2026-07-27 16:14:11 +02:00
parent 33c9c0e433
commit b5655fd6eb
2 changed files with 80 additions and 1 deletions
@@ -25,6 +25,7 @@ class LamniGuiTools:
self.lamni_window = None
self.text_box = None
self.progressbar = None
self.alignment_progressbar = None
self.xeyegui = None
self.pdf_viewer = None
self.idle_text_box = None
@@ -72,6 +73,7 @@ class LamniGuiTools:
if hasattr(self.gui, "lamni"):
self.gui.lamni.delete_all(timeout=self.GUI_RPC_TIMEOUT)
self.progressbar = None
self.alignment_progressbar = None
self.text_box = None
self.xeyegui = None
self.pdf_viewer = None
@@ -288,6 +290,48 @@ class LamniGuiTools:
text += f"\n Hook: {hook_description}"
self.progressbar.set_center_label(text)
# ------------------------------------------------------------------
# Alignment scan progress bar
# ------------------------------------------------------------------
def lamnigui_show_alignment_progress(self):
"""Open (or raise) a single-ring progress bar for tomo_alignment_scan().
A separate dock/widget from lamnigui_show_progress() (the real
tomogram's 3-ring bar) -- kept distinct since it's backed by its
own global var (see LamNI.alignment_scan_progress).
"""
self.lamnigui_show_gui()
if self._lamnigui_is_missing("alignment_progressbar"):
self.lamnigui_remove_all_docks()
self.alignment_progressbar = self.gui.lamni.new(
"RingProgressBar", timeout=self.GUI_RPC_TIMEOUT
)
# Single ring: alignment-scan angle progress (manual update)
self.alignment_progressbar.add_ring().set_update("manual")
self._lamnigui_update_alignment_progress()
def _lamnigui_update_alignment_progress(self):
"""Update the alignment-scan progress ring and centre label from
self.alignment_scan_progress (see LamNI.alignment_scan_progress)."""
if self.alignment_progressbar is None:
return
ring = self.alignment_progressbar.rings[0]
total = self.alignment_scan_progress["total_angles"]
done = self.alignment_scan_progress["angle_index"]
progress = done / total * 100 if total else 0
ring.set_value(progress)
angle = self.alignment_scan_progress.get("angle", 0.0)
text = (
f"Alignment scan progress:\n"
f" Angle {done}/{total}\n"
f" Current angle: {angle:.1f} deg"
)
self.alignment_progressbar.set_center_label(text)
if __name__ == "__main__":
from bec_lib.client import BECClient
@@ -109,6 +109,18 @@ class _ProgressProxy:
return self._load()
class _AlignmentScanProgressProxy(_ProgressProxy):
"""Same dict-proxy pattern as _ProgressProxy, but for tomo_alignment_scan()'s
own progress (angle N/12) -- kept in its own global var, deliberately
separate from tomo_progress, so anything watching tomo_progress for the
real tomogram (e.g. heartbeat/idle-time tracking) never sees alignment
scan writes mixed in.
"""
_GLOBAL_VAR_KEY = "alignment_scan_progress"
_DEFAULTS: dict = {"angle_index": 0, "total_angles": 12, "angle": 0.0}
class LamNIError(Exception):
"""A definite, non-transient tomo-scan failure (bad config, unmet
precondition, ...) that should never be retried by @scan_repeat."""
@@ -224,6 +236,7 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
# tomo_scan()); use tomo_progress_reset() to explicitly clear stale
# progress without starting a new scan.
self._progress_proxy = _ProgressProxy(self.client)
self._alignment_scan_progress_proxy = _AlignmentScanProgressProxy(self.client)
self._init_tomo_queue()
from csaxs_bec.bec_ipython_client.plugins.LamNI.LamNI_webpage_generator import (
@@ -392,6 +405,21 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
self._progress_proxy.reset()
print("Tomo progress reset.")
@property
def alignment_scan_progress(self) -> _AlignmentScanProgressProxy:
"""Proxy dict backed by the BEC global variable ``alignment_scan_progress``.
Tracks tomo_alignment_scan()'s own progress (angle N/total) --
deliberately separate from ``progress``/``tomo_progress`` (the real
tomogram's state), so the two can never be confused by anything
watching one or the other.
Readable from any BEC client session via::
client.get_global_var("alignment_scan_progress")
"""
return self._alignment_scan_progress_proxy
@staticmethod
def _format_duration(seconds: float) -> str:
"""Format a duration in seconds as a human-readable string, e.g. '2h 03m 15s'."""
@@ -1222,7 +1250,11 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
angles = list(np.linspace(0, 360, num=12, endpoint=False))
alignment_scan_numbers = []
for angle in angles:
self.alignment_scan_progress.reset()
self.alignment_scan_progress.update(total_angles=len(angles), angle_index=0, angle=angles[0])
self.lamnigui_show_alignment_progress()
for idx, angle in enumerate(angles):
successful = False
print(f"Starting LamNI scan for angle {angle}")
while not successful:
@@ -1242,6 +1274,9 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
successful = True
self.alignment_scan_progress.update(angle_index=idx + 1, angle=angle)
self._lamnigui_update_alignment_progress()
umv(dev.lsamrot, 0)
self.OMNYTools.printgreenbold(
"\n\nAlignment scan finished. Please run BEC_ptycho_align and load the new fit"