spacr.qt.screens.train_compare¶
Training Runs — several runs’ curves on one axis, with the settings diffed.
The question this screen exists to answer is “why is run B better than run A”, which today is answered by opening two folders of PDFs in one window, two settings CSVs in another, and holding the difference in your head.
Layout:
┌──────────────────────────────────────────────────────────────────────┐
│ /data/screen1/model [Choose folder…] [Scan] │
├──────────────────────┬───────────────────────────────────────────────┤
│ Runs found (4) │ ┌───────────────────────────┐ │
│ ☑ maxvit_t/…/ep_25 │ │ accuracy, 5 series │ │
│ 25 epochs · tr+val │ │ ╱‾‾‾‾ B val │ │
│ ☑ maxvit_t/…/ep_10 │ │ ╱ ─ ─ A val │ │
│ ☐ resnet50/…/ep_8 │ └───────────────────────────┘ │
│ 8 ep x 3 folds ├───────────────────────────────────────────────┤
│ ☐ maxvit_t/…/ep_3 ! │ 2 settings changed · 1 env drift · 0 drift │
│ │ bucket setting A B │
│ Metric [accuracy ▾] │ changed learning_rate 1e-4 1e-3 │
│ Folds [per fold ▾] │ changed batch_size 64 32 │
│ [Overlay selected] │ env n_jobs 30 8 │
├──────────────────────┴───────────────────────────────────────────────┤
│ ! maxvit_t/…/epochs_3: no per-epoch curves in this folder │
│ Clicked: maxvit_t/…/epochs_25 · val — best 0.87 @ 18, last 0.85 @ 25 │
└──────────────────────────────────────────────────────────────────────┘
Design notes:
Discovery runs off the GUI thread. A scan walks a model tree and parses every progress CSV under it, which on a real screen is hundreds of files. It goes through
spacr.qt.bridge.make_thread()like every other spaCR job; tests passthreaded=False, which runs the same code inline. Drawing stays on the GUI thread — by then the data is already in memory.No modal dialogs on any error path. A folder with no runs, a run with no curves, a metric nothing logged — all of it lands in the inline status and problem labels. A QMessageBox hangs a headless run.
Broken runs are listed, not hidden. A folder holding checkpoints but no
train.csvstill appears, marked, with its note in the problem line. A scan that silently drops the folder you were looking for is worse than one that says what is wrong with it.The diff is bucketed, never flat. It renders
spacr.train_compare.diff_settings(), which reuses the provenance bucketing fromspacr.run_journal: environment drift (paths, hosts, worker counts) is shown in its own bucket instead of being counted as something the user changed, and schema drift is summarised. When two runs match, the table says “no differences” in words rather than going blank.Every series says run · split · fold. Clicking a line names its run, folder and both its best and last epoch, because a legend that only carries the run id invites reading a train curve as a held-out result.
Classes¶
Compare training runs: overlaid curves plus the bucketed settings diff. |
Functions¶
The |
Module Contents¶
- class spacr.qt.screens.train_compare.TrainCompareScreen(parent=None, threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetCompare training runs: overlaid curves plus the bucketed settings diff.
- Parameters:
threaded – discover and load runs on a worker thread (the default). Tests pass
Falsefor deterministic, synchronous behaviour.parent – parent widget; ownership only.
- Variables:
last_error – text of the most recent failure,
""when the last operation succeeded. Errors are only ever reported here and in the inline status label — never in a modal dialog.
Build the run list, the curve plot and the settings diff.
- Parameters:
parent – parent widget.
threaded – whether the scan runs on a worker.
- available_metrics() List[str][source]¶
Which metrics the picker is currently offering.
- Returns:
the metric names, in picker order.
- closeEvent(event)[source]¶
Stop background work before going away.
- Parameters:
event – the Qt close event.
- comparison() spacr.train_compare.Comparison | None[source]¶
The last comparison computed, if any.
- Returns:
the comparison, or None before one has been run.
- diff_headers() List[str][source]¶
The settings-diff table’s column headers.
- Returns:
the headers, in column order.
- diff_rows() List[List[str]][source]¶
The settings-diff table as it reads on screen.
- Returns:
one row per differing setting.
- fold_mode() str[source]¶
How the cross-validation folds are being combined.
- Returns:
the mode’s value.
- identify_series(label: str) str[source]¶
Name the run behind a series label and report it inline.
- Parameters:
label – legend label of a plotted series, looked up in the figure’s
spacr_series_by_labelmapping.- Returns:
the description shown, or
''when the label is unknown.
- overlay() bool[source]¶
Compare the ticked runs: draw their curves and fill the diff table.
Everything the drawing needs is already in memory after
scan(), so this runs on the GUI thread.- Returns:
True when a comparison was produced.
- run_ids() List[str][source]¶
The identifiers of every run found.
- Returns:
the run ids, in scan order.
- run_rows() List[str][source]¶
The run list exactly as it reads on screen.
Read off the WIDGET rather than rebuilt from the runs, so a test checks what the user sees rather than what the data says.
- Returns:
one string per visible row.
- runs() List[spacr.train_compare.TrainingRun][source]¶
Every run the last scan found.
A LIST COPY, so a caller cannot reorder this screen’s runs by mutating what it was handed.
- Returns:
the runs.
- scan(root: Any) bool[source]¶
Discover training runs under
rootand list them.Runs off the GUI thread unless the screen was built with
threaded=False. Every failure is reported inline.- Parameters:
root – folder to walk.
- Returns:
True when the scan started (or, unthreaded, succeeded).
- select_runs(run_ids: Sequence[str]) bool[source]¶
Tick exactly these run ids. Unknown ids are reported inline.
- Parameters:
run_ids – run ids to tick; every other run is unticked. Ids that match no listed run are reported and make the call return
False.
- selected_metric() str[source]¶
Return the metric selected for the comparison plot.
This deliberately must not be named
metric.QWidgetinheritsQPaintDevice.metric(PaintDeviceMetric), which Qt calls while laying out and painting the widget. A no-argument Python override here used to raise duringshow()and could take the whole GUI down.
- selected_run_ids() List[str][source]¶
The runs the user has ticked for comparison.
- Returns:
the selected run ids.
- set_fold_mode(mode: str) bool[source]¶
Choose how folds are combined.
- Parameters:
mode – the mode’s value.
- Returns:
True when the mode exists and was selected.
- set_metric(name: str) bool[source]¶
Pick the metric to draw; re-draws when a comparison exists.
- Parameters:
name – metric name; it must be one of
available_metrics(), otherwise an error is shown andFalsereturned.
- spacr.qt.screens.train_compare.panel_canvas_class()[source]¶
The
FigureCanvasQTAggsubclass that sits ON the page.FigureCanvasQT.__init__setsWA_OpaquePaintEventand a white palette, and the figure carries a solidfacecolor. Three opaque things stacked: the area right of “Runs found” was a flat dark rectangle whatever the page-opacity slider said, with square corners where every other container on the page has rounded ones.QSS cannot fix that — a
WA_OpaquePaintEventwidget never lets the sheet’s background through, and a stylesheet cannot round a canvas matplotlib draws edge to edge. So the panel is drawn inpaintEvent, underneath the figure, and the figure’s own patch is made fully transparent so the panel is what shows.Built lazily and cached: no screen in this package imports matplotlib at module scope, and subclassing its Qt backend would do exactly that.
Nested helpers¶
- TrainCompareScreen._run_job._job(payload: Dict[str, Any]) None¶
Call the wrapped function, stashing its result in the payload.
spacr/qt/screens/train_compare.py:960
- TrainCompareScreen.scan._job()¶
Find the training runs under a folder. Off the GUI thread.
spacr/qt/screens/train_compare.py:477
- panel_canvas_class.PanelCanvas.__init__(self, figure)¶
Build the canvas non-opaque, so the panel shows through it.
spacr/qt/screens/train_compare.py:166
- panel_canvas_class.PanelCanvas.paintEvent(self, event)¶
Draw the panel, then let matplotlib draw over it.
spacr/qt/screens/train_compare.py:176