Fixes/flomni #260

Merged
holler merged 8 commits from fixes/flomni into main 2026-07-15 12:57:53 +02:00
Member
No description provided.
holler added 8 commits 2026-07-15 12:08:14 +02:00
Three independent fixes:

- sub_tomo_scan(): in 360-degree tomo_type-1 mode, every sub-tomogram used
  to sweep the full 0-360 range, so each one's own fine grid contained
  angle pairs exactly 180deg apart -- redundant tomographic information.
  Sub-tomograms now each cover a 180deg span, split into low/high halves
  by subtomo_number % 4 with a bit-reversal-of-4 phase table, so adjacent
  pairs (1,2)/(3,4)/(5,6)/(7,8), either quartet, and all 8 combined each
  independently form a complete, evenly-spaced 360deg tomogram at
  successively finer spacing. Total projection count for a given
  tomo_angle_stepsize is now identical between 180 and 360 mode (same N,
  no longer doubled). Updated the 4 other consumers of the old N/step
  formula (zero-deg reference gating, _tomo_type1_actual_grid, the
  parameter wizard, the PDF report) to match. 180-degree mode is
  unchanged. Live-verified against the running flomni sim: real motor
  motion traces the expected boustrophedon path with no duplicate or
  180deg-apart angles.

- Sample storage: added an owner field, packed into the same EPICS DESC
  field as the sample name ("name | owner", via new sample_desc_codec.py)
  since there's no separate PV for it. Wired through
  FlomniSampleStorage, the CLI (flomni_modify_storage_non_interactive,
  ftransfer_modify_storage), the two transfer routines that forward a
  raw DESC value across a gripper move (now unpacked/repacked so owner
  survives the move instead of being dropped or double-packed), and
  SampleStorageWidget. Scoped to flomni only this session; OMNY's
  storage/transfer mixin is unchanged.

- ConsoleButtonsWidget's ABORT button used to send SIGINT then, 500ms
  later, a stop_devices() broadcast to ALL devices with no stop_id --
  an un-suppressed error from that broadcast landing on a queue-tracked
  instruction could kill the scan worker thread outright, requiring a
  full BEC restart. Replaced with: queue.request_scan_abortion() (safe
  no-op if idle, but registers a stop_id so expected errors are
  suppressed), then SIGINT, then a direct, immediate Galil hard stop via
  new GalilController.hard_abort_and_restore_positioning_mode() -- the
  same method ftransfer_abort() now delegates to, so the CLI and GUI
  paths can't drift apart again. SIGINT is sent before the hard stop:
  live testing showed that if the hard stop's mntprgs-clearing side
  effect lands first, a same-session polling loop can mistake it for
  normal completion and fall through into ensure_gripper_up(), which
  must not happen mid-transfer -- sending SIGINT first (near-instant)
  gives that loop's own KeyboardInterrupt handler a head start before
  the hard stop's own multi-step sequence completes. The button is
  labeled per beamline (e.g. "Flomni Motion Stop") and disabled rather
  than silently inert when no hard-stop device is configured/enabled.
  Scoped to flomni only this session (OMNY/LamNI wiring deferred).
  Live-verified against the running sim, including the exact
  stop-lands-mid-queued-instruction scenario that previously crashed
  the scan worker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit's 360-degree phase assignment gave the low and high
halves the SAME phase per pair (1,2)/(3,4)/(5,6)/(7,8), so each pair
concatenated into one continuous evenly-spaced range -- but that means
every low-half angle is exactly 180deg from a high-half angle, which is
exactly the redundant-measurement bug this was supposed to fix, just
reintroduced between subtomos instead of within one.

Corrected: the low half (subtomos 1,4,5,8) now uses the even eighths of
the original 8-way phase_eighths table, and the high half (2,3,6,7) uses
the odd eighths -- complementary, not shared. Folded mod 180, the two
halves interleave into exactly the same 8-way, step/8 grid that 180-mode
itself produces: every position is measured exactly once, using its full
0-360 physical range, with zero redundant measurements anywhere. Total
projection count is unchanged (still identical to 180 mode for a given
tomo_angle_stepsize).

Verified: pure-math unit tests confirm the 360-mode combined set, folded
mod 180, is an exact set match (same count, no repeated residue) against
180-mode's own set. Live-verified against the running flomni sim: real
motor motion for both modes, folding the observed 360-mode angles mod
180 exactly reproduces the observed 180-mode angles.

