spacr.qt.widgets.object_settings_grid

The repeated per-object settings, drawn as one row per question.

78 of Mask’s 201 settings are the SAME twenty-odd questions asked once per object type – cell_diameter, nucleus_diameter, pathogen_diameter, organelle_diameter – and a form that lists them flat asks 203 questions before anything is segmented. A table was chosen over tabs and over leaving the names flat.

spacr.object_settings_table is the model and draws nothing; this is the view over it. The split matters more than it looks: the stored keys never change, so no settings file, notebook, tutorial or spacr-run invocation migrates. What was wrong was the presentation, so only the presentation changes.

WHY THIS SHAPE IS WHAT LETS AN ARBITRARY ORGANELLE COUNT LAND. The number of organelles a run may declare is not fixed. In a flat vocabulary each new organelle is twenty new settings that every tooltip table and translation catalog has to learn; here it is one COLUMN, and the number of questions does not move. ObjectSettingsGrid.add_object() is that operation, and it starts a new organelle from the first one’s answers rather than from a global default nobody chose.

TWO THINGS THIS VIEW IS CAREFUL ABOUT, both of which would corrupt a settings file rather than merely look wrong:

  • A value keeps its type. cell_diameter is an int, organelle_ cellprob_threshold is a float, and a cell edited in a table arrives as a string. Writing "12" where 12 was is a settings file that has quietly changed meaning, and the pipeline reading it either coerces silently or fails a long way from here.

  • A question an object does not ask stays absent. cytoplasm has no channel, no diameter and no detection method – it is DERIVED, cell minus the rest, not found in a channel. Those cells are blank and not editable, because writing a value there invents a key nothing reads.

Classes

ObjectSettingsGrid

The per-object settings table, and the button that widens it.

ObjectSettingsModel

One row per question, one column per object type.

Module Contents

class spacr.qt.widgets.object_settings_grid.ObjectSettingsGrid(parent=None)[source]

Bases: PySide6.QtWidgets.QWidget

The per-object settings table, and the button that widens it.

Parameters:

parent – parent widget.

Build the per-object settings grid.

Sorting is installed after the model is set, as the contract requires – the view is wrapped in a proxy, so the selection model has to be taken afterwards. Sorting the questions on screen reorders nothing on disk, because the stored answers are read from the model rather than the view.

The table opens tall enough to show its rows and can be dragged from the grip: inside a settings panel it is one row of a scrolling form, and a plain QTableView default put twenty-odd questions behind an inner scrollbar inside an outer one.

Parameters:

parent – parent widget, or None.

add_filter(name: str) → bool[source]

Add a filter row for regionprop name, every cell empty.

Parameters:

name – a scalar scikit-image regionprop, e.g. "area" or "intensity_mean"; legacy spellings are accepted.

Returns:

False when the name is not a filterable property or the row is already there.

add_organelle() → bool[source]

Add the next organelle column, seeded from the first one.

Returns:

False when there is no slot left, with the reason on screen rather than as an exception into a GUI slot.

A slot whose channel is unset is hidden.

choose_model_for(obj: str) → bool[source]

Open the model zoo for one object and store what it returns.

Parameters:

obj – object name whose model_name cell receives the chosen path, e.g. "cell".

Returns:

True when a model was chosen. Cancelling leaves the cell alone rather than clearing it – a cancelled dialog is not an instruction to forget the model already set.

content_height() → int[source]

The height that would show every row without an inner scrollbar.

eventFilter(watched, event)[source]

Show the same sticky, linked tooltip the form shows.

THE SAME POPUP, NOT A SECOND ONE. The flat form puts rich help on a widget and lets HoverTooltip draw it: a typed body, an API link, and the setting’s animation when it has one. A table has no widget per cell, so the anchor is the view and the cell under the pointer decides which setting it is speaking for.

The native tooltip is swallowed for the same reason the form swallows it – it disappears the moment the pointer moves toward the API link, and that link is the point.

Parameters:
  • watched – the object the filter is installed on: this widget (font changes), the help band (enter and leave) or the table’s viewport (tooltip, mouse move and leave).

  • event – the event; a tooltip event on the viewport is swallowed and every other event is passed on to the base class.

filter_properties() → Tuple[str, ...][source]

The properties the table has a filter row for, in order.

next_organelle() → str[source]

The role the next organelle column would take, or ''.

Empty at the ceiling. Slot names are lettered – an object type is embedded in an underscore-separated object key, so a digit would be ambiguous against the object LABEL – and the lettering CARRIES past z, so the ceiling is where two letters run out rather than one.

COUNTS UP FROM THE SLOTS IN USE rather than from one. Walking every slot from the start was fine while there were 26; there are now 702, and the caller that presses Add repeatedly turned an O(slots) scan into an O(slots squared) one.

