feat(omny): modernize gui_tools.py to flomni's current dock/window API
CI for csaxs_bec / test (push) Successful in 1m52s
CI for csaxs_bec / test (push) Successful in 1m52s
OMNYGuiTools still used pre-modernization code: a gui_window handle that was never actually populated (self.gui.windows["main"].widget always raised/returned None), the old two-level add_dock().add_widget() API, and bare `if self.figN is None` checks instead of an _is_deleted()-aware guard. omnygui_show_progress() crashed with AttributeError as soon as it was exercised live. Ported OMNYGuiTools onto flomni's current single-level .new() pattern: split __init__()/set_client(), a window-reuse check keyed on self.gui.windows, _omnygui_is_missing() for dock-reuse (verbatim port of flomni's _is_deleted()-based guard), the current RingProgressBar API for omnygui_show_progress(), and new omnygui_show_xeyealign()/ omnygui_show_xeyealign_fittab()/omnygui_show_cameras() methods. cam_xeye is not part of omny's device config (unlike flomni's), so the xeyealign live-view toggle is guarded with an existence check instead of copied unconditionally. omnygui_show_cameras() adds a hard-stop console targeting otransy, the closest omny analog to flomni's ftransy. Also fixes two independently-broken call sites in x_ray_eye_align.py (self.lamni.lamnigui_show_xeyealign[_fittab]() -> the omny-side method names actually defined on OMNYGuiTools -- self.lamni is really the OMNY instance) that were raising AttributeError before the GUI code was even reached. The rest of that file's LamNI-derived content is left alone; a full rewrite is tracked separately. omny.py's __init__ now calls OMNYGuiTools.__init__(self) + set_client(), matching the split-constructor pattern instead of the old single-call OMNYGuiTools.__init__(self, self.client). Deliberately deferred: flomni's ETA/heartbeat progress fields (tomo_start_time, heartbeat, estimated_remaining_time, etc.) are not ported -- omny's self.progress dict only has 7 basic fields. Noted in omny/AI_docs/OPEN_ISSUES.md so it isn't forgotten; not a crash risk since omny_webpage_generator.py already reads these defensively. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt
This commit is contained in:
@@ -105,6 +105,20 @@ Still completely untested (not attempted yet, not known-broken — just unexplor
|
||||
yet — out of scope for the sim-validation work, in scope for the eventual macro-
|
||||
standardization pass.
|
||||
|
||||
## gui_tools.py progress fields (deferred on purpose)
|
||||
|
||||
`gui_tools.py` was modernized to flomni's current single-level `.new()` / `_is_deleted()`-guard
|
||||
pattern (window lifecycle, xray-eye-align GUI, cameras + hard-stop console, `RingProgressBar`).
|
||||
One piece was deliberately **not** ported: flomni's `self.progress` dict carries
|
||||
`tomo_start_time`/`heartbeat`/`estimated_remaining_time`/`estimated_finish_time` plus a
|
||||
`_describe_active_hook()` method feeding an ETA/heartbeat line in `_flomnigui_update_progress()`.
|
||||
omny's `self.progress` (set up in `OMNY.__init__`, `omny.py`) still only has the original 7 fields
|
||||
(`subtomo`, `subtomo_projection`, `subtomo_total_projections`, `projection`,
|
||||
`total_projections`, `angle`, `tomo_type`) and `_omnygui_update_progress()` was written against
|
||||
just those. Mirko's call: keep it simple for this pass, revisit later. Not a crash risk —
|
||||
`omny_webpage_generator.py` already reads the ETA-ish fields defensively via `.get()`, degrading
|
||||
to "N/A" when absent, and that file is unwired/dead code anyway (see below).
|
||||
|
||||
## Environment note (not code, but will bite again if forgotten)
|
||||
|
||||
Both `csaxs_bec` and `bec_widgets` were pip-installed editable pointing at pre-repo-
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import builtins
|
||||
import time
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
@@ -16,76 +20,184 @@ class OMNYGuiToolsError(Exception):
|
||||
|
||||
|
||||
class OMNYGuiTools:
|
||||
# Screen assumed 2560x1440. Window is right-aligned with a small margin
|
||||
# from the top, matching flomni's gui_tools.py convention.
|
||||
_SCREEN_WIDTH = 2560
|
||||
_WINDOW_WIDTH = 1500
|
||||
_WINDOW_HEIGHT = 850
|
||||
_WINDOW_TOP_MARGIN = 50
|
||||
|
||||
def __init__(self, client):
|
||||
self.gui = getattr(client, "gui", None)
|
||||
# No "main" BEC-widgets window exists yet in --nogui sessions, or before
|
||||
# a GUI has actually been opened; fall back to None like flomni's
|
||||
# GuiTools does (self.flomni_window, lazily populated by
|
||||
# flomnigui_show_gui()), instead of crashing OMNY's construction.
|
||||
try:
|
||||
self.gui_window = self.gui.windows["main"].widget
|
||||
except (KeyError, AttributeError):
|
||||
self.gui_window = None
|
||||
def __init__(self):
|
||||
self.omny_window = None
|
||||
self.text_box = None
|
||||
self.progressbar = None
|
||||
self.xeyegui = None
|
||||
self.idle_text_box = None
|
||||
self.fig200 = None
|
||||
self.fig201 = None
|
||||
self.fig202 = None
|
||||
self.fig203 = None
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
self.idle_text_box = None
|
||||
self.console = None
|
||||
|
||||
def set_client(self, client):
|
||||
self.client = client
|
||||
self.gui = getattr(client, "gui", None)
|
||||
|
||||
def omnygui_show_gui(self):
|
||||
self.gui_window.show()
|
||||
if "omny" in self.gui.windows:
|
||||
self.omny_window = self.gui.windows["omny"]
|
||||
self.gui.omny.raise_window()
|
||||
else:
|
||||
# geometry: (pos_x, pos_y, w, h)
|
||||
pos_x = self._SCREEN_WIDTH - self._WINDOW_WIDTH
|
||||
geometry = (pos_x, self._WINDOW_TOP_MARGIN, self._WINDOW_WIDTH, self._WINDOW_HEIGHT)
|
||||
self.omny_window = self.gui.new("omny", geometry=geometry)
|
||||
time.sleep(1)
|
||||
|
||||
def omnygui_stop_gui(self):
|
||||
self.gui_window.hide()
|
||||
self.gui.omny.hide()
|
||||
|
||||
def omnygui_raise(self):
|
||||
self.gui.omny.raise_window()
|
||||
|
||||
def _omnygui_is_missing(self, attribute_name):
|
||||
widget = getattr(self, attribute_name, None)
|
||||
if widget is None:
|
||||
return True
|
||||
if hasattr(widget, "_is_deleted") and widget._is_deleted():
|
||||
return True
|
||||
return False
|
||||
|
||||
def _omnycam_check_device_exists(self, device):
|
||||
try:
|
||||
device
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def omnygui_show_xeyealign(self):
|
||||
self.omnygui_show_gui()
|
||||
if self._omnygui_is_missing("xeyegui"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.xeyegui = self.gui.omny.new("OMNY_XRayEye", object_name="xrayeye")
|
||||
# cam_xeye is not (yet) part of omny's device config -- guard rather
|
||||
# than assume it exists, unlike flomni's unconditional version.
|
||||
if "cam_xeye" in dev and not dev.cam_xeye.live_mode_enabled.get():
|
||||
dev.cam_xeye.live_mode_enabled.put(True)
|
||||
self.xeyegui.switch_tab("alignment")
|
||||
|
||||
def omnygui_show_xeyealign_fittab(self):
|
||||
self.omnygui_show_gui()
|
||||
if self._omnygui_is_missing("xeyegui"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.xeyegui = self.gui.omny.new("OMNY_XRayEye", object_name="xrayeye")
|
||||
self.xeyegui.switch_tab("fit")
|
||||
|
||||
def _omnycam_parking(self):
|
||||
self.omnygui_show_omnycam_parking()
|
||||
|
||||
def omnygui_show_omnycam_parking(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.fig200 is None:
|
||||
self._omnycam_clear()
|
||||
self.fig200 = self.gui_window.add_dock(name="omnycam200").add_widget("BECImageWidget")
|
||||
if self._omnygui_is_missing("fig200") or self._omnygui_is_missing("fig203"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.fig200 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam200):
|
||||
fig = self.fig200.image("cam200")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig200.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam200. Device does not exist.")
|
||||
self.fig203 = self.gui_window.add_dock(name="omnycam203").add_widget("BECImageWidget")
|
||||
self.fig203 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam203):
|
||||
fig = self.fig203.image("cam203")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig203.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam203. Device does not exist.")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
|
||||
def _omnycam_samplestage(self):
|
||||
self.omnygui_show_omnycam_samplestage()
|
||||
|
||||
def omnygui_show_omnycam_samplestage(self):
|
||||
self.omnygui_show_gui()
|
||||
if self._omnygui_is_missing("fig201") or self._omnygui_is_missing("fig202"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.fig201 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam201):
|
||||
fig = self.fig201.image("cam201")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig201.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam201. Device does not exist.")
|
||||
self.fig202 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam202):
|
||||
fig = self.fig202.image("cam202")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig202.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam202. Device does not exist.")
|
||||
|
||||
def omnygui_show_cameras(self):
|
||||
"""General camera view + hard-stop console, the omny analog of
|
||||
flomni's flomnigui_show_cameras(). Distinct from
|
||||
omnygui_show_omnycam_parking()/omnygui_show_omnycam_samplestage()
|
||||
above, which are omny's own pre-existing, narrower parking/
|
||||
samplestage-specific views -- left untouched.
|
||||
"""
|
||||
self.omnygui_show_gui()
|
||||
if (
|
||||
self._omnygui_is_missing("fig200")
|
||||
or self._omnygui_is_missing("fig203")
|
||||
or self._omnygui_is_missing("console")
|
||||
):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.fig200 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam200):
|
||||
fig = self.fig200.image("cam200")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig200.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam200. Device does not exist.")
|
||||
self.fig203 = self.gui.omny.new("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam203):
|
||||
fig = self.fig203.image("cam203")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig203.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam203. Device does not exist.")
|
||||
|
||||
# Hard-stop console, docked below the cameras. otransy is the
|
||||
# closest omny analog to flomni's ftransy (both the transfer-Y
|
||||
# axis) -- no secondary/extra stop device wired yet (Mirko's
|
||||
# call, revisit once a second candidate is confirmed).
|
||||
self.console = self.gui.omny.new(
|
||||
"z_ConsoleButtonsWidget",
|
||||
object_name="console",
|
||||
where="bottom",
|
||||
hard_stop_device_name="otransy",
|
||||
hard_stop_label="OMNY Motion Stop",
|
||||
)
|
||||
self.gui.omny.set_layout_ratios(vertical=[5, 1])
|
||||
|
||||
def omnygui_remove_all_docks(self):
|
||||
self.gui_window.clear_all()
|
||||
if hasattr(self.gui, "omny"):
|
||||
self.gui.omny.delete_all()
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
self.xeyegui = None
|
||||
self.idle_text_box = None
|
||||
self.fig200 = None
|
||||
self.fig201 = None
|
||||
self.fig202 = None
|
||||
self.fig203 = None
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
self.idle_text_box = None
|
||||
self.console = None
|
||||
|
||||
def omnygui_idle(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.idle_text_box is None:
|
||||
if self._omnygui_is_missing("idle_text_box"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.idle_text_box = self.gui_window.add_dock(name="idle_text").add_widget("TextBox")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
self.idle_text_box = self.gui.omny.new("TextBox")
|
||||
text = (
|
||||
"<pre>"
|
||||
+ "██████╗ ███████╗ ██████╗ ██████╗ ███╗ ███╗███╗ ██╗██╗ ██╗\n"
|
||||
@@ -98,79 +210,50 @@ class OMNYGuiTools:
|
||||
)
|
||||
self.idle_text_box.set_html_text(text)
|
||||
|
||||
def _omnycam_clear(self):
|
||||
self.omnygui_remove_all_docks()
|
||||
|
||||
def _omnycam_check_device_exists(self, device):
|
||||
try:
|
||||
device
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _omnycam_samplestage(self):
|
||||
self.omnygui_show_omnycam_samplestage()
|
||||
|
||||
def omnygui_show_omnycam_samplestage(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.fig201 is None:
|
||||
self.omnygui_remove_all_docks()
|
||||
self.fig201 = self.gui_window.add_dock(name="omnycam201").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam201):
|
||||
fig = self.fig201.image("cam201")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig201.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam201. Device does not exist.")
|
||||
self.fig202 = self.gui_window.add_dock(name="omnycam202").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam202):
|
||||
fig = self.fig202.image("cam202")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig202.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam202. Device does not exist.")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
|
||||
def omnygui_show_progress(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.progressbar is None:
|
||||
self.omnygui_remove_all_docks()
|
||||
# Add a new dock with a RingProgressBar widget
|
||||
self.progressbar = self.gui_window.add_dock(name="progress").add_widget(
|
||||
"RingProgressBar"
|
||||
)
|
||||
# Customize the size of the progress ring
|
||||
self.progressbar.set_line_widths(20)
|
||||
# Disable automatic updates and manually set the self.progressbar value
|
||||
self.progressbar.enable_auto_updates(False)
|
||||
# Set precision for the self.progressbar display
|
||||
self.progressbar.set_precision(1) # Display self.progressbar with one decimal places
|
||||
# Setting multiple rigns with different values
|
||||
self.progressbar.set_number_of_bars(3)
|
||||
self.progressbar.rings[2].set_update("scan")
|
||||
# Set the values of the rings to 50, 75, and 25 from outer to inner ring
|
||||
# self.progressbar.set_value([50, 75])
|
||||
# Add a new dock with a TextBox widget
|
||||
self.text_box = self.gui_window.add_dock(name="progress_text").add_widget("TextBox")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
self._omnygui_update_progress()
|
||||
try:
|
||||
self.omnygui_show_gui()
|
||||
if self._omnygui_is_missing("progressbar"):
|
||||
self.omnygui_remove_all_docks()
|
||||
self.progressbar = self.gui.omny.new("RingProgressBar")
|
||||
# Two manually-updated rings: overall tomogram progress and
|
||||
# subtomo progress. Unlike flomni's third, auto-updating
|
||||
# "scan"-linked ring, and unlike flomni's ETA/heartbeat/
|
||||
# active-hook text, omny's progress dict doesn't carry that
|
||||
# data yet (see omny/AI_docs/OPEN_ISSUES.md) -- kept simple
|
||||
# here on purpose, revisit once that's ported.
|
||||
self.progressbar.add_ring().set_update("manual")
|
||||
self.progressbar.add_ring().set_update("manual")
|
||||
self._omnygui_update_progress()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error in omnygui_show_progress: {e}")
|
||||
|
||||
def _omnygui_update_progress(self):
|
||||
if self.progressbar is not None:
|
||||
try:
|
||||
if self.progressbar is None:
|
||||
logger.warning("OMNY progress GUI update skipped: progressbar is not available.")
|
||||
return
|
||||
main_progress_ring = self.progressbar.rings[0]
|
||||
subtomo_progress_ring = self.progressbar.rings[1]
|
||||
progress = self.progress["projection"] / self.progress["total_projections"] * 100
|
||||
subotmo_progress = (
|
||||
subtomo_progress = (
|
||||
self.progress["subtomo_projection"]
|
||||
/ self.progress["subtomo_total_projections"]
|
||||
* 100
|
||||
)
|
||||
self.progressbar.set_value([progress, subotmo_progress])
|
||||
main_progress_ring.set_value(progress)
|
||||
subtomo_progress_ring.set_value(subtomo_progress)
|
||||
|
||||
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
|
||||
self.text_box.set_plain_text(text)
|
||||
text = (
|
||||
f"Progress report:\n"
|
||||
f" Tomo type: {self.progress['tomo_type']}\n"
|
||||
f" Projection: {self.progress['projection']:.0f}\n"
|
||||
f" Total projections expected {self.progress['total_projections']:.1f}\n"
|
||||
f" Angle: {self.progress['angle']:.1f}\n"
|
||||
f" Current subtomo: {self.progress['subtomo']}\n"
|
||||
f" Current projection within subtomo: {self.progress['subtomo_projection']}\n"
|
||||
f" Total projections per subtomo: {int(self.progress['subtomo_total_projections'])}"
|
||||
)
|
||||
self.progressbar.set_center_label(text)
|
||||
except Exception as exc:
|
||||
logger.warning(f"OMNY progress GUI update skipped: {exc}")
|
||||
|
||||
@@ -596,7 +596,8 @@ class OMNY(
|
||||
self.OMNYTools = OMNYTools(self.client)
|
||||
OMNY_rt_client.__init__(self)
|
||||
self.align = XrayEyeAlign(self.client, self)
|
||||
OMNYGuiTools.__init__(self, self.client)
|
||||
OMNYGuiTools.__init__(self)
|
||||
self.set_client(self.client)
|
||||
|
||||
def start_x_ray_eye_alignment(self):
|
||||
if self.OMNYTools.yesno(
|
||||
|
||||
@@ -79,7 +79,7 @@ class XrayEyeAlign:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _reset_init_values(self):
|
||||
self.shift_xy = [0.0, 0.0] # base shift to bring sample to beam centre [µm]
|
||||
self.shift_xy = [0.0, 0.0] # base shift to bring sample to beam centre [µm]
|
||||
self._xray_fov_xy = [0.0, 0.0]
|
||||
|
||||
def tomo_rotate(self, val: float):
|
||||
@@ -161,7 +161,7 @@ class XrayEyeAlign:
|
||||
Compute x/y offsets, push to GUI fit tab, wait for DAP fit,
|
||||
then load fit parameters into the global variable store.
|
||||
"""
|
||||
self.lamni.lamnigui_show_xeyealign()
|
||||
self.lamni.omnygui_show_xeyealign()
|
||||
self.send_message("Getting things ready. Please wait...")
|
||||
|
||||
self.gui.enable_submit_button(False)
|
||||
@@ -188,14 +188,8 @@ class XrayEyeAlign:
|
||||
k = 0
|
||||
while True:
|
||||
if dev.omny_xray_gui.submit.get() == 1:
|
||||
val_x = (
|
||||
getattr(dev.omny_xray_gui, f"xval_x_{k}").get()
|
||||
* self.PIXEL_CALIBRATION
|
||||
)
|
||||
val_y = (
|
||||
getattr(dev.omny_xray_gui, f"yval_y_{k}").get()
|
||||
* self.PIXEL_CALIBRATION
|
||||
)
|
||||
val_x = getattr(dev.omny_xray_gui, f"xval_x_{k}").get() * self.PIXEL_CALIBRATION
|
||||
val_y = getattr(dev.omny_xray_gui, f"yval_y_{k}").get() * self.PIXEL_CALIBRATION
|
||||
self.alignment_values[k] = [val_x, val_y]
|
||||
print(
|
||||
f"Clicked position {k}: "
|
||||
@@ -242,15 +236,11 @@ class XrayEyeAlign:
|
||||
self.gui.enable_submit_button(False)
|
||||
|
||||
self.scans.lamni_move_to_scan_center(
|
||||
self.shift_xy[0] / 1000,
|
||||
self.shift_xy[1] / 1000,
|
||||
self.get_tomo_angle(),
|
||||
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
|
||||
).wait()
|
||||
time.sleep(1)
|
||||
self.scans.lamni_move_to_scan_center(
|
||||
self.shift_xy[0] / 1000,
|
||||
self.shift_xy[1] / 1000,
|
||||
self.get_tomo_angle(),
|
||||
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
|
||||
).wait()
|
||||
|
||||
dev.omny_xray_gui.angle.set(self.get_tomo_angle())
|
||||
@@ -274,16 +264,12 @@ class XrayEyeAlign:
|
||||
self._disable_rt_feedback()
|
||||
self.tomo_rotate(approach_angle)
|
||||
self.scans.lamni_move_to_scan_center(
|
||||
self.shift_xy[0] / 1000,
|
||||
self.shift_xy[1] / 1000,
|
||||
self.get_tomo_angle(),
|
||||
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
|
||||
).wait()
|
||||
self._disable_rt_feedback()
|
||||
self.tomo_rotate(target_angle)
|
||||
self.scans.lamni_move_to_scan_center(
|
||||
self.shift_xy[0] / 1000,
|
||||
self.shift_xy[1] / 1000,
|
||||
self.get_tomo_angle(),
|
||||
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
|
||||
).wait()
|
||||
|
||||
dev.omny_xray_gui.angle.set(self.get_tomo_angle())
|
||||
@@ -317,9 +303,7 @@ class XrayEyeAlign:
|
||||
self.shift_xy[0] += _mvx
|
||||
self.shift_xy[1] += _mvy
|
||||
self.scans.lamni_move_to_scan_center(
|
||||
self.shift_xy[0] / 1000,
|
||||
self.shift_xy[1] / 1000,
|
||||
self.get_tomo_angle(),
|
||||
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
|
||||
).wait()
|
||||
print(
|
||||
f"Current centre: "
|
||||
@@ -350,7 +334,7 @@ class XrayEyeAlign:
|
||||
self.client.set_global_var("tomo_fov_offset", self.shift_xy)
|
||||
|
||||
# Switch GUI to fit tab and wait for DAP to finish fitting
|
||||
self.lamni.lamnigui_show_xeyealign_fittab()
|
||||
self.lamni.omnygui_show_xeyealign_fittab()
|
||||
print("Waiting 5 s for DAP sinusoidal fit to converge...")
|
||||
time.sleep(5)
|
||||
|
||||
@@ -399,12 +383,8 @@ class XrayEyeAlign:
|
||||
f.write("angle\thorizontal\tvertical\n")
|
||||
for k in range(1, 9):
|
||||
angle_deg = LAMNI_ALIGNMENT_ANGLES[k - 1]
|
||||
x_off = (
|
||||
self.alignment_values[1][0] - self.alignment_values[k][0]
|
||||
) * 1000
|
||||
y_off = (
|
||||
self.alignment_values[k][1] - self.alignment_values[1][1]
|
||||
) * 1000
|
||||
x_off = (self.alignment_values[1][0] - self.alignment_values[k][0]) * 1000
|
||||
y_off = (self.alignment_values[k][1] - self.alignment_values[1][1]) * 1000
|
||||
f.write(f"{angle_deg}\t{x_off:.4f}\t{y_off:.4f}\n")
|
||||
print(
|
||||
f" Angle {angle_deg:3d} deg: "
|
||||
@@ -428,4 +408,4 @@ class XrayEyeAlign:
|
||||
|
||||
# Push to XRayEye widget: feeds waveform_x (row 1) and waveform_y (row 2)
|
||||
self.gui.submit_fit_array(data)
|
||||
print(f"Fit data submitted with shape {data.shape}:\n{data}")
|
||||
print(f"Fit data submitted with shape {data.shape}:\n{data}")
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Unit tests for OMNYGuiTools, ported to flomni's current set_client()/
|
||||
_is_deleted()-guard pattern. Focused on the pure-logic pieces that don't
|
||||
require a real bec_widgets/Qt GUI: the dock-reuse guard and the
|
||||
cam_xeye existence guard in omnygui_show_xeyealign() (omny's device config
|
||||
has no cam_xeye device, unlike flomni's)."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.omny.gui_tools import OMNYGuiTools
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
||||
GUI_TOOLS = "csaxs_bec.bec_ipython_client.plugins.omny.gui_tools"
|
||||
|
||||
|
||||
def _make_gui_tools():
|
||||
tools = OMNYGuiTools()
|
||||
client = mock.MagicMock()
|
||||
tools.set_client(client)
|
||||
return tools, client
|
||||
|
||||
|
||||
def test_omnygui_is_missing_true_when_never_set():
|
||||
tools, _ = _make_gui_tools()
|
||||
assert tools._omnygui_is_missing("xeyegui") is True
|
||||
|
||||
|
||||
def test_omnygui_is_missing_true_when_deleted():
|
||||
tools, _ = _make_gui_tools()
|
||||
widget = mock.MagicMock()
|
||||
widget._is_deleted.return_value = True
|
||||
tools.xeyegui = widget
|
||||
assert tools._omnygui_is_missing("xeyegui") is True
|
||||
|
||||
|
||||
def test_omnygui_is_missing_false_for_live_widget():
|
||||
tools, _ = _make_gui_tools()
|
||||
widget = mock.MagicMock()
|
||||
widget._is_deleted.return_value = False
|
||||
tools.xeyegui = widget
|
||||
assert tools._omnygui_is_missing("xeyegui") is False
|
||||
|
||||
|
||||
def test_omnygui_is_missing_false_when_no_is_deleted_method():
|
||||
tools, _ = _make_gui_tools()
|
||||
tools.xeyegui = object()
|
||||
assert tools._omnygui_is_missing("xeyegui") is False
|
||||
|
||||
|
||||
def test_omnygui_show_xeyealign_skips_cam_xeye_when_not_configured():
|
||||
"""omny has no cam_xeye device configured -- must not raise when dev
|
||||
lacks it, unlike flomni's unconditional live_mode_enabled toggle."""
|
||||
tools, client = _make_gui_tools()
|
||||
client.gui.windows = {"omny": mock.MagicMock()}
|
||||
new_widget = mock.MagicMock()
|
||||
client.gui.omny.new.return_value = new_widget
|
||||
|
||||
dev_mock = mock.MagicMock()
|
||||
dev_mock.__contains__ = mock.Mock(return_value=False)
|
||||
|
||||
with mock.patch(f"{GUI_TOOLS}.dev", dev_mock, create=True):
|
||||
tools.omnygui_show_xeyealign()
|
||||
|
||||
new_widget.switch_tab.assert_called_once_with("alignment")
|
||||
|
||||
|
||||
def test_omnygui_show_xeyealign_enables_live_mode_when_cam_xeye_present():
|
||||
tools, client = _make_gui_tools()
|
||||
client.gui.windows = {"omny": mock.MagicMock()}
|
||||
new_widget = mock.MagicMock()
|
||||
client.gui.omny.new.return_value = new_widget
|
||||
|
||||
dev_mock = mock.MagicMock()
|
||||
dev_mock.__contains__ = mock.Mock(return_value=True)
|
||||
dev_mock.cam_xeye.live_mode_enabled.get.return_value = False
|
||||
|
||||
with mock.patch(f"{GUI_TOOLS}.dev", dev_mock, create=True):
|
||||
tools.omnygui_show_xeyealign()
|
||||
|
||||
dev_mock.cam_xeye.live_mode_enabled.put.assert_called_once_with(True)
|
||||
new_widget.switch_tab.assert_called_once_with("alignment")
|
||||
|
||||
|
||||
def test_omnygui_show_gui_reuses_existing_window():
|
||||
tools, client = _make_gui_tools()
|
||||
existing_window = mock.MagicMock()
|
||||
client.gui.windows = {"omny": existing_window}
|
||||
|
||||
tools.omnygui_show_gui()
|
||||
|
||||
client.gui.new.assert_not_called()
|
||||
client.gui.omny.raise_window.assert_called_once()
|
||||
assert tools.omny_window is existing_window
|
||||
|
||||
|
||||
def test_omnygui_show_gui_creates_new_window_when_absent():
|
||||
tools, client = _make_gui_tools()
|
||||
client.gui.windows = {}
|
||||
new_window = mock.MagicMock()
|
||||
client.gui.new.return_value = new_window
|
||||
|
||||
with mock.patch(f"{GUI_TOOLS}.time.sleep"):
|
||||
tools.omnygui_show_gui()
|
||||
|
||||
client.gui.new.assert_called_once()
|
||||
assert tools.omny_window is new_window
|
||||
Reference in New Issue
Block a user