Skip to content

Launch Open Ephys GUI and wait for its API, locally or remotely - #294

Merged
bruno-f-cruz merged 7 commits into
mainfrom
feat/open-ephys-launch
Oct 31, 2026
Merged

bruno-f-cruz merged 7 commits into
mainfrom
feat/open-ephys-launch

Conversation

@bruno-f-cruz

@bruno-f-cruz bruno-f-cruz commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Stacked on #292 (uses run_async(executor=...)). Lets an experiment start the Open Ephys GUI, wait until it's ready, drive it, and shut it down, on this machine or on another rig:

ephys = OpenEphysApp(OpenEphysAppSettings(signal_chain=..., headless=True))
await ephys.launch(executor=remote)       # starts the GUI, waits for its HTTP API
try:
    ephys.start()                         # start recording
    await bonsai_app.run_async()
    ephys.stop()
finally:
    await ephys.close()                   # quits the GUI and waits for the process to end

Changes

  • launch() / start() / stop() / close() (no context manager):
    • launch(executor=None, timeout=180, ...) runs the GUI in the background, polls /api/status until it responds, and returns the client. If launching fails (the GUI exits first, or timeout passes), it closes the GUI itself before raising CommandError / RuntimeError / TimeoutError.
    • start(record=True) / stop() start and stop recording (or only acquisition).
    • close() quits the GUI, waits for it to exit and returns its result (raising CommandError on a non-zero exit). Calling it when nothing is launched does nothing.
  • Remote runs. With an executor that has a host (RemoteRepository) and an address of localhost / 127.0.0.1 / ::1, the client is pointed at the remote host. An explicit address is kept.
  • headless setting: adds --headless.
  • client.quit() fix. It now uses PUT /api/quit, falling back to the old /api/window command only on a 404. On GUI v1.1.0, /api/window returns 404, so the old quit() didn't work on the version Aind.Physiology.OpenEphys ships. /api/quit also works headless.
  • Default executable fix: now ./.open-ephys/open-ephys.exe (hyphens), matching Aind.Physiology.OpenEphys. The old ./.open_ephys/open_ephys.exe never matched.
  • Test submodule. tests/assets/Aind.Physiology.OpenEphys, pinned to 09c8579 (GUI v1.1.0).
  • Removed an unused test fixture that couldn't construct OpenEphysApp.

Tests

  • Fake GUI (fake_open_ephys_gui fixture): a small HTTP script covering readiness, early exit, timeout, caller errors and quit. Also run through a real RemoteRepository.
  • Real GUI, headless, locally and through RemoteRepository: launch, status, processors, start/stop acquisition, quit with exit code 0. About 4 s each.
    • Uses the File Reader chain that ships with the GUI. The repo's example.xml contains Neuropix-PXI, which blocks headless startup indefinitely with no hardware attached.
    • Skipped unless the GUI was downloaded (tests/assets/Aind.Physiology.OpenEphys/.open-ephys/setup.ps1 -NoLaunch), so CI (no submodules) skips them.

Review notes

  • Startup time. Windowed with example.xml, startup took 40–110 s on a machine without hardware, while hardware plugins searched for devices. Hence the 180 s default timeout.
  • Cancelling doesn't stop the GUI process (locally or remotely). If the GUI ignores the quit request, launch logs a warning that it may still be running.
  • Network access. For remote use, the GUI's port (37497) must be reachable from the launcher machine.
  • Possible follow-up: CI could run the real-GUI tests with submodules: true plus the setup step on a Windows runner.

- OpenEphysApp.launch(executor=...) is an async context manager that
  starts the GUI, waits until its HTTP API responds, yields the client,
  and quits the GUI on exit. It fails if the GUI exits before its API is
  up or the API does not respond within the timeout, and a caller error
  takes precedence over a GUI failure
- With an executor that runs on another machine (it has a `host`, like
  RemoteRepository), a loopback address is replaced by that host
- Add a `headless` setting (--headless)
- client.quit() uses /api/quit (GUI v1.1+, works headless), falling back
  to the /api/window command of older GUIs; /api/window returns 404 on
  v1.1.0
- Fix the default executable path to ./.open-ephys/open-ephys.exe, as
  laid out by Aind.Physiology.OpenEphys
- Add Aind.Physiology.OpenEphys as a test submodule; tests against the
  real GUI run headless and are skipped unless the GUI was downloaded
- Remove an unused Open Ephys test fixture that could not construct the app
Base automatically changed from feat/xml-rpc-remote-executor to main October 31, 2026 06:01
- launch() starts the GUI in the background, waits for its API and
  returns the client; it closes the GUI itself if launching fails
- start() / stop() start and stop recording (or only acquisition)
- close() quits the GUI, waits for it to exit and returns its result
- Removes the @asynccontextmanager with an AsyncIterator return type,
  which Pylance reports as deprecated
@bruno-f-cruz
bruno-f-cruz merged commit be139b1 into main Oct 31, 2026
6 checks passed
@bruno-f-cruz
bruno-f-cruz deleted the feat/open-ephys-launch branch October 31, 2026 06:40
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.

1 participant