feat(bec_widgets): add at_each_angle hook dropdown to TomoParamsWidget
A new "At-each-angle hook" combo box in the params panel, built from the live tomo_at_each_angle_hooks global var (no hardcoded mirror of registered names). Rebuilt on every populate (i.e. every 2s poll while not editing, and on entering edit mode), so hooks registered/ unregistered from the CLI while the dialog is open show up automatically. The currently-set hook is always shown even if it isn't in the published list (e.g. registered in a different or since-restarted session), labeled accordingly, so it's never silently hidden. Special-cased in _populate_fields()/_read_fields() like tomo_type -- the generic QComboBox handling assumes integer data, which doesn't fit a hook name string or None. This also makes the manual params["at_each_angle_hook"] pass-through in add_edited_to_queue() (added when there was no widget for this field) redundant; removed, since _read_fields() now supplies it directly from the dropdown. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -386,6 +386,16 @@ class TomoParamsWidget(BECWidget, QWidget):
|
||||
max_=10.0,
|
||||
decimals=3,
|
||||
)
|
||||
self._pw["at_each_angle_hook"] = QComboBox()
|
||||
self._pw["at_each_angle_hook"].setEnabled(False)
|
||||
self._pw["at_each_angle_hook"].setToolTip(
|
||||
"Run a registered custom function at every projection angle instead "
|
||||
"of the normal acquisition (e.g. move a polarizer in/out between "
|
||||
"exposures). Registered from the CLI with "
|
||||
"flomni.register_at_each_angle_hook(name, func) -- this list refreshes "
|
||||
"automatically while the dialog is open."
|
||||
)
|
||||
common_form.addRow("At-each-angle hook:", self._pw["at_each_angle_hook"])
|
||||
vbox.addLayout(common_form)
|
||||
|
||||
# type-1 section
|
||||
@@ -523,8 +533,10 @@ class TomoParamsWidget(BECWidget, QWidget):
|
||||
self._pw["tomo_type"].setCurrentIndex(idx)
|
||||
self._pw["tomo_type"].blockSignals(False)
|
||||
|
||||
self._populate_at_each_angle_hook_combo(params.get("at_each_angle_hook"))
|
||||
|
||||
for key, widget in self._pw.items():
|
||||
if key in ("tomo_type", "_requested_total"):
|
||||
if key in ("tomo_type", "_requested_total", "at_each_angle_hook"):
|
||||
continue
|
||||
val = params.get(key)
|
||||
if val is None:
|
||||
@@ -562,14 +574,39 @@ class TomoParamsWidget(BECWidget, QWidget):
|
||||
|
||||
self._update_type_visibility(type_val)
|
||||
|
||||
def _populate_at_each_angle_hook_combo(self, current: Optional[str]) -> None:
|
||||
"""Rebuild the at_each_angle_hook dropdown from the live
|
||||
``tomo_at_each_angle_hooks`` registry (published by
|
||||
``Flomni.register_/unregister_at_each_angle_hook()``) and select
|
||||
``current``.
|
||||
|
||||
``current`` is added as its own entry even if it isn't in the
|
||||
published list -- e.g. it was registered in a different, or a
|
||||
since-restarted, session -- so the operator sees what's actually
|
||||
set instead of it silently disappearing from the dropdown.
|
||||
"""
|
||||
combo = self._pw["at_each_angle_hook"]
|
||||
registered = self._gv_get("tomo_at_each_angle_hooks") or []
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
combo.addItem("None (default)", None)
|
||||
for name in registered:
|
||||
combo.addItem(name, name)
|
||||
if current and current not in registered:
|
||||
combo.addItem(f"{current} (not registered in this session)", current)
|
||||
idx = combo.findData(current)
|
||||
combo.setCurrentIndex(idx if idx >= 0 else 0)
|
||||
combo.blockSignals(False)
|
||||
|
||||
def _read_fields(self) -> dict[str, Any]:
|
||||
"""Read all input widgets into a params dict."""
|
||||
params: dict[str, Any] = {}
|
||||
|
||||
params["tomo_type"] = self._pw["tomo_type"].currentData()
|
||||
params["at_each_angle_hook"] = self._pw["at_each_angle_hook"].currentData()
|
||||
|
||||
for key, widget in self._pw.items():
|
||||
if key in ("tomo_type", "_requested_total"):
|
||||
if key in ("tomo_type", "_requested_total", "at_each_angle_hook"):
|
||||
continue
|
||||
if isinstance(widget, QDoubleSpinBox):
|
||||
params[key] = widget.value()
|
||||
@@ -933,13 +970,6 @@ class TomoParamsWidget(BECWidget, QWidget):
|
||||
would silently ignore an in-progress edit.
|
||||
"""
|
||||
params = self._read_fields()
|
||||
# _read_fields() only covers widget-backed params (self._pw); there's
|
||||
# no editable widget for at_each_angle_hook yet, so it would
|
||||
# otherwise be silently absent from this job's params -- which would
|
||||
# make the executor leave whatever hook the *previous* queued job
|
||||
# left active in place for this one too. Pass the current live value
|
||||
# through unchanged instead of dropping it.
|
||||
params["at_each_angle_hook"] = self._gv_get("at_each_angle_hook")
|
||||
error = self._validate(params)
|
||||
if error:
|
||||
QMessageBox.warning(self, "Validation error", error)
|
||||
|
||||
Reference in New Issue
Block a user