Also updated the two "angular step of the final combined tomogram"
CLI/parameter-wizard print statements to additionally report the
effective mod-180 reconstruction resolution for 360 mode, since it's
now finer than the raw per-half spacing they already printed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the "effective (mod-180) reconstruction resolution" line added in
the previous commit -- the projection count already says everything
that matters; a derived resolution figure just adds noise. Replaced
with a plain "There are no duplicate angles." note in both display
spots (tomo_parameters() and the interactive wizard).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two independent fixes:

- GUI "beamline busy" indicators (TomoParamsWidget's banner,
  FlomniWebpageGenerator's status page) purely wait for
  progress["heartbeat"] to go stale (120s / 90s respectively) - there was
  no explicit "scan finished" signal to react to instead, since
  tomo_scan() wrote a heartbeat at the start of every projection but
  never cleared it on exit. Wrapped the scan loop in try/finally so
  heartbeat is cleared on every exit path (normal completion, exception,
  or interrupt/abort), not just at the next scan's start - both
  busy-detectors now see this on their very next poll instead of waiting
  out the timeout. Live-verified: heartbeat is None immediately after
  tomo_scan() returns.

- Found while investigating a related report ("angular step of the final
  combined tomogram shown different between 180 and 360 mode, although
  it has to be identical" + "GUI shows projection count doubling when
  switching 180->360"): TomoParamsWidget's _compute_type1()/
  _requested_to_stepsize() (tomo_params.py) and the generated status
  webpage's calcProjections() JS (flomni_webpage_generator.py) are both
  independent, un-synced duplicates of the exact old N=int(angle_range/
  stepsize) formula fixed in flomni.py's own _tomo_type1_actual_grid()
  two commits ago - they were never updated when that fix landed, so the
  GUI and webpage kept reporting double the projection count for 360
  mode. Fixed both to match flomni.py: N/step/total are always computed
  against a fixed 180 degrees, independent of angle_range. Also fixed
  the "angular step of the final (combined) tomogram" CLI/wizard lines
  in flomni.py itself, which used tomo_angle_range/total_projections
  (correct for 180 mode by coincidence, wrong for 360 mode - the true
  combined resolution is always 180/total_projections, identical
  between modes). Added a regression test asserting the widget's
  formulas match flomni.py's for both modes, to catch this exact kind of
  drift if it recurs. Live-verified against the running sim: both modes
  now report identical total_projections and combined-tomogram step for
  the same stepsize.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Supersedes the complementary even/odd-eighths tables from 900c810 with
a single shared phase_eighths table used in both 180 and 360 mode, so
subtomo N carries the same phase-tier role regardless of
tomo_angle_range. The complementary property of the low/high phase
sets now falls out as a consequence of the original table's structure
instead of needing a separately derived table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feye_in() called umv(dev.fttrx1, ...) unconditionally, so a config
without fttrx1 crashed instead of degrading gracefully. Mirrors the
existing feye_out() guard: warn that fttrx1 can't be moved and that
skipping it risks a hardware collision on the real beamline, then ask
yesno before continuing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_hard_stop_available() re-resolved hard_stop_device_name against the
device manager and disabled the button if that lookup failed, checked
once at construction with no way to recover for the widget's lifetime.
In practice this disabled the button even for ftransy confirmed
present and enabled from the CLI. Since _on_abort() already resolves
the device and calls its controller inside its own try/except, the
live pre-check was redundant and its false negatives cost more than
they protected. The button is now enabled whenever a
hard_stop_device_name was configured at all; a name that doesn't
actually resolve fails safely (logged, no-op) at click time instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(flomni,bec_widgets): also hard-stop the optics Galil controller
CI for csaxs_bec / test (pull_request) Successful in 3m0s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m28s
24b1633f04
The abort button only stopped ftransy's controller (the sample-transfer
board). foptx/fopty sit on a physically separate Galil controller (a
different socket port), so motion there kept running through an abort.

Adds an optional extra_hard_stop_device_name to ConsoleButtonsWidget,
wired to "foptx" for flomni. It calls .controller.stop_all_axes()
rather than hard_abort_and_restore_positioning_mode(): the latter's
#POSMODE/mntmod handling is specific to the sample-transfer/mount
program that only runs on ftransy's controller, so the plain
stop_all_axes() (XQ#STOP,1) is the correct generic stop for any other
board. Each device is stopped and logged independently so a failure on
one doesn't skip the other.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
holler merged commit 24b1633f04 into main 2026-07-15 12:57:53 +02:00
holler deleted branch fixes/flomni 2026-07-15 12:57:53 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bec/csaxs_bec#260