Skip to content

Drive the CLI and GUI from one pipeline function (#1) - #4

Merged
degoldschmidt merged 1 commit into
mainfrom
fix/issue-1-gui-provenance
Aug 21, 2026
Merged

Drive the CLI and GUI from one pipeline function (#1)#4
degoldschmidt merged 1 commit into
mainfrom
fix/issue-1-gui-provenance

Conversation

@degoldschmidt

Copy link
Copy Markdown
Collaborator

Closes #1.

A run started from the GUI wrote its tables and figures but no run_info.json /
config.used.yaml, so GUI results directories were not self-describing. Reproduced on
af3eeb4 by calling the GUI's job function directly (it needs no Qt to run):

GUI job wrote: ['comparisons.csv', 'events.csv', 'per_condition.csv', 'per_fly.csv']
run_info.json     present: False
config.used.yaml  present: False

Cause

Duplication, not a broken function. cli.run() and gui.workers.run_pipeline_job() were
near-identical copies of the same sequence — detect → tables → figures → write — and only
the CLI copy went on to call write_provenance(). write_provenance was imported in
cli/app.py and nowhere else in the package.

Also fixed by this

flypad stats reuses the run's config.used.yaml when present and falls back to a
default-constructed Config() when absent, then overwrites per_condition and
comparisons in that directory. On a GUI-produced directory it therefore silently
discarded the run's settings.

Worth scoping honestly: Config() and both presets agree on every field stats actually
uses (stats.ci_level, n_permutations, statistic, plotting.facet_by), so a run with
a stock config recomputed to the same numbers. It diverged only when an experiment.yaml
overrode one of those fields — but silently, either way.

The fix

pipeline.run_experiment() now owns the sequence, provenance included, and both entry
points call it. run_info.json records which one did, in its command field
("run" / "gui"); everything else about the directory is identical.

  • cli.run() drops to a call plus its summary line.
  • run_pipeline_job() becomes a Qt-free delegation tagging command="gui".
  • gui.JobResult is now an alias of the new pipeline.ExperimentResult, so the GUI keeps
    its vocabulary and gui/app.py is untouched.

flypad detect keeps writing its own provenance: it writes a different subset of tables
with different extras, so routing it through the shared function would fit worse than the
duplication it removes.

Tests

Four consistency tests assert the CLI and GUI paths produce:

  • the same set of filenames in the results directory;
  • frame-equal events / per_fly / per_condition / comparisons;
  • byte-identical config.used.yaml and matching config_hash;
  • run_info.json differing only in command and timestamp.

Plus one covering the flypad stats consequence, and one in test_gui.py for the real
GUI entry point.

They live in test_cli.py rather than test_gui.py on purpose: test_gui.py skips
wholesale without qtpy, which CI does not install, so consistency tests there would never
run on the runner. They exercise run_experiment directly — exactly what
run_pipeline_job delegates to.

Verified they have teeth by neutering the write_provenance call again: five of them
fail, including three of the consistency ones.

  • 246 tests pass locally (with the gui extra).
  • Reproducing the runner's environment (UV_PROJECT_ENVIRONMENT + uv sync --frozen):
    ruff, ruff format, mypy and pytest all pass — 232 passed, test_gui skipped for the
    absent qtpy.

Docs

docs/tutorial.md §5 now states that the GUI and flypad run produce identical results
directories and what the command field means. CHANGELOG.md gains the entry under
[Unreleased].

No placeholders in this one — Closes #1 is literal, so it's ready to use as-is. The # placeholder is only in the issue comment draft.

GUI runs left no run_info.json or config.used.yaml. The cause was duplication,
not a broken function: cli.run() and gui.workers.run_pipeline_job() were
near-identical copies of detect -> tables -> figures -> write, and only the CLI
copy went on to call write_provenance().

That also meant `flypad stats` silently degraded on GUI-produced directories.
It reuses the run's config.used.yaml when present and falls back to a
default-constructed Config() when absent, so it recomputed per_condition and
comparisons with default ci_level / statistic / facet_by and overwrote those
tables with numbers that need not match what the GUI displayed.

Both entry points now call pipeline.run_experiment(), which owns the sequence
including provenance; run_info.json records which drove it in `command`
("run" / "gui"). gui.JobResult is now an alias of pipeline.ExperimentResult, so
the GUI keeps its vocabulary while sharing the shape.

Tested for CLI/GUI consistency: the two produce the same file set, byte-identical
config.used.yaml, frame-equal tables, and run_info.json differing only in
`command` and `timestamp`. Those live in test_cli.py rather than test_gui.py
because test_gui.py skips wholesale without qtpy, which CI does not install —
they exercise run_experiment directly, which is what run_pipeline_job delegates
to. Removing the provenance call again fails five of them.
@degoldschmidt
degoldschmidt merged commit 8aeb17c into main Aug 21, 2026
4 checks passed
@degoldschmidt
degoldschmidt deleted the fix/issue-1-gui-provenance branch August 21, 2026 12:42
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.

running analysis from gui does not create config.used.yaml and run_info.json

1 participant