Plan for a graphical way to run MuMDIA. Written 2026-08-28 from a survey of eight comparable tools, a measured audit of what the engine can already support, and the group's actual deployment (analyses run on a shared compute node reached through a jump host).
The recommendation is a web UI served by the engine itself (mumdia serve),
with each run executed as a detached child process and its state kept on disk. The
reasoning below matters more than the conclusion, because two of the four
candidates are eliminated by a constraint that is easy to miss and expensive to
discover late.
Eight tools were examined: DIA-NN, FragPipe, AlphaDIA, AlphaPept, MaxQuant, Skyline, PeptideShaker, DeepLC.
| toolkit | compute | shared params file | binds a port | cancel | survives GUI exit | |
|---|---|---|---|---|---|---|
| DIA-NN | C# WinForms | child process | --cfg, only if args > 32k |
no | yes | no ("progress will be lost") |
| FragPipe | Java Swing | child processes (DAG) | .workflow + .fp-manifest |
no | yes | no; use --headless |
| AlphaDIA | Electron + React | child process | config.yaml |
no | yes | no, no reattach |
| AlphaPept | Streamlit | detached process | settings YAML | 8505 | not located | yes |
| MaxQuant | C# WinForms | same process | mqpar.xml |
no | not documented | no |
| Skyline | C# WinForms | same process | flags | no | unconfirmed | no |
| PeptideShaker | Java Swing | same JVM | params file | no | yes | no |
| DeepLC | NiceGUI | same process | none | 8080 | no | no |
Four conclusions follow, and three of them are about behaviour rather than appearance.
A GUI is expected to be a launcher over the command line, not a separate application. Six of eight keep the CLI as the real interface and five build or write the exact invocation. DIA-NN's own README advertises that its log "prints the exact set of commands it used". This is the shape users can debug, script and publish, and it is the shape to copy.
Progress is expected to be a log tail. Not one of the eight shows a calibrated per-stage percentage or an ETA for the search itself. AlphaPept's coarse markers and FragPipe's batch bar are the only progress bars in the set. Modest instrumentation is therefore differentiating rather than table stakes.
Job lifetime is the discriminating requirement, and almost everyone gets it wrong. Seven of eight tie the run to the UI process. Only AlphaPept decouples them, and it does so with three cheap primitives: a queue directory, a PID file and a log file. For an 85-minute run started from a laptop that will sleep, a GUI that loses the run when it closes is not usable, and no toolkit choice fixes that.
Nobody has solved a local GUI driving remote compute. In eight tools there is not one such path. The documented answers are "run it all remotely from the CLI", a container, or X forwarding, which is FragPipe's official answer and the worst option on a slow link. We should not expect to invent this cheaply.
Measured crate counts, resolved with default features into an empty crate:
| approach | crates (Windows) | crates (linux-gnu) | system requirements |
|---|---|---|---|
tiny_http |
6 | 6 | none |
axum |
50 | 49 | none of consequence |
eframe (egui) |
173 | 263 | pkg-config at build; libX11, libxcb, libGL, libEGL, libwayland at run time |
tauri |
236 | 278 | GTK3 + WebKit2GTK via -sys; Xcode on macOS; MSVC plus WebView2 on Windows |
The workspace has deliberately avoided C toolchains: parquet uses snap rather
than zlib-ng "needs cmake", mzdata uses miniz_oxide for the same reason,
mimalloc was chosen partly for being pure Rust. The only C-toolchain crate today
is cc, as a build dependency of blake3 and libmimalloc-sys. ci.yml contains
no apt-get line at all, and the only system package in release.yml is
musl-tools.
The Linux release target is a fully static musl binary, and both desktop
toolkits are incompatible with it. They dynamically link system graphics or
webview libraries and, in eframe's case, dlopen a Vulkan loader, which a
statically linked binary cannot do. Choosing either means giving up the
single-file Linux artifact and adding system-package steps to two workflows.
Neither can run in the shipped container, which has no display.
That removes eframe and tauri. The choice is between a web UI inside the
binary and a separate Python application.
Launcher: a web UI served by the engine, mumdia serve, using tiny_http.
- Six crates, no system dependency on any platform, builds for musl, ships inside
the existing binary and release archive. The container needs one
EXPOSEline. - Reached over SSH the way this group already works: bind loopback,
ssh -L 8080:localhost:8080 doxy, open the page. Both CompOmics precedents already do exactly this (DeepLC's[web]extra on 8080, AlphaPept on 8505). - The server owns the runs, so a closed browser, a sleeping laptop and a dropped tunnel are all survivable by construction.
tiny_httprather thanaxumbecause the UI is single-user and local: blocking request handling is adequate, and it avoids pulling tokio and hyper into a binary that currently has no async runtime.
Results viewer: separate, later, and probably Python. A launcher needs no
Parquet reader, because everything it must show is already on disk as text or JSON
(section 4). A viewer that draws chromatograms and annotated spectra needs lazy
access to a 4.53 GB, 27-million-row chromatograms.parquet, and the group has
already specified that work in Python with Plotly and polars or duckdb. Keeping
the two apart lets the launcher ship in the engine and the viewer follow in the
ecosystem where DeepLC already lives. Revisit only if the viewer turns out to need
nothing but tables.
Runs execute as detached child mumdia run processes, never on a server
thread. Three independent reasons:
rayon::ThreadPoolBuilder::build_global()can be called once per process, and--threadsalso sets process-globalMUMDIA_NN_THREADSandOMP_NUM_THREADS. A server hosting many runs on threads would freeze one thread budget at startup for every run.- The sidecar workers inherit stdout deliberately, so a child process with a piped stdout captures the engine's own lines and the Python worker's per-fold progress in one stream, attributable to that run. On a server thread the worker's output would land on the server's stdout, belonging to nobody.
- A crashing or cancelled run cannot take the server with it.
Verified against the current tree, because the honest starting point matters.
Usable today, with no engine change:
- A 21-step progress bar for free. Every stage writes
<artifact>.report.jsonon completion, at 17 call sites, carryingstage,rows,elapsed_ms,content_hashand astatsmap. Polling the output directory yields real per-stage completion with row counts. This is the single most valuable existing hook. - The results summary needs no Parquet reader.
psms_scored.parquet.report.jsonalready carriespsms,classifier,target_psms_at_1pct,target_peptides_at_1pct,target_protein_groups_at_1pct,target_precursors_at_1pct, and records the classifier that actually ran against the one requested.peptides.tsvandproteins.tsvgive the tables. - Provenance for a results page.
manifest.jsoncarries the engine version, the short commit with a-dirtymarker, the commit date, the full command line, and a blake3 hash of every input. It is written once at the end, so it is a completion record, not a status file. - A reliable setup check.
mumdia doctorcomputes per-role interpreter path, how it was resolved, missing modules and package versions, and its exit code is trustworthy. - Per-run progress in an experiment.
run-experimentlogsrun = <name>, i, n, so "run 3 of 8" is available. - A resume shortcut. All 21 subcommands are standalone on path-addressable
inputs. Cancelling at minute 70 discards the rescore but leaves a valid
psms_competed.parquet, andmumdia rescore --competed ...restarts from there. That is worth surfacing as a button.
Missing, and needed:
| # | gap | why the GUI needs it | size |
|---|---|---|---|
| 1 | No incremental progress in the stage that dominates the run. Rescore is 80% of an 85-minute run and emits one line at its start and one at its end. The only live signal in between is roughly one unstructured Python print every two to four minutes. | Without this the GUI shows an elapsed clock and nothing else for most of the run. | medium |
| 2 | No machine-readable log. tracing-subscriber is built without its json feature. Worse, the human format writes to stdout with ANSI escapes even when piped, wrapping each field name and value separately, and println! output interleaves on the same stream. A naive key=value parser cannot work. |
A GUI parsing stdout needs --log-format json and --no-color. |
small |
| 3 | No doctor --json. |
The setup screen is the highest-value screen, since misconfigured Python environments are the failure every surveyed tool documents at length. All the fields already exist; only the output format is missing. | small |
| 4 | No signal handling anywhere. No ctrlc, no SIGINT/SIGTERM handler, no cancellation flag. Ctrl-C kills the process wherever it is. |
A Stop button, which four of eight surveyed tools have. | small |
| 5 | Artifact writes are not atomic. File::create targets the final path with no temp-then-rename, so an interrupted run leaves a truncated Parquet at the canonical name. It fails to open rather than reading as valid, so it is detectably broken, but it is still rubble in the output directory. |
Cancellation has to leave a clean directory. Worth fixing regardless of the GUI. | small |
| 6 | No per-run state on disk. | Reattaching after a server restart, and listing what ran. AlphaPept's queue-directory plus PID-file plus log-file design is the proven minimum. | medium |
Items 2, 3, 4 and 5 are worth doing for the command line alone, which is the argument for doing them first.
Three screens. Deliberately not a configuration editor: there are 160 leaf configuration fields across 17 structs, and the three shipped profiles set only 4, 12 and 12 of them respectively.
Setup. Pick input mzML files from the server's filesystem (the data lives on
the compute node, so this is a server-side browser, not an upload). Pick a library
source: a FASTA, or the precursor and fragment pair. Pick one of the three shipped
profiles. Set the output directory and --threads. A "check environment" button
runs doctor and renders one row per sidecar role.
Run. The 21-step stage list with per-stage elapsed time and row counts from
the report.json files, a live log tail, and a Stop button. When a run is stopped
or fails after competition, offer the rescore --competed restart.
Results. The counts from the scored artifact's report, the provenance stamp,
a preview of peptides.tsv and proteins.tsv, and links to the files.
Explicitly out of scope for the first version: a general configuration editor (offer a JSON text area for advanced users instead), plots, vendor-format conversion, multi-user accounts, and file upload.
Two traps to avoid, both documented in this repository. Do not offer
--top-peaks-ms2 as a general control: carrying the AIF-specific value 300 to a
50-window run cost 60% of the peptides, and a slider would manufacture that
failure. And do not label a peptides.tsv row count as a precursor count at a
precursor q threshold: the rows are precursors but the filter column is a
base-peptide q.
A server that accepts a file path and starts a process is a remote-code-execution
surface. On a shared compute node, loopback is not a boundary: every other user
on that host can reach 127.0.0.1. A config field names the Python interpreter to
execute, so an unauthenticated request can run an arbitrary program as the
operator.
Requirements, not options:
- bind
127.0.0.1by default, and require an explicit flag to bind anything else; - print a URL containing a random per-process token, and require it on every request, the way Jupyter does. This is the single control that makes a shared compute node safe;
- reject paths outside an allowed root, so the file browser cannot be walked to arbitrary locations;
- never accept an interpreter path from the request; take it from the config or
from
"auto"resolution only.
Note as a live trap rather than a hypothetical: NiceGUI's non-native default binds
0.0.0.0, so the DeepLC precedent would expose the service to the network if
copied without thought.
Phase 0, useful on its own, no GUI (small). --log-format json and
--no-color; doctor --json; a SIGINT/SIGTERM handler that stops cleanly;
temp-then-rename for artifact writes. This is a robustness and scriptability
improvement whether or not the GUI is ever built, and it is the prerequisite for
everything else.
Phase 1, incremental progress (medium). A progress event per unit of work in the loops that dominate: extract over candidates, features over rows, and above all an iteration counter in the NN rescore worker, which is 80% of a run. Emit it on the structured log so both the CLI and the GUI can consume it.
Phase 2, mumdia serve (medium). tiny_http, one embedded page, no build
step and no npm. Runs as detached children with per-run state on disk. The three
screens of section 5, with the token and path-root controls of section 6.
Phase 3, results viewer (separate project). Python, lazy Parquet, per the existing specification.
Phase 0 should not wait for a decision about the GUI. Phase 2 should not start before Phase 1, because a launcher that cannot show progress through the stage that takes 80% of the run will be judged by exactly that.
The survey found no comparable tool that solves remote GUI over remote compute, and this plan does not solve it either: it sidesteps it by putting the UI on the compute node and reaching it through the tunnel the group already uses. If the requirement ever becomes a desktop application on a laptop driving a remote engine, the work is mostly SSH plumbing (agent-held passphrase keys, a jump host, correct detachment, and reattachment across GUI restarts), not user interface, and reattachment is precisely the part every surveyed tool skips.