spacr.qt.screens.hyperparam

Hyperparameter search panel — the Live-Preview-shaped window for sweeps.

Structurally this is the sibling of spacr.qt.widgets.live_preview: a self-contained QWidget that owns its own controls, runs the expensive work on a QThread, streams results back over signals, reports every failure inline (never in a modal — a QMessageBox hangs a headless run), and pushes a chosen configuration back into the host screen’s settings panel through a callback the host registers. Host screens embed it with build_hyperparam_card(), exactly the way the Mask screen embeds the live preview, and toggle it with a label reading “Hyperparameter search” where the Mask screen’s says “Live”.

What it deliberately does not do is announce a winner and stop talking. The table is ordered by the criterion the user picked, but the panel also draws the small-multiples panel (embeddings for UMAP, score-versus-trial with the noise band for the classifiers) and prints the spread, the within-noise flag and the failure count, because those are the parts that say whether the winner means anything. See spacr.hyperparam for why.

Classes

HyperparamPanel

Search-space controls, a live results table and a small-multiples panel.

SearchRequest

Everything the worker needs for one sweep.

UmapSearchSettingsDialog

Tabbed search and UMAP-graph settings for HyperparamPanel.

WalkAxesDialog

Choose which parameters a Walk searches, and how finely.

Functions

build_hyperparam_card(host, *[, panel_later])

Build the Hyperparameter search card + panel pair.

build_panel_figure(result[, max_panels, palette])

Draw the small-multiples panel for a finished (or partial) sweep.

criteria_disagree(→ Optional[str])

Say plainly when re-ranking by another criterion picks another winner.

figure_to_pixmap(→ PySide6.QtGui.QPixmap)

Rasterise a matplotlib figure into a QPixmap without touching disk.

format_params(→ str)

Render a configuration as k=v, k=v in sorted-key order.

format_scores() → str)

Render every criterion a trial recorded, one per line.

parse_values(→ List[Any])

Parse a comma-separated list of values for one parameter.

render_trial_figure(→ bool)

Draw ONE trial's embedding and write it to png_path.

searchable(→ bool)

Whether a hyperparameter search exists for app_key.

Module Contents

class spacr.qt.screens.hyperparam.HyperparamPanel(app_key: str = 'umap', parent=None)[source]

Bases: PySide6.QtWidgets.QWidget

Search-space controls, a live results table and a small-multiples panel.

Parameters:
  • app_key – searchable application: 'umap', 'classify', 'classify_merged', 'ml_analyze' or 'activation'.

  • parent – optional Qt parent.

Raises:

ValueError – if app_key has no hyperparameter-search definition.

Variables:

search_finished – emitted with the SearchResult when a sweep ends (including a stopped, partial one).

Build the controls, table and preview for app_key.

adaptive_parameters() → Tuple[int, int, float, float][source]

Parse adaptive increments, rounds and convergence threshold.

apply_selected() → bool[source]

Push the selected configuration into the host’s settings panel.

Returns:

True when a configuration was handed to the callback.

apply_settings(settings: Dict[str, Any]) → None[source]

Adopt the host screen’s current settings as the search’s base.

Any searched parameter that already has a value in settings and no list in its field is seeded with that single value, so a search always includes what the user has configured.

Parameters:

settings – the host screen’s settings dict.

closeEvent(event)[source]

Stop a running sweep before the widget is torn down.

Destroying a QWidget whose QThread is still running aborts the process, which is exactly how the headless test suite would die.

Parameters:

event – the close event, passed on to the base class after a running sweep worker is asked to stop and waited for up to 3 s.

cluster_selected() → bool[source]

Cluster the selected map once, without changing its coordinates.

cluster_walk_sizes() → Tuple[int, ...][source]

HDBSCAN scales around the visible starting value.

current_adaptive_space() → spacr.hyperparam.SearchSpace[source]

Return one Walk starting centre, using settings defaults if blank.

A Walk is a path from ONE point, so every parameter here carries a single value. The three base fields give the centre; on top of them every axis chosen in the Axes dialog contributes its own starting value, because a searched parameter the centre does not carry has nothing to walk away from and the engine refuses the whole run.

current_space() → spacr.hyperparam.SearchSpace[source]

Build the SearchSpace from the fields.

Returns:

the space.

Raises:

ValueError – with a message meant to be shown inline when a field holds a value of the wrong type or nothing is filled in.

gpu_backend() → str[source]

Which backend the next search will use: 'cuml' or 'cpu'.

open_settings() → None[source]

Open or focus the tabbed search/module settings window.

open_umap_grid() → spacr.qt.widgets.umap_search_viewer.UmapGalleryDialog | None[source]

Spawn the black-background wall of every embedding in the table.

open_walk_axes() → WalkAxesDialog | None[source]

Open the axis picker. Returns the dialog, or None when not UMAP.

request_gpu_enabled(checked: bool, *, anchor=None) → bool[source]

Set whether the next search uses the cuML GPU backend.

