GUI hub#
Run the unified PhenoTypic web interface — pipeline builder, results viewer, and run console — under a single URL from any machine, including HPC clusters reached over SSH.
What the hub is#
The hub is one Dash application that mounts three tools under sub-paths of a single server:
Path |
Tool |
|---|---|
|
Home — sandbox tree, capability badges, tab navigation |
|
Pipeline builder (node-graph editor) |
|
Results viewer (OpenSeadragon overlays, measurement tables) |
|
Run console (submit local or SLURM jobs, tail logs, view dashboard) |
All four pages share a top-bar tab strip and a sidebar showing the sandbox
directory tree. The server is single-user and frozen-at-launch: the file
browser can only see the directory you pass via --root.
Launching the hub#
Two equivalent entry points boot the same server:
uv run phenotypic-gui --root ./images --port 8050
uv run python -m phenotypic.gui --root ./images --port 8050
Options:
Flag |
Default |
Effect |
|---|---|---|
|
current working directory |
Freezes the sandbox the file browser is allowed to see |
|
|
Interface to bind; loopback-only by default |
|
|
TCP port |
|
|
Browser-visible path prefix for path-stripping proxies |
|
off |
Enable Dash auto-reload and verbose tracebacks |
The startup banner printed to stdout repeats the URL and the SSH-tunnel command for the bound port.
Open OnDemand path prefix#
Open OnDemand proxies apps through a browser path such as
/node/<node>/<port>/ and forwards stripped paths to the local server. Pass
that path, not the full URL, as --url-prefix:
uv run phenotypic-gui \
--root /rhome/ejaco020 \
--host 0.0.0.0 \
--port 30099 \
--url-prefix /node/hz01/30099/
Then open the full proxy URL in your browser:
https://ondemand.hpcc.ucr.edu/node/hz01/30099/
The app does not infer node names, ports, or OOD domains. The default prefix is
/, so local launches keep the usual /builder/, /results/, and /run/
paths.
Warning
phenotypic gui (without a hyphen) is not supported.
The existing phenotypic CLI is reserved for batch pipeline execution with
explicit path options, not subcommands. Always use the hyphenated form
phenotypic-gui or the module form python -m phenotypic.gui. Typing
uv run phenotypic gui will fail because gui is an unexpected positional
argument.
The four pages#
Home (/)#
The landing page. The sidebar shows the sandbox directory tree; each entry carries zero or more capability badges:
Badge |
Meaning |
|---|---|
|
Directory contains image files |
|
File is a PhenoTypic pipeline JSON |
|
Directory is a CLI output root ( |
|
Directory could not be listed due to a permission error |
Hidden files (names starting with .) and symlinks whose targets fall outside
the sandbox root are hidden by default. Two toggles in the sidebar header
surface them. The Refresh button flushes the classifier’s LRU cache and
re-classifies all visible nodes — use it after dropping new files into a
directory.
Builder (/builder/)#
The existing node-graph pipeline builder, mounted under the hub prefix. Use it
to compose ImagePipeline objects, set per-operation parameters, and export
pipeline.json. The builder’s file picker is scoped to the same sandbox root
as the rest of the hub.
Results viewer (/results/)#
The existing results viewer, accessible from the hub without a separate launch.
The viewer starts with an empty-state landing page; click a directory with the
out badge in the sidebar to load it.
Memory note. The viewer loads the master measurements parquet and per-image overlays into memory on first access. Navigating away and releasing the viewer frees the Python object graph, but process RSS may not return to the OS — the CPython allocator pools freed pages rather than returning them immediately. Subsequent access re-loads the data from disk.
Run console (/run/)#
Pick a pipeline JSON, an input directory, and an output directory from the sidebar or the inline file pickers. Toggle Local or SLURM mode, then click Validate (dry-run) or Run.
Validate runs the CLI with --dry-run: it validates the pipeline and lists
the images that would be processed without writing any output. The log tail
shows the result.
Run (Local) spawns python -m phenotypic ... as a subprocess of the GUI
process. See CLI Batch Processing for the full list
of CLI flags that the form exposes. Only one local run can be active at a time;
the Run button is disabled while a local subprocess is live. SLURM submissions
have no equivalent cap.
Run (SLURM) shells out to the CLI’s SLURM path. See
SLURM Pipelines. The submission runs on a background
thread so the UI stays responsive. Once progress/job_metadata.json is
written, the array primary job ID surfaces as slurm-<id> in the Recent Runs
panel.
The Recent Runs panel lists runs from the current session and historical runs rehydrated from the sandbox at boot time. Click a row to re-point the iframe at that run’s dashboard.
Log tail and iframe dashboard#
Once a local run starts, stdout (and stderr, which is merged into stdout) is
teed to <output_dir>/.gui_log/stdout.log and to a 5000-line in-memory ring
buffer. The log-tail panel polls the ring buffer on a short interval.
Once dashboard.html lands in the output directory’s deliverables/
subfolder, the iframe panel points at
/runs/<rel>/deliverables/dashboard.html. The /runs/ route is registered on
the hub’s Flask server directly — not under the dispatcher middleware — so the
URL works regardless of which tab is active. The iframed dashboard polls its own
progress files using relative URLs, which the /runs/ route resolves inside the
sandbox.
Cancel sends SIGTERM to the subprocess and waits up to 10 seconds. If the
subprocess does not exit within that window, SIGKILL is sent. The GUI also
registers an atexit hook so closing the hub with Ctrl-C propagates a SIGTERM
to any live subprocesses before the process exits.
SSH-tunnel pattern#
When the hub runs on an HPC cluster, forward the port to your workstation in a separate terminal before opening the browser:
ssh -L 8050:localhost:8050 <user>@<cluster>
Then open http://localhost:8050/ locally. The default --host 127.0.0.1
binding means the server only accepts connections from localhost, so the
tunnel is the only path in from outside the cluster.
Running the GUI on a Slurm compute node#
If you launch the GUI from inside a srun / salloc allocation, the server is
bound to localhost on the compute node, not on the login node you SSH’d
into. A single-hop tunnel to the login node will return
channel N: open failed: connect failed: Connection refused because nothing is
listening on the login node’s port. You need a two-hop tunnel.
Pick the compute node hostname (visible in your shell prompt after the
allocation lands, e.g. <user>@gpu12:~$) and use one of the following:
Option 1 — ProxyJump from your workstation (simplest, single command):
ssh -J <user>@<cluster> -L 8050:localhost:8050 <user>@<compute-node>
This requires that the login node can SSH to the compute node without an extra password; that is the default on most Slurm clusters while you have an active allocation.
Option 2 — Nested SSH (works even if direct SSH to the compute node is blocked, as long as the login node permits forwarding):
ssh -L 8050:localhost:8050 <user>@<cluster> \
ssh -L 8050:localhost:8050 -N <compute-node>
Option 3 — Two terminals (useful when you want to keep the allocation shell interactive):
Terminal A — start the GUI on the compute node:
ssh <user>@<cluster> srun -A <account> -p <partition> -t 4:00:00 --pty -c 8 --mem=32g bash -l uv run python -m phenotypic.gui --root <project-dir> --port 8050
Terminal B — open the two-hop tunnel from your workstation:
ssh -J <user>@<cluster> -L 8050:localhost:8050 -N <user>@<compute-node>
In all three options, open http://localhost:8050/ on your workstation once
the GUI logs Dash is running on http://127.0.0.1:8050/.
To confirm the diagnosis if a tunnel keeps failing: from the login node,
run curl http://localhost:8050/. It will fail (the GUI is on the compute
node). From the compute node, the same curl succeeds. That mismatch is
the symptom that you need a two-hop tunnel rather than a single-hop one.
Cloud deployment#
Cloud deployment (multi-user, per-session sandbox roots, authentication) is a
non-goal in v1. The hub is single-user with no auth gate. A
TODO(cloud-deploy) in src/phenotypic/gui/shell/_sandbox.py marks the
hook point for a future auth layer.
Do not expose the hub on 0.0.0.0 without authentication — there is nothing
stopping another user from reading files or submitting jobs through the
unguarded API.
Standalone tools#
Each component can be launched independently for debugging:
# Pipeline builder only
uv run python -m phenotypic.gui.builder --image-root ./images
# Results viewer only
uv run python -m phenotypic.gui.results_viewer --output-root ./out
# Run console only
uv run python -m phenotypic.gui.run_console --root ./images
All three accept --host, --port, and --debug with the same defaults as
the hub launcher.