spacr.qt.screens.pipeline_graph¶
Workflow inputs and outputs¶
Pipeline Graph¶
Read recorded artifact dependencies and stale/missing status. The graph describes provenance and does not regenerate missing artifacts.
Open: the application’s Help/tools menus.
Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.
Inputs
Run history and artifacts — Project run records, settings, output paths, artifact provenance, status and logs.
Outputs
Figures and table exports — The output location chosen by the tool; exports describe the selected data and filters.
Pipeline Graph — what produced what, and which of it is still true.
A spaCR project accumulates files faster than anyone can hold in their head: masks, a measurements database, crops, model weights, predictions, regression tables, figures. Some of those were made from each other. Some were made from a previous version of each other, because the mask step was re-run with a different diameter on Tuesday and nothing downstream was. Nothing in the GUI showed that, and a stale number does not look stale — it looks like a number.
This screen draws the DAG:
┌──────────────┐ ┌────────────────┐ ┌───────────────┐
│ mask │──▶│ measure │──▶│ classify │
│ masks OK │ │ db STALE │ │ preds STALE │
└──────────────┘ └────────────────┘ └───────────────┘
Boxes are artifacts, arrows are “was made from”, and the colour is the
verdict spacr.artifacts.Registry.is_stale() returns — with its
reasons shown in the panel beside the graph, because “stale” on its own
is an accusation, not an explanation. Click a box and the panel names the
run that produced it, the settings digest, the spaCR version, and what
re-running it would invalidate.
Three deliberate choices:
The graph is drawn from provenance, not from the module list. What the
pipeline is supposed to do is spacr.ports.PORTS, and that is
drawn too — dimmed, underneath, as the “declared order” strip — but the
boxes and arrows are what actually happened. A project where measure read
last week’s masks does not look like the tidy diagram, and that difference
is the entire value of the screen.
Missing is not stale. A deleted file is red and says so; an outdated
one is amber. spacr.artifacts refuses to conflate them and neither
does this.
Nothing here writes. No re-run button, no “mark as fresh”, no delete. The screen answers a question; acting on the answer is the module’s job, and a one-click re-run wired to a graph is how somebody overwrites the artifact they were trying to check.
The layout is computed by a pure function (layout_rects()) off the
graph’s layers, so the arrangement is testable without pixels, and the
heavy part — opening the registry and asking it about every artifact — runs
through spacr.qt.job_runner.JobRunner, off the GUI thread.
Classes¶
Draws one |
|
Pick a project; see its provenance DAG with staleness marked. |
Functions¶
|
The pixel size the whole graph needs, as |
|
Place every node of |
|
Factory the registry calls to build this screen. |
|
Add Pipeline Graph to the app registry. Idempotent. |
Module Contents¶
- class spacr.qt.screens.pipeline_graph.GraphCanvas(parent=None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetDraws one
PipelineGraph.Custom painting rather than
QGraphicsScene: the graph is a few dozen boxes with straight-ish edges, and a scene brings its own selection model, its own coordinate system and its own set of ways to leak a C++ object into a Python-owned widget. The whole drawing is onepaintEventover a dict of rectangles that a test can read directly.- Parameters:
parent – Qt parent.
- Variables:
selected – artifact id of the box the user last clicked, or
"".
Create the empty pipeline-graph canvas.
- Parameters:
parent – parent widget, or
None.
- graph() spacr.pipeline_graph.PipelineGraph | None[source]¶
The graph currently drawn, or
None.
- mousePressEvent(event) None[source]¶
Select whatever box was clicked.
- Parameters:
event – the mouse press; its
position()picks the box, and it is then passed on to the base-class handler.
- node_at(x: int, y: int) str[source]¶
Artifact id of the box containing this point, or
"".- Parameters:
x – horizontal widget coordinate, in pixels.
y – vertical widget coordinate, in pixels.
- node_rects() Dict[str, PySide6.QtCore.QRect][source]¶
{artifact id: rectangle}for every box currently drawn.
- paintEvent(event) None[source]¶
Paint the edges, then the boxes, then the selection ring.
- Parameters:
event – the paint event; not read, the whole canvas is repainted.
- select(artifact_id: str) None[source]¶
Select a box by id (
""clears) and emitnode_clicked.- Parameters:
artifact_id – the artifact to select; an id that is not drawn clears the selection.
- set_graph(graph: spacr.pipeline_graph.PipelineGraph | None, visible: set | None = None) None[source]¶
Show
graph, drawing only the ids invisiblewhen given.- Parameters:
graph – the pipeline graph to lay out and draw, or
Nonefor an empty canvas.visible – artifact ids to draw;
Nonedraws every node, and ids not in the graph are ignored.
- class spacr.qt.screens.pipeline_graph.PipelineGraphScreen(parent=None, project: str = '', threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetPick a project; see its provenance DAG with staleness marked.
- Parameters:
parent – Qt parent.
project – open straight onto this project root, skipping the folder picker.
threaded –
Falsebuilds the graph inline, so a test drives the screen synchronously without the behaviour diverging.
- Variables:
last_error – text of the most recent failure,
""when the last operation worked. Failures land here and in the banner — never in a modal dialog, which hangs a headless run.
Build the screen and arm its drop zone.
- Parameters:
parent – parent widget, or
None.project – a project folder to draw immediately; empty leaves the screen asking for one.
threaded – scan on a worker thread. Set
Falsein tests soload_projectfinishes before it returns.
- closeEvent(event) None[source]¶
Drain the worker before the widget goes.
- Parameters:
event – the close event; passed on unchanged to the base class.
- describe(artifact_id: str) str[source]¶
The detail block for one artifact, as plain text.
Split out from the widget so a test can assert on the content without reading a
QTextEditback.- Parameters:
artifact_id – the artifact to describe; an empty or unknown id, or no graph loaded, gives
"".
- graph() spacr.pipeline_graph.PipelineGraph | None[source]¶
The graph currently drawn, or
None.
- load_project(project: str) None[source]¶
Build and draw the graph for
project, off the GUI thread.Returns as soon as the job is submitted;
graph_loadedfires when the graph is drawn. A project with no registry is not a failure — it draws the declared module order and says nothing has run.- Parameters:
project – the spaCR project folder; stripped, and an empty value only asks for a folder.
- spacr.qt.screens.pipeline_graph.canvas_size(graph: spacr.pipeline_graph.PipelineGraph) Tuple[int, int][source]¶
The pixel size the whole graph needs, as
(width, height).
- spacr.qt.screens.pipeline_graph.layout_rects(graph: spacr.pipeline_graph.PipelineGraph) Dict[str, PySide6.QtCore.QRect][source]¶
Place every node of
graphon a grid: one column per layer.Column is
spacr.pipeline_graph.Node.depth, which is the longest distance from a root — so an arrow never points backwards. Row is the node’s position within its layer, in the order the graph already sorted them (newest first), so re-building the same graph twice draws the same picture.- Parameters:
graph – the graph to lay out.
- Returns:
{artifact_id: QRect}, empty for an empty graph.