spacr.qt.widgets.measure_input_table

The FEATURES table: rows are fields, columns are channels and mask types.

The one control the Measure module has no equivalent of. Measure reads a merged/ folder a pipeline already built, and everything about which file is which channel was decided by the plate’s naming convention long before Measure saw it. A user who drew masks with a mouse has no naming convention and no pipeline, so the question has to be asked, and this is where it is asked: one row per field, one column per channel and per mask type, and three ways to fill a cell – drop files on it, click it and browse, or write one regex that fills the whole table at once.

The model behind it is spacr.measure.FieldTable, which is Qt-free and is what spacr.measure.measure_from_field_table() consumes. This module holds no measurement knowledge of its own: it edits that table and shows what it says.

Classes

MeasureInputTable

Edit a spacr.measure.FieldTable by dropping, browsing or regex.

Functions

files_under(→ List[str])

Every file among paths, with any folder expanded one level.

role_caption(→ str)

What a mask column is called on screen.

Module Contents

class spacr.qt.widgets.measure_input_table.MeasureInputTable(parent: PySide6.QtWidgets.QWidget | None = None, *, threaded: bool = True)[source]

Bases: PySide6.QtWidgets.QWidget

Edit a spacr.measure.FieldTable by dropping, browsing or regex.

Parameters:

parent – parent widget, or None.

Variables:

table_changed – emitted whenever the table or its problems change, so the window around it can refresh the derived settings and the Run button together.

Build the table, its column controls and its regex box.

Parameters:
  • parent – parent widget, or None.

  • threaded – run the walk over dropped folders on a worker. False runs it inline, so a test can drop a folder and read the table on the next line.

add_dropped(paths: Sequence[Any]) → None[source]

Add every file under paths, once a worker has expanded them.

SPLIT FROM add_paths(), and the split is the fix for a frozen application. Everything here is a list of strings; the isdir and listdir that turn a dropped folder into files run on this widget’s own worker (files_under()), because a drop is a path the user chose and on a microscope rig that is the share the images live on – one stat under a sleeping autofs mount was measured at over twenty seconds, on the thread that paints.

NOTHING IS ADDED BY THE TIME THIS RETURNS, which is the point. Read the table from table_changed, not from the line after this.

Parameters:

paths – files and folders from a drop or a file dialog.

Returns:

None. The count does not exist yet.

add_field()[source]

Add one empty row, numbered after the last one.

add_paths(paths: Sequence[Any]) → int[source]

Sort paths into the table with the current regex.

Synchronous, and safe to be: the regex is matched against each path’s BASENAME, so sorting files into cells is string work that touches no filesystem. Anything that might be a FOLDER must come through add_dropped() instead, which expands it on a worker first.

Parameters:

paths – file paths, as strings or anything str accepts.

Returns:

how many of them landed in a cell.

assign_file(row: int, column: str, path: str) → bool[source]

Put one file in one cell, as double-clicking and browsing does.

Parameters:
  • row – the row’s position in the table.

  • column – 'channel:<index>' counting from zero, or a mask role name.

  • path – the file to put there.

Returns:

whether the cell existed and took it.

clear() → None[source]

Forget every row and every file that was ever dropped.

The channel-token memory goes with them: an empty table’s columns mean nothing yet, and keeping the old ranking would let a token the user has cleared decide where the next drop’s files land.

dragEnterEvent(event)[source]

Accept a drag that carries local files.

Decided from the mime data alone. Asking the filesystem whether the drag is worth accepting would run a stat on the GUI thread for every drag-move event the pointer produces.

Parameters:

event – the Qt drag event.

dragMoveEvent(event)[source]

Keep accepting while local files stay over the table.

Parameters:

event – the Qt drag event.

dropEvent(event)[source]

Sort the dropped files into rows and columns.

Returns before anything is assigned when a folder was dropped: the folder is expanded on a worker. Read the table from table_changed, not from the line after this one.

Parameters:

event – the Qt drop event.

is_scanning() → bool[source]

True while a walk started by add_dropped() is still running.

problems() → List[str][source]

Everything stopping this table being measured, as sentences.

reapply_regex() → int[source]

Sort every file this table has ever been given, again.

The cells are emptied first, so a corrected regex REPLACES the previous assignment instead of adding a second one beside it.

Returns:

how many files landed in a cell.

regex() → str[source]

The pattern in the regex box.

remove_selected() → int[source]

Remove every selected row. Returns how many went.

set_file_picker(picker) → None[source]

Replace the modal file chooser with picker(caption) -> path.

Headless Qt refuses a static modal – it runs its event loop in C++ and hangs the run – so a test drives the browse path through this rather than through QFileDialog.

Parameters:

picker – callable given the dialog caption and returning the chosen path, or an empty value for no choice; None restores the file dialog.

set_regex(pattern: str) → None[source]

Put pattern in the regex box and sort the known files again.

Parameters:

pattern – regular expression matched against each file’s basename; its named groups (such as field, channel and mask) decide the cell a file lands in. The cells are emptied and every known file re-sorted.

table()[source]

The model this widget edits. Live, not a copy.

unassigned() → List[Tuple[str, str]][source]

(path, reason) for every dropped file that landed nowhere.

spacr.qt.widgets.measure_input_table.files_under(paths: Sequence[str]) → List[str][source]

Every file among paths, with any folder expanded one level.

A user who drew masks for six fields drops the folder, not the files, so a drop that refused a directory would refuse the ordinary case.

NOT FOR THE GUI THREAD. Every path here is one the user chose, which on a microscope rig means a network share, and the isdir is what wakes the automount. MeasureInputTable.add_dropped() is the only caller and it runs this on a worker. Looked up through the module global so a test can replace it and see which thread it ran on.

Parameters:

paths – what was dropped or chosen.

Returns:

the files, folders expanded, in a stable order.

spacr.qt.widgets.measure_input_table.role_caption(role: str) → str[source]

What a mask column is called on screen.

An organelle slot reads as a number – Organelle 2 – never as the lettered role it is stored under, the same caption the settings forms and the Import Project screen give it.

Parameters:

role – a role from spacr.crops.MASK_PLANE_ORDER.

Returns:

the column heading.