spacr.qt.curation_tool¶
B12 C7 — the brush and the track surgery, as widgets.
spacr.curation is the session: the undo history, the ledger, and the
rules a join or a split has to obey, all in plain numpy and pandas so they can
be tested without a display. This is the mouse and the two panels.
The brush is a CanvasTool, like the ROI pen
and the counter, so it borrows the canvas’s mouse without the canvas knowing
what it is for — and the world coordinates it is handed are what make a stroke
painted at 8× zoom land where the same stroke at 1× does.
One drag is one undo¶
A stroke is dozens of move events and exactly one thing the user did.
BrushTool.press() opens a stroke, BrushTool.release() closes it,
and undo takes back the stroke — not the last few pixels of it. That is the
whole reason CanvasTool grew a release hook.
Nothing is edited off the record¶
Both panels write their ledger after every action rather than on a Save button. A correction that is only in memory when the application is killed is a correction that happened to the data and not to the record of it, and the two disagreeing is worse than neither existing.
Classes¶
The brush controls, the undo button, and the ledger beside the image. |
|
Turns a drag on a |
|
Join, split and delete tracks, with the ledger beside them. |
Module Contents¶
- class spacr.qt.curation_tool.BrushPanel(canvas: spacr.qt.layer_viewer.LayerCanvas, parent=None, *, layer: spacr.layers.LabelsLayer | None = None, artifact: str = '', session: spacr.curation.MaskCuration | None = None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe brush controls, the undo button, and the ledger beside the image.
- Parameters:
canvas – the canvas to paint on.
layer – the labels layer to edit. Defaults to the first one in the canvas’s stack, so the ordinary case needs no argument.
artifact – the mask’s path, so the ledger is written beside it.
parent – parent widget; ownership only.
session – an already-built
MaskCuration.Nonebuilds one from the other arguments, which is the ordinary case; PASSING ONE HANDS THE PANEL A SESSION THAT ALREADY HAS STATE – a part-finished correction, or one a test wrote directly – so the panel resumes it instead of starting over.
Build the brush controls over one canvas.
- Parameters:
canvas – the canvas being painted.
parent – parent widget.
layer – the labels layer being edited.
artifact – what the edits are written to.
session – the curation session recording them.
- closeEvent(event) None[source]¶
Stop painting and let go of the model.
- Parameters:
event – the close event, passed on to the base class after painting stops and the panel unsubscribes from the layer stack and the session.
- save_log(path: str | None = None) str | None[source]¶
Write the ledger beside the mask. Returns the path.
- save_mask(path: str | None = None) str | None[source]¶
Write the corrected labels back to the mask, ledger and all.
The pixels and the record go in one call (
spacr.curation.MaskCuration.save_mask()), because either one alone misreports the file: a ledger beside untouched pixels claims corrections that were never applied, and labels with no ledger are a hand-edited mask nobody can tell from a segmented one.- Parameters:
path – where to write; anything falsy means the artefact this panel was opened on.
clickedhands a slot the checked state, so a bool arriving here reads as “no path”, not as one.- Returns:
the path written, or
Nonewhen it could not be.
- property session: spacr.curation.MaskCuration[source]¶
The curation session this panel drives.
- class spacr.qt.curation_tool.BrushTool(session: spacr.curation.MaskCuration)[source]¶
Bases:
spacr.qt.layer_viewer.CanvasToolTurns a drag on a
LayerCanvasinto paint.Left drag paints the active label; right drag erases (paints 0), because “take that bit off the mask” is the other half of the same gesture and having to switch to an eraser mode for it doubles the interactions in a job that is already mostly correction.
[and]resize the brush and Backspace undoes, so a whole correction pass is one hand on the mouse.- Parameters:
session – the
spacr.curation.MaskCurationto paint into.
Bind the brush to one curation session.
- Parameters:
session – the session its edits are recorded in.
- key(view: spacr.qt.layer_viewer.LayerCanvas, event: Any) bool[source]¶
[/]resize the brush; Backspace undoes a stroke.- Parameters:
view – the canvas that received the key press; not used.
event – the key event; Backspace or Delete undo, and its text
[or]shrinks or grows the radius by a factor of 1.5 (never below 0.5).
- move(view: spacr.qt.layer_viewer.LayerCanvas, world: Dict[str, float], event: Any) bool[source]¶
Continue the stroke, but only while a button is actually down.
The canvas sends
movefor every mouse motion, drag or not. Without this guard the brush would paint wherever the cursor happened to travel after the button came up — which is the sort of bug that destroys a mask in the time it takes to reach for the undo button.- Parameters:
view – the canvas that received the motion; not used.
world – world coordinate under the pointer, where the next dab is laid.
event – the mouse move event; its held buttons are read, and nothing is painted unless the left or right button is down.
- press(view: spacr.qt.layer_viewer.LayerCanvas, world: Dict[str, float], event: Any) bool[source]¶
Open a stroke and lay the first dab.
- Parameters:
view – the canvas that received the press; not used.
world – world coordinate under the pointer, where the first dab is laid.
event – the mouse press event; its button picks painting (left) or erasing (right), and any other button is ignored.
- release(view: spacr.qt.layer_viewer.LayerCanvas, world: Dict[str, float], event: Any) bool[source]¶
Close the stroke, so undo takes back all of it.
- Parameters:
view – the canvas that received the release; not used.
world – world coordinate under the pointer; not used.
event – the mouse release event; not used.
- class spacr.qt.curation_tool.TrackCurationPanel(parent=None, *, tracks: pandas.DataFrame | None = None, artifact: str = '', session: spacr.curation.TrackCuration | None = None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetJoin, split and delete tracks, with the ledger beside them.
- Parameters:
tracks – a track table, or
Noneto open one later.artifact – the tracks CSV the table came from.
The three operations are three buttons and no modes. A join takes the two selected tracks; a split takes one track and the frame in the spinner; a delete takes whatever is selected. Every one of them is refused with a sentence rather than silently declined when it would break the table — a button that sometimes does nothing is indistinguishable from a bug.
- Parameters:
parent – parent widget; ownership only.
session – an already-built
TrackCuration.Nonebuilds one from the other arguments, which is the ordinary case; PASSING ONE HANDS THE PANEL A SESSION THAT ALREADY HAS STATE – a part-finished curation, or one a test wrote directly – so the panel resumes it instead of starting over.
Build the track-curation controls.
- Parameters:
parent – parent widget.
tracks – the tracks being curated.
artifact – what the edits are written to.
session – the curation session recording them.
- load(path: str) spacr.curation.TrackCuration | None[source]¶
Read a tracks CSV and open it.
Any ledger already beside it is read back too, so a second curation session continues the first one’s history rather than starting a fresh one that makes the earlier edits invisible.
- Parameters:
path – tracks CSV to read; its
.curation.jsonledger beside it is read back if present. A read or validation failure is shown in the status line and returnsNone.
- save(path: str | None = None) str | None[source]¶
Write the curated table and its ledger. Returns the CSV path.
- set_tracks(tracks: pandas.DataFrame, *, artifact: str = '') None[source]¶
Open a track table. The seam a screen (or a test) goes through.
- Parameters:
tracks – track table to curate; it must contain
frameandtrack_idand is copied byspacr.curation.TrackCuration.
- property session: spacr.curation.TrackCuration | None[source]¶
The curation session, or
Nonebefore a table is open.