Hidden slots count as in use too.

objects() → Tuple[str, ...][source]

Which object columns are on screen.

questions() → Tuple[str, ...][source]

Which questions are on screen, in order.

reset_user_height() → None[source]

Forget a dragged height and go back to fitting the rows.

set_app_key(app_key: str) → None[source]

Say which module’s API documentation the tooltips should link to.

Parameters:

app_key – registry key of the module, e.g. "mask"; None or empty clears it.

set_filters(value) → None[source]

Show object_filters as it now stands, e.g. after a file load.

Parameters:

value – the setting’s value, a mapping or its text.

set_settings(settings: Mapping[str, Any]) → None[source]

Show the per-object half of a flat settings dict.

The rest is KEPT, not dropped: settings() returns it unchanged beside the table’s own keys, so this widget can edit a corner of a settings file without holding the whole of it hostage.

Parameters:

settings – flat settings dict, or None; its <object>_<question> keys become the table.

set_user_height(height: int) → None[source]

Fix the table at height px, clamped to at least MIN_TABLE_H.

Parameters:

height – wanted table height in pixels.

set_value(question: str, obj: str, text: str) → bool[source]

Type into one cell the way the editor would.

The screen’s own edit path, exposed so a test drives the same code an item delegate does rather than reaching into the model.

Parameters:
  • question – settings question, i.e. the key suffix shared by every object ("min_area" for cell_min_area).

  • obj – object name, the key prefix ("cell", "nucleus", …).

  • text – what the user would type; converted to the type of the cell’s current value (or a sibling’s). Returns False for an unknown row or column, a cell the object does not ask, or no change.

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

The whole settings dict, with the table’s answers written back.

The filter rows are written back into object_filters: each shown object’s list is rebuilt from its cells, in row order, and an object the table does not show keeps the list it had.

status_text() → str[source]

What the line under the table says.

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

The table itself, for a caller that wants the shape.

class spacr.qt.widgets.object_settings_grid.ObjectSettingsModel(parent=None)[source]

Bases: PySide6.QtCore.QAbstractTableModel

One row per question, one column per object type.

A model rather than a widget full of cells because the table is 55 rows by as many objects as the run has, and every one of those cells would otherwise be a widget the form has to build, lay out and translate.

Parameters:

parent – parent widget.

Create the empty per-object settings table model.

Parameters:

parent – parent object, or None.

asks(question: str, obj: str) → bool[source]

Whether obj asks question at all.

Absence is a fact about the object, not a value it has yet to be given: cytoplasm is derived and has no channel to be found in.

Parameters:
  • question – settings question, i.e. the key suffix shared by every object ("min_area" for cell_min_area).

  • obj – object name, the key prefix ("cell", "nucleus", …).

columnCount(parent=QModelIndex()) → int[source]

How many objects the table has a column for.

Parameters:

parent – unused; the model is flat.

Returns:

the column count.

data(index, role=Qt.DisplayRole)[source]

One cell of the table.

Parameters:
  • index – the cell.

  • role – the Qt display role.

Returns:

the cell’s value for that role, or None.

flags(index)[source]

Which cells are editable.

ONLY THE CELLS AN OBJECT ACTUALLY ASKS. A blank cell means that object does not ask that question, and making it editable would invite an answer to a question nobody posed.

Parameters:

index – the cell.

Returns:

the Qt item flags.

headerData(section, orientation, role=Qt.DisplayRole)[source]

One header label: a question name, or an object name.

Parameters:
  • section – the row or column number.

  • orientation – which header.

  • role – the Qt display role.

Returns:

the label, or None.

objects() → Tuple[str, ...][source]

The object columns, in the order they are drawn.

question_at(row: int) → str[source]

The settings question one row asks, or ''.

Parameters:

row – zero-based table row; out of range gives ''.

rowCount(parent=QModelIndex()) → int[source]

How many questions the table asks.

Parameters:

parent – unused; the model is flat.

Returns:

the row count.

setData(index, value, role=Qt.EditRole) → bool[source]

Write one cell back into the settings.

Parameters:
  • index – the cell.

  • value – what the user typed.

  • role – the Qt edit role.

Returns:

True when the value was taken.

set_table(table: Mapping[str, Mapping[str, Any]]) → None[source]

Show table, as spacr.object_settings_table.to_table() returns it.

Parameters:

table – {question: {object: value}} mapping, or None for an empty table. Rows keep its order; columns follow the canonical object order.

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

The table as it now stands, including every edit.

value_at(question: str, obj: str) → Any[source]

One cell’s stored value. KeyError-free: absent is None.

Parameters:
  • question – settings question, i.e. the key suffix shared by every object ("min_area" for cell_min_area).

  • obj – object name, the key prefix ("cell", "nucleus", …).