UI updates
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtCore import QSignalBlocker, pyqtSignal
|
||||
from PyQt6.QtWidgets import (
|
||||
QComboBox,
|
||||
QDialog,
|
||||
@@ -23,10 +23,8 @@ import pyqtgraph as pg
|
||||
|
||||
from python_app.models.dataset_model import TraceData
|
||||
from python_app.orchestration.preprocess_assets import (
|
||||
PREPROCESS_ASSET_KEYS,
|
||||
PREPROCESS_ASSET_SPECS,
|
||||
S11_PREPROCESS_ASSET_KEYS,
|
||||
S21_PREPROCESS_ASSET_KEYS,
|
||||
VISIBLE_PREPROCESS_ASSET_KEYS,
|
||||
preprocess_asset_display_name,
|
||||
)
|
||||
|
||||
@@ -44,6 +42,10 @@ class PreprocessDialog(QDialog):
|
||||
"""Initialize window metadata and compose dialog UI."""
|
||||
super().__init__(parent)
|
||||
self._set_combos: dict[str, QComboBox] = {}
|
||||
self._preview_plot: pg.PlotWidget | None = None
|
||||
self._preview_placeholder: QLabel | None = None
|
||||
self._preview_host_layout: QVBoxLayout | None = None
|
||||
self._preview_plot_unavailable = False
|
||||
self._init_window()
|
||||
self._build_ui()
|
||||
|
||||
@@ -84,8 +86,7 @@ class PreprocessDialog(QDialog):
|
||||
header_row.addWidget(refresh_button)
|
||||
layout.addLayout(header_row)
|
||||
|
||||
layout.addWidget(self._build_selector_group("S21", S21_PREPROCESS_ASSET_KEYS, group))
|
||||
layout.addWidget(self._build_selector_group("S11", S11_PREPROCESS_ASSET_KEYS, group))
|
||||
layout.addWidget(self._build_selector_group("S21", VISIBLE_PREPROCESS_ASSET_KEYS, group))
|
||||
return group
|
||||
|
||||
def _build_selector_group(self, title: str, keys: tuple[str, ...], parent: QGroupBox) -> QGroupBox:
|
||||
@@ -110,35 +111,26 @@ class PreprocessDialog(QDialog):
|
||||
self._progress_label = QLabel("0 / 0", group)
|
||||
self._combo_label = QLabel("<none>", group)
|
||||
|
||||
self._tx_antenna_label_input = QLineEdit(group)
|
||||
self._rx_antenna_label_input = QLineEdit(group)
|
||||
self._tx_antenna_label_input.setPlaceholderText("e.g. TX_A")
|
||||
self._rx_antenna_label_input.setPlaceholderText("e.g. RX_B")
|
||||
|
||||
layout.addWidget(QLabel("Active type"), 0, 0)
|
||||
layout.addWidget(self._active_kind_label, 0, 1)
|
||||
layout.addWidget(QLabel("Progress"), 1, 0)
|
||||
layout.addWidget(self._progress_label, 1, 1)
|
||||
layout.addWidget(QLabel("Current combo"), 2, 0)
|
||||
layout.addWidget(self._combo_label, 2, 1)
|
||||
layout.addWidget(QLabel("TX antenna label"), 3, 0)
|
||||
layout.addWidget(self._tx_antenna_label_input, 3, 1)
|
||||
layout.addWidget(QLabel("RX antenna label"), 4, 0)
|
||||
layout.addWidget(self._rx_antenna_label_input, 4, 1)
|
||||
layout.addLayout(self._build_sequence_button_grid(group), 5, 0, 1, 2)
|
||||
layout.addLayout(self._build_sequence_action_row(group), 6, 0, 1, 2)
|
||||
layout.addLayout(self._build_sequence_button_grid(group), 3, 0, 1, 2)
|
||||
layout.addLayout(self._build_sequence_action_row(group), 4, 0, 1, 2)
|
||||
|
||||
self._capture_log = QPlainTextEdit(group)
|
||||
self._capture_log.setReadOnly(True)
|
||||
self._capture_log.setPlaceholderText("Capture history per combo")
|
||||
self._capture_log.setMinimumHeight(180)
|
||||
layout.addWidget(self._capture_log, 7, 0, 1, 2)
|
||||
layout.addWidget(self._capture_log, 5, 0, 1, 2)
|
||||
return group
|
||||
|
||||
def _build_sequence_button_grid(self, parent: QGroupBox) -> QGridLayout:
|
||||
"""Build per-asset capture start buttons."""
|
||||
layout = QGridLayout()
|
||||
for index, key in enumerate(PREPROCESS_ASSET_KEYS):
|
||||
for index, key in enumerate(VISIBLE_PREPROCESS_ASSET_KEYS):
|
||||
button = QPushButton(f"Start {preprocess_asset_display_name(key)}", parent)
|
||||
button.clicked.connect(lambda _checked=False, asset_key=key: self.start_sequence_requested.emit(asset_key))
|
||||
layout.addWidget(button, index // 2, index % 2)
|
||||
@@ -166,25 +158,32 @@ class PreprocessDialog(QDialog):
|
||||
root_layout.addWidget(self._status_label)
|
||||
|
||||
def _build_preview_plot(self, root_layout: QVBoxLayout) -> None:
|
||||
"""Build trace preview plot used after each successful capture."""
|
||||
self._preview_plot = pg.PlotWidget(background="#101418")
|
||||
self._preview_plot.showGrid(x=True, y=True, alpha=0.2)
|
||||
self._preview_plot.setLabel("bottom", "Frequency", units="Hz")
|
||||
self._preview_plot.setLabel("left", "Magnitude", units="dB")
|
||||
self._preview_plot.setMinimumHeight(320)
|
||||
root_layout.addWidget(self._preview_plot)
|
||||
"""Build lazy preview host used after each successful capture."""
|
||||
host = QWidget(self)
|
||||
layout = QVBoxLayout(host)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
placeholder = QLabel("Preview will appear after the first successful capture.", host)
|
||||
placeholder.setWordWrap(True)
|
||||
placeholder.setMinimumHeight(320)
|
||||
layout.addWidget(placeholder)
|
||||
|
||||
self._preview_host_layout = layout
|
||||
self._preview_placeholder = placeholder
|
||||
root_layout.addWidget(host)
|
||||
|
||||
def set_name(self) -> str:
|
||||
"""Return requested target set name."""
|
||||
return self._set_name_input.text().strip()
|
||||
|
||||
def set_set_name(self, value: str) -> None:
|
||||
"""Replace requested target set name."""
|
||||
self._set_name_input.setText(value)
|
||||
|
||||
def selection_snapshot(self) -> dict[str, str]:
|
||||
"""Return currently selected set names keyed by preprocess asset key."""
|
||||
return {key: self._set_combos[key].currentText().strip() for key in PREPROCESS_ASSET_KEYS}
|
||||
|
||||
def antenna_labels(self) -> tuple[str, str]:
|
||||
"""Return optional TX/RX user labels used in capture logs."""
|
||||
return self._tx_antenna_label_input.text().strip(), self._rx_antenna_label_input.text().strip()
|
||||
return {key: self._set_combos[key].currentText().strip() for key in VISIBLE_PREPROCESS_ASSET_KEYS}
|
||||
|
||||
def clear_capture_log(self) -> None:
|
||||
"""Clear capture history text box."""
|
||||
@@ -198,16 +197,11 @@ class PreprocessDialog(QDialog):
|
||||
total_count: int,
|
||||
input_pos: int,
|
||||
output_pos: int,
|
||||
tx_label: str,
|
||||
rx_label: str,
|
||||
) -> None:
|
||||
"""Append one capture progress row to dialog log."""
|
||||
tx_info = tx_label or "-"
|
||||
rx_info = rx_label or "-"
|
||||
self._capture_log.appendPlainText(
|
||||
f"{kind}: {captured_count}/{total_count} | "
|
||||
f"input={input_pos} output={output_pos} | "
|
||||
f"TX={tx_info} RX={rx_info}"
|
||||
f"input={input_pos} output={output_pos}"
|
||||
)
|
||||
|
||||
def set_capture_state(
|
||||
@@ -242,21 +236,26 @@ class PreprocessDialog(QDialog):
|
||||
|
||||
def set_available_sets(self, available_sets: dict[str, list[str]]) -> None:
|
||||
"""Replace combo-box choices for all preprocess assets."""
|
||||
for key in PREPROCESS_ASSET_KEYS:
|
||||
for key in VISIBLE_PREPROCESS_ASSET_KEYS:
|
||||
combo = self._set_combos[key]
|
||||
self._set_combo_items(combo, available_sets.get(key, []), combo.currentText().strip())
|
||||
with QSignalBlocker(combo):
|
||||
self._set_combo_items(combo, available_sets.get(key, []), combo.currentText().strip())
|
||||
|
||||
def set_selected_sets(self, selected_sets: dict[str, str]) -> None:
|
||||
"""Apply selected set names to all comboboxes and emit selection update."""
|
||||
for key in PREPROCESS_ASSET_KEYS:
|
||||
def set_selected_sets(self, selected_sets: dict[str, str], *, emit_signal: bool = True) -> None:
|
||||
"""Apply selected set names to all comboboxes and optionally emit update."""
|
||||
for key in VISIBLE_PREPROCESS_ASSET_KEYS:
|
||||
selected_value = selected_sets.get(key, "")
|
||||
if not selected_value:
|
||||
continue
|
||||
combo = self._set_combos[key]
|
||||
index = combo.findText(selected_value)
|
||||
if index >= 0:
|
||||
with QSignalBlocker(combo):
|
||||
index = combo.findText(selected_value)
|
||||
if index < 0:
|
||||
combo.addItem(selected_value)
|
||||
index = combo.findText(selected_value)
|
||||
combo.setCurrentIndex(index)
|
||||
self._emit_selection_changed()
|
||||
if emit_signal:
|
||||
self._emit_selection_changed()
|
||||
|
||||
def set_status(self, message: str) -> None:
|
||||
"""Set short human-readable status line."""
|
||||
@@ -266,17 +265,54 @@ class PreprocessDialog(QDialog):
|
||||
"""Draw the latest captured sweep trace for the requested channel in dB scale."""
|
||||
samples = trace.s11 if channel == "s11" else trace.s21
|
||||
magnitude_db = 20.0 * np.log10(np.maximum(np.abs(samples), 1e-12))
|
||||
self._preview_plot.clear()
|
||||
self._preview_plot.plot(
|
||||
trace.frequency_hz,
|
||||
magnitude_db,
|
||||
pen=pg.mkPen("#4cc9f0", width=1.8),
|
||||
)
|
||||
if self._ensure_preview_plot():
|
||||
assert self._preview_plot is not None
|
||||
self._preview_plot.clear()
|
||||
self._preview_plot.plot(
|
||||
trace.frequency_hz,
|
||||
magnitude_db,
|
||||
pen=pg.mkPen("#4cc9f0", width=1.8),
|
||||
)
|
||||
elif self._preview_placeholder is not None:
|
||||
self._preview_placeholder.setText(
|
||||
f"{title}\n"
|
||||
f"input={trace.combo.input_pos}, output={trace.combo.output_pos}, "
|
||||
f"points={trace.frequency_hz.size}\n"
|
||||
f"Preview plot is unavailable on this PyQtGraph/PyQt6 build."
|
||||
)
|
||||
combo = trace.combo
|
||||
self._status_label.setText(
|
||||
f"{title}: input={combo.input_pos}, output={combo.output_pos}, points={trace.frequency_hz.size}"
|
||||
)
|
||||
|
||||
def _ensure_preview_plot(self) -> bool:
|
||||
"""Create preview plot lazily and keep a text fallback when unavailable."""
|
||||
if self._preview_plot is not None:
|
||||
return True
|
||||
if self._preview_plot_unavailable:
|
||||
return False
|
||||
if self._preview_host_layout is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
plot = pg.PlotWidget(background="#101418", enableMenu=False)
|
||||
plot.showGrid(x=True, y=True, alpha=0.2)
|
||||
plot.setLabel("bottom", "Frequency", units="Hz")
|
||||
plot.setLabel("left", "Magnitude", units="dB")
|
||||
plot.setMinimumHeight(320)
|
||||
except Exception:
|
||||
self._preview_plot_unavailable = True
|
||||
return False
|
||||
|
||||
if self._preview_placeholder is not None:
|
||||
self._preview_host_layout.removeWidget(self._preview_placeholder)
|
||||
self._preview_placeholder.deleteLater()
|
||||
self._preview_placeholder = None
|
||||
|
||||
self._preview_plot = plot
|
||||
self._preview_host_layout.addWidget(plot)
|
||||
return True
|
||||
|
||||
def _emit_selection_changed(self) -> None:
|
||||
"""Emit current selection snapshot change."""
|
||||
self.selection_changed.emit()
|
||||
@@ -294,5 +330,7 @@ class PreprocessDialog(QDialog):
|
||||
if not current_text:
|
||||
return
|
||||
index = combo.findText(current_text)
|
||||
if index >= 0:
|
||||
combo.setCurrentIndex(index)
|
||||
if index < 0:
|
||||
combo.addItem(current_text)
|
||||
index = combo.findText(current_text)
|
||||
combo.setCurrentIndex(index)
|
||||
|
||||
Reference in New Issue
Block a user