Commit Graph
1078 Commits
Author SHA1 Message Date
x01dcandClaude Sonnet 5 bb5ff0cedf fix(omny): rotate the root osamroy device, not its user_setpoint signal
CI for csaxs_bec / test (push) Successful in 1m53s
Live-reproduced (local BEC deployment, real client) that the readoutPriority
fix (c37bfc7) didn't actually fix the readback-progressbar crash -- it just
moved it to a different device (rt_positions instead of cam200), confirming
the earlier diagnosis was incomplete.

Real root cause: omny_rotation() called
self.actions.set(self.dev.osamroy.user_setpoint, angle, wait=False),
passing the user_setpoint Signal sub-component instead of the root device.
ScanActions._normalize_device_name (bec_server) normalizes a sub-signal to
its dotted name ("osamroy.user_setpoint"), which never matches the plain
"osamroy" registered as requiring a response by the preceding
add_scan_report_instruction_readback(devices=["osamroy"], ...) call. So the
rotation's own completion is never reported, and the readback progressbar's
request-status listener stays subscribed for the rest of the scan -- until
post_scan()'s complete_all_devices() batches many unrelated owned devices
into one instruction later, which DOES trigger a response (since the literal
"osamroy" is also in that batch), reporting every device in it and crashing
the still-alive progressbar on whichever one happens to complete first.

Confirmed live this is omny-specific, not a flomni-shared exposure as
previously assumed: flomni_fermat_scan's equivalent flomni_rotation() calls
self.actions.set(self.dev.fsamroy, angle, wait=False) -- the whole root
device -- so its own completion reports correctly and the progressbar exits
cleanly well before complete_all_devices() runs. A live flomni_fermat_scan
run with cam_xeye still async+enabled completed without incident, disproving
the earlier "flomni has the same latent exposure" assumption.

