Skip to content

Capture plan for any detector (bp.count); Phantom arms in prepare and waits for the event trigger; GUI tab - #92

Open
Anthony Sligar (sligara7) wants to merge 2 commits into
NSLS2:mainfrom
sligara7:hex-gui-phantom-event-trigger
Open

Anthony Sligar (sligara7) wants to merge 2 commits into
NSLS2:mainfrom
sligara7:hex-gui-phantom-event-trigger

Conversation

@sligara7

@sligara7 Anthony Sligar (sligara7) commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Step 1 of an operator event-trigger button in the HEX GUI. The Phantom is often triggered by the operator while watching the live image; this PR adds the plan and device behaviour the button will act on. The button itself comes in a follow-up.

Changes

  • PhantomAcquireLogic.start_acquiring now arms the camera (Acquire without waiting for the put, which only completes when acquisition ends) and returns once it reports waiting for a trigger. The trigger / post-trigger / download waits run in a background task, collected by wait_for_idle (errors surface there) and cancelled by ensure_stopped. ophyd-async calls start_acquiring from prepare for externally triggered detectors, where waiting for the trigger hung prepare. One behaviour serves a PandA-triggered fly scan and an operator-triggered capture.
  • hextools.tomography.flyscans.capture (any ophyd-async detector, not only the Phantom): a bp.count that prepares each detector for one event of num_images frames and takes one reading. A Phantom arms in trigger() and waits for its event trigger (timeout, default 600 s), then downloads the first num_images post-trigger frames; other detectors take the frames straight away. The prepare runs in per_shot, after count stages the detectors, because staging clears an earlier prepare. Times are plain floats, so the GUI renders them as number fields.
  • The profile exposes capture by name, and the GUI's Tomography view gets a Capture tab (detector list accepts any detector).

Ported from earlier work on planrunner-readiness (f19ea8d), without its queueserver send_event_trigger helper (a later step). An earlier revision of this PR had a Phantom-only fly-based phantom_capture in src/hextools/capture.py; that file is gone.

How it was verified: mock

  • tests/tomography/test_capture.py: capture on ophyd-async's SimBlobDetector produces one event whose image holds num_images frames. Observed failing before capture existed.
  • tests/detectors/test_phantom.py: the two new tests pass (start_acquiring returns before the trigger; ensure_stopped cancels a readout still waiting for its trigger).
  • The four Phantom tests that fail on main (60acf66) fail the same way here (arm_success, wait_for_idle_timeout, wait_for_idle_success, detector_full_stack), so capture has no passing end-to-end Phantom mock test yet. No new lint errors on the files touched.
  • Not yet run in the GUI, on the simulator, or on the TST or real beamline.

Notes

  • Overlaps Audit the ophyd-async devices and their mock tests #88 (devices-and-mock-tests), which carries other Phantom fixes; whichever merges second will need a merge in phantom.py and its tests.
  • CI is red on main itself (same test failures; type check cannot see the GUI packages), so the checks here will be red for the same reasons.

Assisted-by: copilot:claude-opus-4-8

Copilot AI balanced review requested due to automatic review settings October 5, 2026 21:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved GUI input, frame-selection, and acquisition-cleanup defects affect capture correctness.

Review effort: Balanced
Findings: 4 Medium severity

Open (4)
What changed in this PR

Adds operator-triggered Phantom capture to the beamline plans and Tomography GUI. The trigger button remains a follow-up.

Changes:

  • Moves trigger and readout waits into a background acquisition task.
  • Adds phantom_capture, profile exposure, and a GUI tab.
  • Updates mock tests for asynchronous acquisition and cancellation.
File Description
tests/​detectors/​test_phantom.py Tests nonblocking arming and readout cancellation.
src/​hextools/​profiles/​collection.py Exposes the capture plan by name.
src/​hextools/​gui/​__main__.py Adds the Phantom Capture tab.
src/​hextools/​detectors/​phantom.py Separates arming from background readout and adds cleanup.
src/​hextools/​capture.py Introduces the operator-triggered capture plan.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/hextools/capture.py Outdated
Comment thread src/hextools/capture.py Outdated
Comment on lines +536 to +537
readout, self._readout = self._readout, None
await readout
Comment on lines +583 to +588
if self._readout is not None:
readout, self._readout = self._readout, None
readout.cancel()
with contextlib.suppress(asyncio.CancelledError):
await readout
await super().ensure_stopped()
Copilot AI balanced review requested due to automatic review settings October 6, 2026 15:56
@sligara7 Anthony Sligar (sligara7) changed the title Phantom capture: arm in prepare, wait for the operator's event trigger; GUI tab Capture plan for any detector (bp.count); Phantom arms in prepare and waits for the event trigger; GUI tab Oct 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Queue Server detector selection and post-trigger frame handling have unresolved correctness issues.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity

Open (5)
Resolved since last review (2)



def capture(
detectors: list[StandardDetector],
Comment on lines +701 to +703
def _prepare_then_read(dets, take_reading=None):
for det in dets:
yield from bps.prepare(det, info, group="capture_prepare")
Comment on lines +703 to +705
yield from bps.prepare(det, info, group="capture_prepare")
yield from bps.wait(group="capture_prepare")
yield from bps.one_shot(dets, take_reading=take_reading)
…r; GUI tab

Step 1 of the HEX GUI event-trigger work (ports the device change and phantom_capture plan from f19ea8d on planrunner-readiness, without the queueserver helper, which is step 3). PhantomAcquireLogic.start_acquiring now arms and returns once the camera waits for its trigger; the trigger/post-trigger/download waits run in the background, collected by wait_for_idle and cancelled by ensure_stopped. phantom_capture (hextools.capture) arms in free-run and waits in complete for the event trigger. The profile exposes it by name and the GUI's Tomography view gets a 'Phantom Capture' tab.

Verification so far: mock tests - the two new arm/stop tests pass; the four Phantom tests that fail on main (60acf66) fail the same way here. Not yet run in the GUI or on the simulator.

Assisted-by: copilot:claude-opus-4-8
Replaces the Phantom-only phantom_capture (src/hextools/capture.py, removed) with capture(detectors, num_images, exposure_time, timeout, sample_name) in hextools.tomography.flyscans. It is bp.count with a per_shot that prepares each detector for one event of num_images frames after count's staging (staging clears an earlier prepare), then takes one reading. A Phantom arms in trigger() and waits for its event trigger; other detectors take the frames straight away. Times are plain floats, so the GUI renders them as numbers, not device selectors. GUI tab renamed 'Capture'.

Verified with mock tests: tests/tomography/test_capture.py runs capture on ophyd-async's SimBlobDetector and checks one event holding num_images frames; it failed (no capture) before this change. The 4 Phantom test failures are unchanged from the PR's previous commit.

Assisted-by: copilot:claude-opus-4-8
Copilot AI balanced review requested due to automatic review settings October 6, 2026 20:52
@sligara7
Anthony Sligar (sligara7) force-pushed the hex-gui-phantom-event-trigger branch from 1989089 to 6cb5f1c Compare October 6, 2026 20:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +583 to +588
if self._readout is not None:
readout, self._readout = self._readout, None
readout.cancel()
with contextlib.suppress(asyncio.CancelledError):
await readout
await super().ensure_stopped()
# in per_shot, after staging and before the reading.
def _prepare_then_read(dets, take_reading=None):
for det in dets:
yield from bps.prepare(det, info, group="capture_prepare")
Comment on lines +536 to +537
readout, self._readout = self._readout, None
await readout

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants