spacr.qt.screens.measure_inputs

The window the FEATURES button in Make Masks opens.

Three things stacked, and the order is the order the questions come in:

  1. spacr.qt.widgets.measure_input_table.MeasureInputTable – which file is which channel, and which is which object.

  2. The Measure module’s OWN settings form, built from spacr.qt.screens.settings_model.SettingsWidgets. Not a copy of it and not a chosen subset: the same widgets the Measure screen builds, from the same defaults, so a setting that exists there exists here and a setting that gains a control there gains one here without this file being edited.

  3. Run, off the GUI thread, through spacr.measure.measure_from_field_table() – which writes the folders the Mask module would have written and then calls measure_crop itself. The database and the folder tree are Measure’s because Measure makes them.

IT IS A QDialog, AND THAT IS WHAT MAKES IT LOOK LIKE spaCR. It used to be a bare QWidget shown with the Qt.Window flag, which meant the operating system’s own title bar, square corners, an opaque background and minimise/maximise/close buttons – a window from a different program sitting in the middle of this one. spacr.qt.widgets.glass gives every dialog in the package the translucent card, the rounded corners and the travelling rim, and it recognises a dialog by its TYPE. So the dress is not applied here; being a dialog is.

Its actions are at the bottom right, in the order every spaCR dialog uses: the thing you came to do in the accent colour (Measure, PrimaryButton), and the way out in red beside it (Close, DangerButton). Escape is the other way out, and closes rather than hides – see reject().

THE SETTINGS THE TABLE DECIDES ARE SHOWN AND DISABLED rather than hidden. cell_mask_dim is not a question once the table says which column holds the cell masks, but a user who has read the Measure documentation comes here looking for it, and a control that is missing reads as a feature that is missing. Shown, filled in, and not editable says the table already answered it. spacr.measure.FIELD_TABLE_DECIDED_KEYS is the list, and it is derived from the plane order rather than written out again here.

Classes

MeasureInputsScreen

Table, Measure's settings, and a Run that goes through Measure.

Functions

open_measure_inputs(→ MeasureInputsScreen)

Open the FEATURES window, owned by owner's window.

write_setting_value(→ bool)

Write one value into whichever kind of control holds it.

Module Contents

class spacr.qt.screens.measure_inputs.MeasureInputsScreen(parent: PySide6.QtWidgets.QWidget | None = None, *, threaded: bool = True)[source]

Bases: PySide6.QtWidgets.QDialog

Table, Measure’s settings, and a Run that goes through Measure.

Parameters:
  • parent – parent widget, or None.

  • threaded – False runs the measurement inline, emitting the same signals in the same order, so a test can drive the whole window synchronously without the behaviour diverging.

Variables:

run_finished – emitted with the result dict when a run completes, or with None when it failed.

Build the window and its three parts.

apply_settings_dict(settings: Dict[str, Any]) → int[source]

Write settings into the panel’s controls.

The same contract spacr.qt.screens.app_screen.AppScreen.apply_settings_dict() has, so a settings pack saved from the Measure screen loads here.

Parameters:

settings – the values to write. Keys with no control are ignored.

Returns:

how many controls took a value.

closeEvent(event)[source]

Stop the run’s threads before Qt destroys the widgets.

Qt ABORTS THE PROCESS if a running QThread is destroyed, and this window is closable while a measure run – minutes of work – is in flight. spacr.qt.job_runner.shutdown_all() covers the application quitting; it does not cover one window being closed.

Parameters:

event – the Qt close event.

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

The settings a run would use: the panel’s, over-written by the table.

The same call the run makes, so what this returns is what would happen rather than a second opinion about it.

destination() → str | None[source]

Where the next run would write, as far as this window knows.

done(result)[source]

Stop the run whichever way the window is dismissed.

THE HOOK IS done AND NOT reject, and not closeEvent alone. Escape reaches reject, which hides the window WITHOUT a close event, and that would leave a measure run – minutes of work, on a thread – going behind a window nobody can see; a running QThread destroyed at exit aborts the process. Overriding reject to close instead looks like the fix and is a loop: QDialog.closeEvent itself calls reject.

Parameters:

result – Qt’s dialog code, passed straight on.

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

What the last run returned, or None if none has finished.

run() → bool[source]

Measure the table, off the GUI thread.

Returns:

whether a run was started. A table with problems in it starts nothing and says what they are.

set_destination(path: str | None) → None[source]

Where the project is written. None puts it beside the files.

Parameters:

path – folder the project is written to, stored as a string; any falsy value (None or an empty string) puts it beside the input files.

stop_the_runners() → None[source]

Shut down the measure worker and the folder scanner, if any.

spacr.qt.screens.measure_inputs.open_measure_inputs(owner: PySide6.QtWidgets.QWidget | None = None, *, folder: str | None = None, threaded: bool = True) → MeasureInputsScreen[source]

Open the FEATURES window, owned by owner’s window.

Parameters:
  • owner – the screen the button is on, so Qt keeps the window alive for as long as that screen is and closes it with the application.

  • folder – the folder the user is drawing in. Its files are offered to the table straight away, because a user who pressed FEATURES from an open folder means that folder.

  • threaded – False runs the measurement and the folder walk inline; for tests.

Returns:

the window, already shown.

THE FOLDER IS NOT READ HERE. This runs in the Make Masks button handler, on the GUI thread, and folder is a path the user chose – on a microscope rig, the share the images live on. It used to isdir and listdir it and stat every entry before the window was even shown, so pressing FEATURES on a folder living on a sleeping automount froze the application with no traceback. The walk is MeasureInputTable.add_dropped()’s, on a worker, and the table fills in a moment later.

spacr.qt.screens.measure_inputs.write_setting_value(widget, value) → bool[source]

Write one value into whichever kind of control holds it.

THE TRAP THIS EXISTS FOR: only some of the settings controls carry a set_value method. cell_min_size is a plain QSpinBox, and a writer that tests hasattr(widget, 'set_value') skips it – silently, leaving the default 8000 in place. A run configured that way measures nothing and reports success, because every object was smaller than a minimum nobody chose.

The coercions are the ones spacr.qt.screens.app_screen.AppScreen._apply_value() makes, kept in step with it by hand. They are not shared yet because that method is bound to a screen this window is not one of; if a third caller appears, move it rather than copying it again.

Parameters:
  • widget – the control to write into.

  • value – the value, coerced to the control’s own type and left alone when it cannot be.

Returns:

whether the control took it.

Nested helpers

MeasureInputsScreen.run.work()

Run the measurement. Worker thread; touches no widget.

The table it measures is a SNAPSHOT taken above, not the widget’s live model. A measure run takes minutes and preparing the next batch while it runs is the natural thing to do, so the GUI thread can append rows and delete channel and mask keys underneath this – which reaches the worker as a KeyError in the middle of writing, or as half a field that nobody asked for. The snapshot is plain data, so copying it is cheap and it cannot be edited from anywhere.

spacr/qt/screens/measure_inputs.py:447