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>