docs(flomni): document at_each_angle hooks in the user manual and AI_docs
CI for csaxs_bec / test (push) Successful in 1m33s

Adds a "Custom behavior at each projection angle" subsection to the
Tomography section of the flOMNI user manual (docs/user/ptychography/flomni.md):
what a hook function looks like, a polarizer-modulation example, and
how to register/activate/deactivate/list/unregister one, with an
explicit warning about resetting at_each_angle_hook before queuing a
normal follow-up job. Also links it from the quick-start queueing
section. AI_docs/TOMO_QUEUE_COMMAND_JOBS_PLAN.md gets a new section
3.4 recording the design decision (session-only registry, not a
hardcoded one; a new _TOMO_SCAN_PARAM_NAMES entry, not a new job kind;
the GUI param-snapshot leak found and fixed while wiring this up; no
GUI widget yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-13 16:37:56 +02:00
co-authored by Claude Sonnet 5
parent 40a3b78afe
commit 48647bf0d9
2 changed files with 103 additions and 1 deletions
+54 -1
View File
@@ -88,7 +88,7 @@ Several parameter sets can be queued and run one after another automatically, e.
If a queued scan is interrupted, the next call to `flomni.tomo_queue_execute()` resumes it automatically rather than starting over, then continues with the remaining entries.
The same queue can also hold **command jobs** that reconfigure the beamline between scans (e.g. change energy, then re-peak the undulator gap) instead of running a tomogram, so "scan A, reconfigure, scan B" runs unattended as one queue. See [Tomography](user.ptychography.flomni.tomography) below for the full command reference, including command jobs.
The same queue can also hold **command jobs** that reconfigure the beamline between scans (e.g. change energy, then re-peak the undulator gap) instead of running a tomogram, so "scan A, reconfigure, scan B" runs unattended as one queue. A queued tomogram can also run custom code at each projection angle (e.g. moving a polarizer in and out between two exposures) via a registered **at_each_angle hook**. See [Tomography](user.ptychography.flomni.tomography) below for the full command reference, including command jobs and hooks.
#### If something went wrong…
@@ -406,6 +406,59 @@ whole job is either safe to redo from the top or it isn't:
- If any step is not idempotent, you are asked whether to re-run the job from the top
or mark it done as-is.
#### Custom behavior at each projection angle
By default, every projection in a tomo scan is a single ptychography scan at that
angle. For cases that need something more — e.g. record a projection, move a
polarizer in, record again, move it back out, at every angle — write a small Python
function and register it as an **at_each_angle hook**. Once registered, it can be
selected per tomo-queue job, so the whole modulated tomogram runs unattended along
with any other queued jobs.
A hook is a function `func(flomni, angle)`, called once per projection angle instead
of the normal acquisition:
```python
def polarizer_modulation(flomni, angle):
flomni.tomo_scan_projection(angle, _internal=True)
umv(dev.polarizer, "in")
flomni.tomo_scan_projection(angle, _internal=True)
umv(dev.polarizer, "out")
```
| command | explanation |
| --- | --- |
| `flomni.register_at_each_angle_hook("name", func)` | Load a hook function under `name`. |
| `flomni.at_each_angle_hook = "name"` | Activate it — the next tomo-queue job added will use it. |
| `flomni.at_each_angle_hook = None` | Deactivate it — back to normal projections for the next job. |
| `flomni.list_at_each_angle_hooks()` | Print the names of all currently registered hooks (the active one is marked). |
| `flomni.unregister_at_each_angle_hook("name")` | Remove a hook by name. |
Example — queue a polarizer-modulated tomogram, then a normal one:
```python
flomni.register_at_each_angle_hook("polarizer_mod", polarizer_modulation)
flomni.tomo_parameters() # set up the scan parameters as usual
flomni.at_each_angle_hook = "polarizer_mod" # activate the hook
flomni.tomo_queue_add("polarizer run")
flomni.at_each_angle_hook = None # back to normal -- do not skip this!
flomni.tomo_queue_add("plain follow-up scan")
flomni.tomo_queue_execute() # runs both, hook active only for the first
```
**Do not forget to reset `flomni.at_each_angle_hook = None`** before adding a job
that should run normally — each queued job remembers whatever `at_each_angle_hook`
was set to at the moment it was added (like every other tomo parameter), so a job
added right after a hook-using one without resetting it first will silently run with
that hook still active.
Registered hooks are **session-only** — they live in memory and do not survive a
kernel restart. If `tomo_queue_execute()` reaches a job whose `at_each_angle_hook`
isn't registered in the current session (e.g. after restarting BEC), it stops with a
clear error rather than silently running a plain scan; re-run
`register_at_each_angle_hook()` for that hook, then call `tomo_queue_execute()`
again to resume.
### Sample storage and transfer
[See short version](user.ptychography.flomni.transfer)