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

/builder/

Pipeline builder (node-graph editor)

/results/

Results viewer (OpenSeadragon overlays, measurement tables)

/run/

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

--root PATH

current working directory

Freezes the sandbox the file browser is allowed to see

--host ADDR

127.0.0.1

Interface to bind; loopback-only by default

--port N

8050

TCP port

--url-prefix PREFIX

/

Browser-visible path prefix for path-stripping proxies

--debug

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

img

Directory contains image files

cfg

File is a PhenoTypic pipeline JSON

out

Directory is a CLI output root (deliverables/master_measurements.parquet + results/)

?

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):

  1. 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
    
  2. 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.