Fixed by matching flomni's pattern exactly. OMNYGalilMotor.move()
(ogalil_ophyd.py) already does self.user_setpoint.put(...) plus proper
completion bookkeeping internally, so setting the root device is a strict
superset of the previous behavior, not a functional change to the move
itself. The readoutPriority change from the previous commit is kept (still
correct, matches flomni's camera convention) even though it wasn't the
actual fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt
2026-09-02 04:28:50 +02:00
x01dcandClaude Sonnet 5 c37bfc76d6 fix(omny): set cam200-203/cam_xeye readoutPriority to on_request, fixes scan crash
CI for csaxs_bec / test (push) Successful in 2m0s
omny.tomo_scan_projection(1) -> omny_fermat_scan crashed with ValueError: 'cam200'
is not in list once a scan actually did a readback-tracked osamroy rotation (the
first time this branch's testing got that far).

Root cause, traced through bec_server + bec_ipython_client core code: omny_fermat_scan's
add_scan_report_instruction_readback(devices=["osamroy"]) registers osamroy as requiring
a response; post_scan()'s complete_all_devices() then batches every enabled+claimable-owned
device into one end-of-scan instruction, including cam200 (readoutPriority: async +
enabled: true default to ownership_mode: "claimable", auto-acquired for any scan unless
readoutPriority is "on_request"). The batched instruction's response flag isn't scoped
per-device, so the server publishes a device-request-status message for cam200 too, under
the same RID the osamroy-only readback progressbar is listening on -- which then crashes
on an unrecognized device name.

cam200/201/202/203 (and cam_xeye, added last pass) are view-only GUI cameras never
referenced by any scan code (confirmed via repo-wide grep). Changing their readoutPriority
to on_request excludes them from the claimable/owned auto-acquire set entirely, fixing the
crash with a config-only change -- and matches flomni's own convention for this same
category of device (cam_flomni_gripper/cam_flomni_overview are both on_request already).
Live-mode GUI streaming (start_live_mode(), used by the parking/samplestage camera views)
is unaffected since it's a dedicated background thread, not driven by readout-priority
scheduling -- flomni's own cameras already prove on_request + live-mode works together.

Applied to both simulated_omny.yaml and the real ptycho_omny.yaml per Mirko: this brings
both configs in line with flomni's convention, not just a sim-only workaround.

The underlying bug (instruction-batch-scoped response flag in scan_actions.py:_send(),
plus missing per-device filtering in ReadbackDataHandler/DeviceProgressBar) is a genuine
bec-core issue, not omny-specific -- flomni's own cam_xeye has the same latent exposure,
untouched here since it's out of this branch's scope. Recorded in omny/AI_docs/OPEN_ISSUES.md
and reported upstream separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt
2026-09-02 03:46:52 +02:00
x01dcandClaude Sonnet 5 c802104f6d fix(omny): fix stale widget-API bugs in gui_tools.py cameras/xray-eye, add cam_xeye
CI for csaxs_bec / test (push) Successful in 1m51s
Live testing of the previous gui_tools.py port (49ade7c) hit two further crashes:

1. omnygui_show_xeyealign() -> ValueError: Device 'cam_xeye' not found in current
   BEC session. The OMNY_XRayEye widget (shared verbatim with flomni) hardcodes
   CAMERA = ("cam_xeye", "image") as a module constant -- it isn't configurable
   per-beamline, so omny needs its own cam_xeye device or the widget can never be
   constructed. Added cam_xeye to simulated_omny.yaml (hand-added, duplicating
   flomni's real cam_xeye's camera_id/pixel_calibration as placeholders; SimIDSCamera
   already accepts both camera_id and the legacy camera_ID key omny's other cameras
   use). ptycho_omny.yaml intentionally left untouched -- the real hardware camera ID
   and pixel calibration for OMNY's x-ray-eye camera are unknown; noted as a followup
   in OPEN_ISSUES.md, including that a future regen of simulated_omny.yaml from
   ptycho_omny.yaml will drop this hand-added block until the real config has one too.

2. omnygui_show_omnycam_parking() -> ValueError: Unknown widget type: BECImageWidget.
   That string was carried over unmodified from omny's pre-modernization code, not
   from flomni's current gui_tools.py (whose current class name is "Image"). Beyond
   the widget name, the per-camera calls inside were also stale: fig.set_rotation(
   deg_90=3) doesn't exist on the current Image/ImageBase widget at all (replaced by
   the num_rotation_90 property), and self.figN.lock_aspect_ratio(True) called a
   property as a function. Ported omnygui_show_omnycam_parking()/
   omnygui_show_omnycam_samplestage() onto flomni's current per-camera pattern
   (image(device=, signal="preview"), num_rotation_90/lock_aspect_ratio as
   properties, start_live_mode()), preserving the existing 3x90 rotation for
   cam200/cam203 (physical mount) and no rotation for cam201/cam202, unchanged from
   before.

Also removed omnygui_show_cameras() (added last pass, modeled on flomni's single
generic camera view): redundant now that both existing parking/samplestage views got
the current API, since omny -- unlike flomni -- already has two purpose-built camera
views. Its hard-stop console (z_ConsoleButtonsWidget, targeting otransy) now lives
directly in both omnygui_show_omnycam_parking() and omnygui_show_omnycam_samplestage()
instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt
2026-09-01 22:19:46 +02:00
x01dcandClaude Sonnet 5 49ade7ce98 feat(omny): modernize gui_tools.py to flomni's current dock/window API
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
2026-09-01 21:51:46 +02:00
x01dc 68a56607b8 fix(omny): fix scan hang -- RT flyer typo, add missing tracker-signal guard
CI for csaxs_bec / test (push) Successful in 1m54s
Two bugs found by comparing omny's RT-flyer code against flomni's (per
Mirko, since flomni/lamni's fermat scans already work fine in sim).

1. Root cause of the reported scan hang (tomo_scan_projection() starts a
   scan but the progress bar freezes at 0% forever, no error):
   RtOMNYFlyer.read_positions_from_sampler() called self.get_scan_status()
   instead of self.controller.get_scan_status(). get_scan_status() only
   exists on RtOMNYController, never on RtOMNYFlyer -- so this line raised
   an AttributeError inside the background thread complete() spawns for
   readout. An uncaught exception in a thread target doesn't propagate;
   the thread just dies silently, before ever calling status.set_finished().
   scan_core()'s `while not status.done` loop then spins forever, and since
   the thread died before ever updating progress, the bar stays at 0%.
   Confirmed correct in both RtFlomniFlyer's and RtLamniFlyer's equivalents
   already; fixed to match.

2. RtOMNYSetpointSignal._socket_set() never had the laser-tracker-signal-
   strength guard RtFlomniSetpointSignal._socket_set() got in commit
   68320e1 ("check tracker signal before rt move, which is otherwise stuck
   forever") -- a real-hardware failure mode (a move issued with a weak
   interferometer signal never converges). OMNY has its own duplicate
   setpoint-signal class, so the fix never propagated. Ported using OMNY's
   own existing laser_tracker_check_and_wait_for_signalstrength() (a more
   capable, retrying/auto-readjusting check) rather than flomni's
   single-shot version, which OMNY doesn't have. Lower confidence this one
   is the actual cause of the reported *simulated* hang specifically --
   the sim doesn't model signal-dependent convergence -- but it's a real,
   confirmed gap worth closing for real-hardware safety regardless.

4 new regression tests in tests/tests_devices/test_rt_omny.py, including
one that directly confirms the fake stand-in used for bug #1's test would
have caught the original bug (raises AttributeError the same way the real
class would).
2026-09-01 21:27:43 +02:00
x01dc 5281812b69 feat(omny): start osamroy sim at logical 0, speed up all sim stages 4x
CI for csaxs_bec / test (push) Successful in 2m24s
- osamroy's sim_initial_position is now -25 (raw), so the simulated
  logical readback (raw + 25, per the controller's calibration-mark
  offset) starts at ~0 deg instead of 25 -- no rotation-to-zero needed
  just from starting a session.
- All 21 SimOMNYGalilMotor axes now have an explicit sim_velocity, set
  to 4x their previous implicit default (the _SimGalilAxis fallback of
  5.0 * 25600 = 128000 steps/s). 10.0 for the 20 translation axes
  (stppermm=51200); 5.716463414632019 for osamroy specifically, since
  its stppermm=89565.8666667 differs -- both correspond to the same
  512000 steps/s, i.e. a genuine 4x speedup for every stage.
2026-09-01 20:39:20 +02:00
x01dc 68896a8ea2 fix(omny): let OmnyFermatScan.corridor_size accept None, auto-estimate
CI for csaxs_bec / test (push) Successful in 2m41s
omny_fermat_scan.py's corridor_size was Annotated[float] = 3, never
updated to accept None the way flomni_fermat_scan.py's already does.
But omny.py's tomo_scan_projection() calling code passes
corridor_size=None whenever omny.corridor_size (a property defaulting
to -1) hasn't been set to a positive value -- producing
ScanInputValidationError: Invalid type for scan argument 'corridor_size':
None is neither float or int.

Ported flomni's fix directly: corridor_size is now
Annotated[float | None] = None, capped at the new MAX_CORRIDOR_SIZE
(3 um) class constant if a caller passes something larger, and
prepare_scan() now calls the same _estimate_corridor_size() static
method flomni uses (median nearest-neighbor distance * 1.5, capped at
MAX_CORRIDOR_SIZE) to pick a sensible corridor size from the actual
scan positions whenever corridor_size is None, instead of crashing.

2 new tests for _estimate_corridor_size() in test_omny_fermat_scan.py.
2026-09-01 20:27:57 +02:00
x01dc e08ac1a44b fix(omny): make correction data global-var-backed, fix default file path
CI for csaxs_bec / test (push) Successful in 2m33s
Two real bugs found while investigating a recurring AttributeError:
'OMNY' object has no attribute 'corr_pos_x' from tomo_scan_projection().

1. default_correction_file/default_correction_file_x were bare relative
   filenames ("correction_omny_202204.txt"), unlike flomni's equivalent
   which resolves an absolute path via Path(csaxs_bec.__file__). open()
   would only ever succeed if bec were launched from this exact plugin
   directory -- silently swallowed everywhere else by the existing
   FileNotFoundError handler in reset_correction(). The real correction
   files genuinely exist on disk next to the plugin code and were very
   likely never actually being loaded.

2. corr_pos_x/corr_angle_x/corr_pos_y/corr_angle_y/corr_pos_y_2/
   corr_angle_y_2 were plain instance attributes, only ever assigned
   inside reset_correction() (itself only called from
   otransfer_put_sample() during a sample transfer) -- hence the
   AttributeError when calling tomo_scan_projection() beforehand.
   Converted to global-var-backed properties defaulting to [] when unset,
   mirroring flomni's corr_pos_y/corr_angle_y/etc. exactly. This
   supersedes a shallower getattr()-based band-aid from an earlier,
   uncommitted attempt at this fix. As a bonus, correction data now
   persists across BEC client restarts, same as flomni's.

Note flomni has no x-correction concept at all (only corr_pos_y/
corr_pos_y_2) -- omny's x-correction is its own thing, not something
copied from flomni.

17 new/updated tests in test_omny_alignment_mixin.py, including an
end-to-end check that reset_correction(use_default_correction=True) now
actually loads the real correction_omny_202204*.txt files.
2026-09-01 20:15:25 +02:00
x01dc 62b4b8d8fa feat(omny): skip OSA/FZP moves when already at the target position
CI for csaxs_bec / test (push) Successful in 2m58s
oosa_in(), oosa_out(), ofzp_in(), ofzp_out() used to unconditionally
re-move every axis (and, for FZP, unconditionally disable rt feedback)
even when already at the target -- visible as repeated no-op progress
bars on back-to-back calls. Ported the same "skip if already there"
optimization flomni's fosa_in()/ffzp_in() already have (and that lamni
already got too, see test_lamni_optics_skip_if_in_position.py).

For oosa_in()/oosa_out() the "already there" check has to run *before*
_oosa_check_y(), since that call unconditionally parks oosay at 0.9 --
never the real in/out position for either near- or far-field mode -- so
checking afterward would always miss the match. oosa_out()'s "out"
targets are hardcoded literals in the existing code (no user_parameter
for out, unlike oosa_in's far_field_in/near_field_in), so the skip
check compares against those same literals.

New tests/tests_bec_ipython_client/test_omny_optics_skip_if_in_position.py
(9 tests, mirroring the lamni test's structure): skip-vs-move for both
near/far field on oosa_in/oosa_out, and skip-vs-move-and-disable-feedback
for ofzp_in/ofzp_out.
2026-09-01 19:52:36 +02:00
x01dc 5e8fda761f feat(omny): seed the 3 known CryoCon channels in SimOMNYTemperatures
CI for csaxs_bec / test (push) Successful in 3m56s
Chan A "Cryo sample" 90K, Chan B "Cryo head" 82K, Chan C "Sample backup"
90K, all closed-loop (reading == setpoint). Channel D has no known real
use yet and is left unseeded.
2026-09-01 19:20:12 +02:00
x01dc 7aedac148c feat(omny): seed realistic values in SimOMNYDewar and SimOMNYTemperatures
CI for csaxs_bec / test (push) Successful in 1m52s
- SimOMNYDewar: patched_device()'s all-zero default read as an alarm
  state (LN2 flow 0.0 l/s trips OMNYDewar.is_flow_low()'s 3.8 l/s
  threshold). Seeded to a healthy/nominal state instead. These values
  are illustrative -- no real dewar-controller reference was available.
- SimOMNYTemperatures: seeded names/units/readings/setpoints for the 29
  real "TEMP" channels, sourced from the real OMNY_tempcontroller/
  tempcontrol.py channel config (name, EGU, ChanHeat, ALARMLIM) and
  cross-referenced against the existing _set_TEMP_default_setpoints()
  (already in this repo, confirmed to cover the same 29 channels).
  Heated channels read at their real setpoint; monitor-only channels get
  a plausible reading below their real alarm limit -- no channel now
  shows a false alarm. Channels not in use are now named "-" (matching
  tempcontrol.py's real default), so show_all() cleanly hides them
  instead of listing 19 fake all-zero rows. temperature_update_time and
  cryo_temperature_update_time are set to now() to avoid a false
  "controller communication is not running" warning.
- The 4 "CRYO" channels are NOT seeded yet -- pending the cryocon
  controller reference (still needed from Mirko).
2026-09-01 19:16:35 +02:00
x01dc da0121216b feat(omny): fall back to OS login for superuser check when no pgroup is set
CI for csaxs_bec / test (push) Successful in 2m3s
bec.active_account is a Redis-backed pgroup, only set via bec-set-account
-- a fresh dev/test session normally has it unset. _is_superuser_account()
now falls back to os.getenv("USER") in that case, matching the
account/system-user comparison convention already used in
OMNY_shared/omny_general_tools.py's _accounts_match(). Lets gac-x01dc's
superuser grant take effect without requiring bec-set-account first.
2026-09-01 19:12:03 +02:00
x01dc 336cae3cf5 feat(omny): grant gac-x01dc superuser access to advanced tomo modes
CI for csaxs_bec / test (push) Successful in 2m54s
For testing tomorrow. Note: bec.active_account is a Redis-backed pgroup
value set via `bec-set-account <pgroup>`, not derived from the OS login --
testing under this account requires running `bec-set-account gac-x01dc`
(or otherwise setting the pgroup to that value) before it takes effect.
2026-09-01 14:31:00 +02:00
x01dc e6928f295e feat(omny): add 4/8 sub-tomogram tomo modes, hide advanced modes from regular users
CI for csaxs_bec / test (push) Successful in 2m26s
- New shared static helper _equally_spaced_subtomo_angles(), ported from
  flomni's _subtomo_angle_plan() with the 180/360-degree-range branch
  removed (OMNY only ever scans 180 degrees). Generalizes the bit-reversal
  sub-tomogram interleaving to any power-of-two sub-tomogram count; for
  n_subtomos=2 it reduces to OMNY's original type-1 scheme.
- sub_tomo_scan() refactored onto the new helper (gains an n_subtomos
  param); tomo_type 1 (2 sub-tomograms) is otherwise unchanged.
- Adds tomo_type 4 (4 sub-tomograms) and 5 (8 sub-tomograms), gated by the
  same "wear in OMNY" authorization-code prompt already used for tomo_type
  2/3.
- Advanced tomo modes (2, 3, 4, 5) are now hidden from tomo_parameters()'s
  menu unless bec.active_account is in the new _SUPERUSER_ACCOUNTS
  allowlist; a non-superuser landing on an advanced type is forced back to
  type 1. _SUPERUSER_ACCOUNTS is left empty -- needs real account codes
  before anyone can use the advanced modes.
- As a side effect of reusing flomni's formula, also fixes a latent bug:
  the old code derived the inter-sub-tomogram phase offset from the raw
  configured tomo_angle_stepsize rather than the achievable (int-truncated)
  step, producing an unevenly-spaced combined grid whenever stepsize didn't
  divide 180 evenly.
- New tests/tests_bec_ipython_client/test_omny_tomo_angles.py: angle-count
  and no-duplicate-angle checks for n_subtomos in (2, 4, 8), a regression
  check against the old type-1 formula, an explicit test of the
  achievable-step fix, a cross-check against flomni's hardcoded N=8
  bit-reversal table, and superuser-gating tests (including the full
  tomo_parameters() interactive flow forcing a non-superuser back to
  type 1, and a superuser successfully unlocking type 5).
2026-09-01 14:26:10 +02:00
x01dc 446b201302 fix(omny): realistic sim motor temperatures, rotation heads-up, and corr_pos_x crash
CI for csaxs_bec / test (push) Successful in 1m47s
- sim_galil.py: default unseeded analog-input channels to a voltage that
  reads back as a realistic ~30 degC motor temperature instead of the
  previous 0.0V/150 degC default (real motors run well under 60 degC).
- rt_omny_ophyd.py: feedback_enable_with_reset() now tells the user the
  current angle and warns it might take a while when osamroy has more
  than 5 deg to rotate back to zero, matching flomni's style of heads-up.
- omny_sample_transfer_mixin.py: otransfer_put_sample() now calls
  reset_correction()/reset_tomo_alignment_fit() when mounting into the
  sample stage (pin_position == 0), mirroring flomni's
  ftransfer_put_sample() -> ftransfer_flomni_stage_in() pattern. Fixes
  an AttributeError ('OMNY' object has no attribute 'corr_pos_x') hit
  when calling tomo_scan_projection() after a sample transfer.
2026-09-01 14:17:38 +02:00
x01dc 57903579fc fix(sim): apply osamroy's 25 deg rotation offset in the simulated Galil controller
CI for csaxs_bec / test (push) Successful in 2m12s
Real hardware already applies this correctly: controller1.dmc's #NEWPAR
handler subtracts 25 deg from the target when setting osamroy's position
(contrID=3, axis=2), symmetric with the +25 the ophyd read side
(OMNYGalilReadbackSignal._socket_get) already adds back. The simulated
Galil controller (SimGalilState.start_move_from_newpar) didn't replicate
this, so a simulated osamroy.move(x) landed 25 deg away from where real
hardware would -- a simulator-fidelity bug, not a hardware or ophyd bug.

Also adds osamroy to the offline sim_lamni_omny_harness so this class of
bug is caught there going forward, and a focused unit test for the
round-trip and its scoping to port 8083 / axis 2 only.
2026-09-01 03:00:01 +02:00
Mirko HollerandClaude Sonnet 5 7066243c57 fix(omny): unbreak --session omny startup and the sim/harness gaps blocking its live smoke test
CI for csaxs_bec / test (push) Successful in 1m57s
- post_startup.py: --session omny imported OMNY from the flomni plugin
  package instead of omny (ImportError on every start)
- omny/x_ray_eye_align.py: XrayEyeAlign.__init__ referenced self.lamni.align
  on the OMNY instance mid-construction, before OMNY.__init__ finishes
  assigning it (AttributeError on every OMNY() construction)
- omny/gui_tools.py: OMNYGuiTools.__init__ crashed with KeyError in any
  session without an open BEC-widgets "main" window (e.g. --nogui); guarded
  to fall back to None like flomni's GuiTools
- omny_e2e_tests/sim_flomni_harness.py: FakeDeviceManager had no .connector,
  breaking rt_lamni_ophyd.py's feedback_enable_with_reset() client-info call
- omny_e2e_tests/sim_lamni_omny_harness.py: fixed a stale import of the
  renamed LamNIFermatScan module
- AI_docs/SIMULATED_ENDSTATIONS.md: recorded the OMNY live-test results
- omny/AI_docs/OPEN_ISSUES.md: new, tracks what the live smoke test found,
  including the unfixed osamroy rotation-offset bug that still blocks scans

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 19:51:18 +02:00
x12saandClaude Sonnet 5 2b581c1a9c feat(flomni): add A4 report Print button to sample-storage widget
CI for csaxs_bec / test (pull_request) Successful in 1m44s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 3m4s
Adds a Print button to OMNY_SampleStorage that renders an A4 PDF
replica of the on-screen stage/gripper/magazine grid (via
bec_lib.pdf_writer.PDFWriter) and sends it to the local CUPS queue
WSLA_X12SA via `lp`. Checks printer availability with a quick local
`lpstat` query first, so a host without that queue gets a plain
"printer not available" message instead of a raw CUPS error; the
actual `lp` call still offers Retry/Ignore on failure, mirroring the
existing P-touch label-print fail-soft pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgQoxFiJRZi1FnmnF8epKh
2026-08-20 16:02:37 +02:00
wakonig_k 71a1ac7ec5 feat(hexapod): add hexapod configuration and pivot device class
CI for csaxs_bec / test (pull_request) Successful in 1m47s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 1m40s
2026-08-19 16:17:10 +02:00
x12saandClaude Sonnet 5 a20be61251 fix(tests): stub measured_log in flomni tomo-angle test helper
CI for csaxs_bec / test (pull_request) Successful in 1m40s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m39s
make_flomni_for_golden_resume() builds a bare Flomni via object.__new__,
bypassing __init__, so it never got measured_log or a working
sample_get_measured_log_key -- both added by the measured-sample-tracking
commit for the P-touch label printer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 15:34:58 +02:00
x12saandClaude Sonnet 5 a31fe481a7 feat(flomni): track measured samples and offer a P-touch label print on clear/replace
CI for csaxs_bec / test (push) Failing after 1m39s
CI for csaxs_bec / test (pull_request) Failing after 1m44s
Records whether a tomo scan was started/completed for the sample
currently on the stage (a new _MeasuredSampleLog, redis-backed via
BEC global vars, keyed by the packed name|owner identity so it
survives ftransfer_get_sample/put_sample moves between slots). When a
tray slot's occupant is cleared or replaced via
ftransfer_modify_storage() (CLI) or the sample-storage widget's
Clear/Change-name actions (GUI), and that sample was measured, offer
to print an account/date/samplename label via PTouchLabelPrinter.
Also surfaces measured status in ftransfer_show_all() and the
widget's slot cells, including the stage line.

GUI failures retry via QMessageBox Retry/Ignore (a modal dialog runs
its own Qt event loop, so this can't hang the process the way the
CLI's input()-based ensure_ready() would) instead of losing the label
on the first failed attempt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 15:28:16 +02:00
bec-gitea 3f74191411 Updating to template version 1.5.1
CI for csaxs_bec / test (pull_request) Successful in 1m42s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m43s
2026-08-19 14:07:57 +02:00
holler fbc93e2876 Upload files to "csaxs_bec/bec_ipython_client/plugins/OMNY_shared"
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m45s
2026-08-19 13:54:51 +02:00
x12saandClaude Sonnet 5 0ddab287c3 feat(flomni): add PTouchLabelPrinter for Brother PT-P950NW labels
Fills the named text objects of a pre-transferred P-touch Template and
triggers a print over raw TCP:9100, validated end-to-end against the
real printer at BRN94DDF8AAB8EC.psi.ch. Shared OMNY_shared utility, not
an ophyd device, per docs/developer/ptouch_label_printer_plan.md.

Not yet wired into flomni.py -- that's the next step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 13:50:46 +02:00
holler b34ecb9b6c Upload files to "docs/developer" 2026-08-19 12:03:18 +02:00
x01dcandClaude Sonnet 5 1cbfdc40ea docs(flomni): add design plan for Brother P-touch label printer integration
Feasibility/design session output: print account+date labels on
sample-tray unload via P-touch Template mode, as a shared OMNY_shared
utility rather than an ophyd device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGC1TfxZJvJHs6Qz1V8w1h
2026-08-19 12:03:18 +02:00
x01dcandClaude Sonnet 5 8936071766 fix(flomni): stop OSA collision at fosa_in(), correct stale foptz init position
CI for csaxs_bec / test (pull_request) Successful in 1m45s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m44s
fosa_in() moved fosax/fosay/fosaz straight to their configured "in"
positions with no collision check. ffzp_info() already computed OSA-to-pin
clearance but never gated the move on it. Add a shared
_osa_remaining_space() helper (also used to deduplicate ffzp_info()'s own
calculation) and raise FlomniOpticsError in fosa_in() when the computed
clearance at the target position is <= 0.

Root cause of the specific collision seen: stage init's _align_setup()
hardcoded foptz to 23 instead of its calibrated "in" value of 17
(ptycho_flomni.yaml), 6 mm off from what fosaz's own "in" calibration
assumes. Hardcode foptz to 17 instead, and lock foptz.limits to +-0.1 mm
around it in both set_limits() and _align_setup() so any future move away
from 17 requires deliberately widening the limits first. Also corrects
the stale "in: 23" in ptycho_flomni.yaml's foptz userParameter to 17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 10:15:52 +02:00
x01dcandClaude Sonnet 5 308b4edacd fix(flomni): abort sample transfer when stage-out is declined
CI for csaxs_bec / test (push) Successful in 1m45s
ftransfer_gripper_move() silently returned on "No" to the stage-out
confirmation, but its return was indistinguishable from success to its
callers (ftransfer_get_sample/ftransfer_put_sample), which then proceeded
to command the physical get/mount sequence on the controller -- moving
the gripper without the stage ever having moved out or the gripper being
positioned at the transfer coordinates. Now raises FlomniError instead,
which propagates out of both callers and stops the transfer before any
controller command is sent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 15:19:17 +02:00
x01dcandClaude Sonnet 5 0a2c6a429c docs(omny_shared): update webpage-upload doc for omny.psi.ch cutover
CI for csaxs_bec / test (pull_request) Successful in 1m46s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 3s
CI for csaxs_bec / test (push) Successful in 1m44s
Reflects the single-URL omny.psi.ch config (myfritz/omny-test list retired)
and replaces the omny-test.psi.ch-specific .svg note with the known
permission and intermittent-WAF issues on omny.psi.ch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 14:54:57 +02:00
x01dcandClaude Sonnet 5 50d4f8e425 feat(flomni,lamni): repoint all omny endpoints from myfritz/omny-test to omny.psi.ch
CI for csaxs_bec / test (push) Successful in 1m51s
Cuts over the webpage status-page mirror, TomoIDManager's sample-counter
registration, and the samples-folder PDF upload to omny.psi.ch exclusively,
replacing v1p0zyg2w9n2k9c1.myfritz.net and omny-test.psi.ch everywhere.

Note: omny.psi.ch currently has known server-side issues (a permission
error on /upload.php, and a WAF blocking /samples/* outright) tracked
separately with PSI admins -- this switch is expected to be broken until
those are fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 14:42:10 +02:00
x01dcandClaude Sonnet 5 6bb8d8027d feat(flomni,lamni): mirror status-page uploads to omny.psi.ch
CI for csaxs_bec / test (push) Successful in 1m45s
Add omny.psi.ch as a third upload target alongside production and the
omny-test.psi.ch trial mirror.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 13:24:54 +02:00
x01dcandClaude Sonnet 5 5af6f82bb1 feat(bec_widgets): add data_viewer widget; rename OMNY widget classes
CI for csaxs_bec / test (pull_request) Successful in 1m57s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m48s
Add DataViewer, ported from debye_bec's bec_widgets/widgets/data_viewer,
adapted to this repo's parent()-first __init__ convention required by
bw-generate-cli.

Drop the "Widget" suffix from SampleStorage, SlitControl, and TomoParams
for naming consistency with DataViewer, then further rename for OMNY
namespacing and Designer-list ordering:
- SampleStorage -> OMNY_SampleStorage
- TomoParams -> OMNY_TomoParams
- XRayEye -> OMNY_XRayEye (XRayEye2DControl untouched)
- ConsoleButtonsWidget -> z_ConsoleButtonsWidget (sorts last)

Update the live gui.<area>.new("...") string-based widget lookups in
flomni/LamNI gui_tools.py that construct XRayEye/ConsoleButtonsWidget by
name, so they keep resolving after the rename. Delete now-stale generated
plugin/register/pyproject trios and regenerate everything via
`bw-generate-cli --target csaxs_bec` (client.py, designer_plugins.py, and
fresh per-widget trios under the new snake_case names).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZHQaRmthXmyfn2pxP3dF9
2026-08-17 17:20:21 +02:00
x01dcandClaude Sonnet 5 67769d5ff8 fix(flomni,lamni): use .get() in tomo_queue_show()'s job-line formatter
CI for csaxs_bec / test (pull_request) Successful in 2m14s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m49s
A job's params dict only has whatever _TOMO_SCAN_PARAM_NAMES held when it
was snapshotted by tomo_queue_add(); a job persisted before a key was
added (e.g. fovx/fovy, tomo_circfov) raised KeyError on the hard p['key']
lookup, making the whole queue un-inspectable instead of just missing a
field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 15:47:51 +02:00
x01dc 75cd5d0d00 fix(flomni,omny): expose motor tolerance to scan-server device proxy
CI for csaxs_bec / test (pull_request) Successful in 1m54s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m56s
self.dev.fsamroy/.osamroy on the scan server is a generic bec_lib
Positioner proxy rebuilt from serialized device info, not the real
ophyd instance. tolerance was only a plain __init__ attribute, so it
was dropped during serialization and flomni_rotation()/rotation()
crashed with AttributeError: 'Positioner' object has no attribute
'tolerance'. Register tolerance in USER_ACCESS so it serializes and
is reachable via RPC like controller already is.
2026-08-17 11:31:04 +02:00
x01dcandClaude Sonnet 5 4ac20013b8 fix(tomo_params): debounce beamline-busy banner to stop flicker
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m49s
_refresh_busy_banner() toggled the banner's visibility synchronously
on every poll tick and scan-queue push message, so brief blips in the
underlying queue status made it flicker on/off.

Show busy immediately, but hold the banner up for 5s of continuous
idle before clearing it, mirroring the debounce pattern already used
for XRayEye's queue-guarded toggles (_queue_idle_timer).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:52:33 +02:00
x01dcandClaude Sonnet 5 abb933e10d fix(tests): patch input() where yesno() actually calls it
CI for csaxs_bec / test (pull_request) Successful in 1m52s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m48s
find_rotation_center()'s y/n prompts now go through OMNYTools.yesno()
(omny_general_tools.py) instead of a raw input() in x_ray_eye_align.py,
but these tests were still patching input() on the old module -- so
the real input() got called under pytest's captured stdin and raised
"reading from stdin while output is captured".

Patch input() on omny_general_tools instead, where yesno() actually
calls it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:39:12 +02:00
x01dcandClaude Sonnet 5 f0152fe028 fix(lamni,omny): consolidate ad-hoc y/n prompts onto OMNYTools.yesno()
CI for csaxs_bec / test (push) Failing after 1m44s
CI for csaxs_bec / test (pull_request) Failing after 1m44s
Several confirmation prompts hand-rolled their own input() parsing
instead of using the established yesno() helper, including one
weaker variant (no default, no retry, case-sensitive) and a "Close
the shutter now?" prompt duplicated verbatim four times.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 14:46:49 +02:00
x01dcandClaude Sonnet 5 4c3c86183a fix(galil): poll thread 0 idle instead of guessing a sleep before XQ#FES/FRM
CI for csaxs_bec / test (pull_request) Successful in 1m53s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 2m25s
XQ#NEWPAR dispatches asynchronously on controller thread 0 and returns as
soon as it starts, not once it finishes; the dispatch time varies with
controller load. drive_axis_to_limit/find_reference followed it with a
fixed sleep before starting XQ#FES/XQ#FRM (also thread 0), which has
already needed bumping twice (0.1->0.3, 0.3->0.2) and still raced on
lgalil: sending XQ#FES while thread 0 was still busy got a '?' reply
(Galil error 19, thread already running).

Replace the fixed sleep with _wait_for_thread_idle(0), reusing the
existing is_thread_active() primitive (already used the same way in
hard_abort_and_restore_positioning_mode) to poll until thread 0 is
actually free before dispatching the next routine.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 23:28:09 +02:00
x01dcandClaude Sonnet 5 bdb65d0326 fix(smaract): stop mangling GCLS/GPL/GST/etc. replies with str.strip()
str.strip(prefix) strips individual characters, not a literal prefix, so
replies made entirely of characters in the prefix (e.g. ":CLS0,0" for
losax) collapsed to '' and crashed float('') in describe(). Other replies
could silently truncate to a wrong value instead. Since the prefix is
already validated by _message_starts_with(), removeprefix() is a safe
drop-in fix for all affected parsers in SmaractController.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 23:28:09 +02:00
wyzula_j 34e7dff17d ci: pytest-cov migrated to coverage
CI for csaxs_bec / test (pull_request) Successful in 1m55s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 2m28s
2026-08-12 17:34:33 +02:00
x12sa 13ab2d5f42 change PV names of slits in optics hutch
CI for csaxs_bec / test (pull_request) Successful in 4m24s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 5m15s
2026-08-04 15:18:00 +02:00
x01dcandClaude Sonnet 5 0e7ef10eee fix(lamni,flomni,omny): stop force-opening tomo-queue panel of running job
CI for csaxs_bec / test (pull_request) Successful in 1m49s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m47s
The status webpage's tomo-queue accordion auto-expanded a job's details
panel as soon as its status became "running", overriding whatever
open/closed state the user had selected. Panel state now depends only
on the existing DOM snapshot of user selections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 13:38:13 +02:00
x01dc badbb82ba4 fix(lamni,flomni,omny): re-issue rotation after an interrupted move instead of skipping
CI for csaxs_bec / test (push) Successful in 1m52s
A Ctrl+C during lsamrot/fsamroy/osamroy rotation leaves the cached
user_setpoint at the target even though the axis never finished moving
(stop() halts the motor mid-move). The next scan then wrongly concluded
"no rotation required" from setpoint equality alone, leaving LamNI's
air bearings unclamped (confirmed via galil show all) and the beamline
unable to scan until the setpoint was nudged manually.

lamni_rotation() now also requires
lgalil_is_air_off_and_orchestra_enabled() before skipping, since that
flag only goes true once the Galil #CENROT sequence (air-clamp plus
centering) has fully completed. flomni_rotation()/omny_rotation() use
a readback-vs-tolerance check instead, since those axes have no
air-bearing clamp step.

Along the way, fixed lgalil_is_air_off_and_orchestra_enabled() itself:
it did bool(socket_put_and_receive(...)) directly on the raw Galil
reply string, which is always truthy, so it could never report
anything but True. Confirmed via HW testing in LamNI.
2026-07-31 13:26:15 +02:00
x01dc a082a8cffa remove sample name querry, already done at coarse align 2026-07-31 12:49:46 +02:00
x01dc 59c7906ba7 docs update 2026-07-31 12:49:23 +02:00
x01dc 171481928a docs update 2026-07-31 12:49:13 +02:00
x01dcandClaude Sonnet 5 48b9f893ab fix(lamni,flomni): suppress spurious 0-deg damage-estimation shot on golden-ratio resume
CI for csaxs_bec / test (push) Successful in 1m48s
previous_subtomo_number resets to -1 on every tomo_scan() call in the
golden-ratio branches (tomo_type 2/3), including a resume. That makes
the first loop iteration look like a genuine sub-tomogram transition,
firing the 0-deg reference shot before the actual resume angle even
though the rotation isn't passing through 0 deg at that moment.

Mirrors tomo_type 1's existing start_angle-is-None resume guard:
suppress the shot only for the first, possibly-spurious transition
right after a resume, then let later genuine transitions in the same
call fire normally. Same fix applied to both lamni.py and flomni.py,
which share byte-for-byte identical golden-ratio logic here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:43:30 +02:00
x01dcandClaude Sonnet 5 7caf57cff6 feat(lamni): check measurement configuration before tomo_scan()
Flomni's tomo_scan() warns and prompts if the X-ray eye isn't out and
the FZP/OSA optics aren't in before scanning; LamNI had no equivalent,
letting a tomogram start with the eye still in the beam or the optics
retracted. Ports the same check, wired into the existing
force/interactive gating so it never blocks on input() during
unattended/queued runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:42:49 +02:00
x01dcandClaude Sonnet 5 1db9c18e0a fix(lamni): drop redundant 45deg detour in smear verification sweep, widen GUI retry budget
CI for csaxs_bec / test (push) Successful in 1m51s
find_rotation_center_smear_experimental() rotated 45->0 for its post-shift
verification sweep, needlessly re-treading ground the preceding full smear
sweep already covered and adding a long extra blocking move that could
starve the GUI heartbeat past _gui_call_with_retry's budget, surfacing as
RuntimeError: GUI is not alive. Sweep straight back to 0 instead, and widen
the retry budget (8x1.5s -> 25x2.0s) so update_frame() can ride out long
blocking moves on slower hardware.

Also tidy a stray comment-block artifact in ptycho_lamni.yaml and update
lamni.md docs for the current xrayeye_rotation_center_calibration_* API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 11:54:07 +02:00
x01dcandClaude Sonnet 5 fa30e11b3c fix(lamni): tolerate transient GUI heartbeat race, fix sample-name prompt order
CI for csaxs_bec / test (pull_request) Successful in 1m46s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 1m46s
xrayeye_rotation_center_calibration_isolated/smear_experimental() reproducibly
crashed with "RuntimeError: GUI is not alive" when keep_shutter_open=True, right
after a long blocking device move (interferometer feedback reset, live rotation
sweep). Root cause: bec_widgets' GUI liveness check is a Redis heartbeat with a
10s TTL refreshed from the same Qt event loop that renders live-view frames;
with live view left on continuously, a long blocking move can starve that
heartbeat past its TTL even though the GUI process is still alive. Add
_gui_call_with_retry() and use it at the on_live_view_enabled(True) call sites
that follow these blocking waits, so the transient false negative is retried
instead of crashing the calibration.

Also reorder the sample-name prompt in find_rotation_center() and
find_rotation_center_smear_experimental() to run before the alignment GUI is
shown -- showing the GUI first steals OS focus, forcing the operator to click
back to the terminal to answer the prompt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 16:01:10 +02:00