Source code for spacr.qt.widgets.derived_table_source
"""Shared derived-table actions for plotting and gating screens."""
from PySide6.QtWidgets import QDialog, QPushButton
from ...derived_tables import execute, save_definition, schemas
[docs]
class DerivedTableSource:
"""Add named merges to an existing table screen without owning its loader."""
def _install_merge_button(self, layout):
"""Place the merge action immediately after the screen's table picker.
:param layout: Header layout containing the table picker.
"""
self._merge_definition = None
self._merge_button = QPushButton("Merge tables", self)
self._merge_button.setObjectName("MergeTablesButton")
self._merge_button.setToolTip("Combine measurements into a named table with a validated preview")
self._merge_button.setEnabled(False)
self._merge_button.clicked.connect(self.open_merge_dialog)
layout.addWidget(self._merge_button)
[docs]
def open_merge_dialog(self):
"""Create a validated persistent result and load it through the normal path."""
from .merge_tables_dialog import MergeTablesDialog
if not self._path:
return
try:
available = schemas(self._path)
selected = [t for t in getattr(self, "_tables", [self._table_picker.currentText()])
if t in available]
kwargs = {"selected": selected}
if self._merge_definition:
kwargs["initial_definition"] = self._merge_definition
dialog = MergeTablesDialog(self._path, self, **kwargs)
if dialog.exec() == QDialog.Accepted:
self.use_derived_table(dialog.definition)
except Exception as exc:
self._source.setText(f"Could not merge tables: {exc}")
[docs]
def use_derived_table(self, definition):
"""Validate and persist a definition, then use the screen's ordinary loader.
:param definition: Named definition bound to the currently loaded database.
"""
source = self._path
self._jobs.cancel()
self._source.setText("Creating merged table…")
def work():
"""Validate and persist the source-bound definition on a worker."""
frame, _report = execute(source, definition)
return save_definition(source, frame.attrs.get("merge_definition", definition))
self._jobs.submit(work, lambda name: self.load_path(source, table=name))
def _derived_frame_loaded(self, frame):
"""Retain reproduction metadata and update image-only actions.
:param frame: Newly loaded source or derived frame.
"""
self._merge_definition = frame.attrs.get("merge_definition")
database = bool(self._path and not str(self._path).lower().endswith((".csv", ".tsv", ".txt")))
self._merge_button.setEnabled(database and len(getattr(self, "_paths", [])) <= 1)
can_link = not self._merge_definition or frame.attrs.get("image_provenance", False)
for name in ("_annotate", "_export", "_to_annotate"):
button = getattr(self, name, None)
if button is not None:
enabled = bool(can_link)
if name == "_to_annotate":
enabled = enabled and self.builder.canvas.selected_count() > 0
button.setEnabled(enabled)
if not can_link:
button.setToolTip("Image linking is unavailable without verified spaCR image/object provenance; tabular plotting and gating remain available.")
def _has_merge_image_provenance(self):
"""Whether image-specific actions can safely link the current derived rows."""
return not self._merge_definition or bool(
self._frame is not None and self._frame.attrs.get("image_provenance", False))