When cuML is unavailable, the toggle remains off and the shared availability panel explains the compatible installation options. Any installation offered by that panel takes effect after spaCR restarts.

Parameters:
  • checked – Request GPU execution when True or CPU execution when False.

  • anchor – Optional widget beside which to show the availability panel.

Returns:

bool – The effective GPU-enabled state after checking availability.

Validate the space and start the sweep in the background.

Returns:

True when a sweep was started; False when validation failed (the reason is on the status label — never in a dialog).

selected_params() → Dict[str, Any] | None[source]

The configuration on the selected row, or the best one.

Returns:

the parameter dict, or None when nothing is selectable.

selected_trial() → spacr.hyperparam.Trial | None[source]

The real trial object attached to the selected table row.

set_apply_callback(cb: Callable[[Dict[str, Any]], Any] | None) → None[source]

Register the callback that writes a chosen config into the settings panel. Mirrors LivePreviewPanel.set_propagate_callback.

Parameters:

cb – called with the parameter dict when the user hits Apply.

set_search_fn(fn) → None[source]

Override the search backend.

Parameters:

fn – fn(request, on_trial, should_stop) -> SearchResult.

set_settings_provider(provider: Callable[[], Dict[str, Any]] | None) → None[source]

Register a callback returning the host’s current module settings.

The main settings form remains editable while this panel or its popup is open. Reading it immediately before each search prevents a source path dropped after the panel opened from being lost in a stale snapshot.

Parameters:

provider – zero-argument callback returning a settings mapping.

set_walk_axes(axes: Mapping[str, Mapping[str, Any]]) → None[source]

Replace the chosen Walk axes.

Parameters:

axes – parameter name to {'start': ..., 'resolution': ...}; names outside UMAP_WALK_PARAMETERS raise ValueError, and resolution is raised to at least 2.

show_trial(trial: spacr.hyperparam.Trial) → bool[source]

Load one row’s stored coordinates into the native 2-D/3-D view.

Parameters:

trial – the trial whose stored embedding (and optional cluster_labels, backend and n_components) in extra_metrics is shown; returns False when it has no embedding.

Ask the running sweep to stop; the result comes back partial.

walk_axes() → Dict[str, Dict[str, Any]][source]

The chosen Walk axes, {name: {'start', 'resolution'}}.

Empty means the Walk uses the two parameters it always used, which is what an untouched panel should do.

walk_selected_clusters() → bool[source]

Search HDBSCAN scales on the selected map and display the best.

walk_start_for(name: str) → str[source]

Where the Walk should start on name, as text for a field.

The main search field wins when it holds one value, then the run’s settings, then UMAP’s own default. A starting point the user can see somewhere else in the panel is the one they expect.

Parameters:

name – the UMAP parameter name.

property result: spacr.hyperparam.SearchResult | None[source]

The most recent SearchResult, if any.

class spacr.qt.screens.hyperparam.SearchRequest[source]

Everything the worker needs for one sweep.

Kept as a plain dataclass so tests can construct one directly, exactly the way spacr.qt.widgets.live_preview.PreviewRequest is used.

class spacr.qt.screens.hyperparam.UmapSearchSettingsDialog(panel: HyperparamPanel)[source]

Bases: PySide6.QtWidgets.QDialog

Tabbed search and UMAP-graph settings for HyperparamPanel.

Parameters:

panel – the panel these settings belong to. It is also the dialog’s parent, and the widgets the dialog lays out belong to it.

Build the settings popup around the panel’s own search controls.

The panel’s settings group is re-parented in here, and its embedded Run, Stop and Propagate buttons are hidden: they date from when the panel was the whole window, and leaving them would put two Runs and two Propagates on screen. The dialog carries its own stylesheet rather than touching the application palette.

Parameters:

panel – the hyperparameter panel this edits; its app_key decides the title and whether the UMAP tabs are built.

closeEvent(event)[source]

Remember the dialog’s geometry before it goes.

Parameters:

event – the Qt close event.

propagate_settings() → None[source]

Push the tuned settings back to the screen that opened this.

class spacr.qt.screens.hyperparam.WalkAxesDialog(panel: HyperparamPanel)[source]

Bases: PySide6.QtWidgets.QDialog

Choose which parameters a Walk searches, and how finely.

One row per structural UMAP parameter: whether it takes part, where the walk starts on it, and the axis resolution – how many values that axis contributes to each round. 2 is the classic pair either side of the centre; 3 includes the centre; 5 reaches two steps out.

The round cost is the PRODUCT of the resolutions, which is why the dialog shows that number and says when it will trip the per-round limit. Ten axes at resolution 2 is 1024 fits to take one step, and a user who has to discover that by waiting has been failed by the control, not by the search.

Parameters:

panel – the panel whose axes this walks. Also the dialog’s parent.

Build the dialog that chooses which parameters the Walk searches.

One row per UMAP parameter, each with a switch, a starting value and a resolution – every one of them changes the structure of the embedding, so a search restricted to n_neighbors and min_dist leaves the rest at a default nobody chose.

Parameters:

panel – the hyperparameter panel whose walk axes this edits.

selection() → Dict[str, Dict[str, Any]][source]

The chosen axes as {name: {'start': str, 'resolution': int}}.

spacr.qt.screens.hyperparam.build_hyperparam_card(host, *, panel_later: bool = False)[source]

Build the Hyperparameter search card + panel pair.

Mirrors spacr.qt.screens.app_screen._build_live_preview_card: it returns the pair without adding it to any layout, so the host screen can put it in whatever splitter it likes and start it hidden behind the toggle.

Parameters:
  • host – the AppScreen asking for the card; its app_key selects the parameter set.

  • panel_later – return None for the panel and leave the card’s body empty. The screen fills it with _fill_hyperparam_card() when the card is first shown, so a module whose search nobody opens does not build and polish its ~130 widgets at open.

Returns:

(panel, card).

spacr.qt.screens.hyperparam.build_panel_figure(result: spacr.hyperparam.SearchResult, max_panels: int = MAX_PANELS, palette: Dict[str, str] | None = None)[source]

Draw the small-multiples panel for a finished (or partial) sweep.

For a UMAP sweep this is one scatter per trial — the deliverable, because the scores cannot tell you an embedding is right. For a classifier sweep there is no embedding to draw, so it is score-versus-trial with the noise band shaded: every configuration inside the band is indistinguishable from the winner.

Parameters:
  • result – the sweep to draw.

  • max_panels – cap on the number of embedding panels.

Returns:

a matplotlib Figure, or None when there is nothing to draw.

spacr.qt.screens.hyperparam.criteria_disagree(result: spacr.hyperparam.SearchResult, criteria: Sequence[str]) → str | None[source]

Say plainly when re-ranking by another criterion picks another winner.

Ranking attribution methods has no ground truth, so the useful output is not the top row but whether the top row survives a change of criterion. When it does not, that is the result and it goes on the status line.

Parameters:
  • result – the finished sweep.

  • criteria – the criteria to re-rank by.

Returns:

the sentence, or None when every criterion agrees (or there is not enough recorded to tell).

spacr.qt.screens.hyperparam.figure_to_pixmap(fig) → PySide6.QtGui.QPixmap[source]

Rasterise a matplotlib figure into a QPixmap without touching disk.

Parameters:

fig – the matplotlib Figure.

Returns:

the pixmap (null if rendering failed).

spacr.qt.screens.hyperparam.format_params(params: Dict[str, Any]) → str[source]

Render a configuration as k=v, k=v in sorted-key order.

Parameters:

params – parameter name to value.

spacr.qt.screens.hyperparam.format_scores(trial: spacr.hyperparam.Trial, keys: Sequence[str] = ()) → str[source]

Render every criterion a trial recorded, one per line.

An Activation sweep computes four criteria for every trial precisely because they disagree, so the row that shows only the ranked one is hiding the finding. The panel puts this on the row’s tooltip and in the figure titles.

Parameters:
  • trial – the trial to describe.

  • keys – criteria to show first, in order; anything else the trial recorded that is a plain number follows.

Returns:

the multi-line text (empty when the trial recorded nothing).

spacr.qt.screens.hyperparam.parse_values(text: str, kind: str, name: str) → List[Any][source]

Parse a comma-separated list of values for one parameter.

Parameters:
  • text – the raw field contents, e.g. "5, 15, 50".

  • kind – 'int', 'float' or 'str'.

  • name – parameter name, used in the error message.

Returns:

the parsed values, in the order given.

Raises:

ValueError – with a sentence the panel shows inline when a value does not match kind.

spacr.qt.screens.hyperparam.render_trial_figure(trial: spacr.hyperparam.Trial, metric: str, png_path: str) → bool[source]

Draw ONE trial’s embedding and write it to png_path.

Pure matplotlib, no Qt, so this is safe to call from a worker thread – which is the whole point. Fifty embeddings drawn on the GUI thread is a fifty-second freeze, and the search is the one place where the user most wants to keep interacting (to stop it).

Returns False when the trial has no embedding to draw: a classifier sweep has none, and a failed UMAP trial has none either. The caller treats that as “no figure for this cell” rather than as an error.

Parameters:
  • trial – the trial to draw; needs an embedding in extra_metrics and a score.

  • metric – the metric name shown with the score in the figure title.

  • png_path – where the PNG is written.

spacr.qt.screens.hyperparam.searchable(app_key: str) → bool[source]

Whether a hyperparameter search exists for app_key.

Parameters:

app_key – the app key looked up in APP_PARAMS.

Nested helpers

HyperparamPanel.adaptive_parameters._number(edit, default, cast, label)

One field as a number, or the default when it is blank.

spacr/qt/screens/hyperparam.py:1427

_complete_metrics_when_opened.show_popup(self)

Fill the metric list the first time the popup opens.

Deferred because the list comes from umap, and importing it at build time would pay for it on every screen that never opens this combo.

spacr/qt/screens/hyperparam.py:2734