Rework radar system
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""Select radar views and supply independent renderers with settings and history."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
from PyQt6.QtWidgets import QComboBox, QPushButton, QStackedWidget, QWidget
|
||||
|
||||
from python_app.gui.controllers.renderers.sar3d import Sar3dRenderer
|
||||
from python_app.gui.controllers.history_controller import HistoryController
|
||||
from python_app.gui.controllers.renderers.bscan import BscanRenderer
|
||||
from python_app.gui.controllers.renderers.gpr import GprRenderer
|
||||
from python_app.gui.controllers.renderers.trace import TraceRenderer
|
||||
from python_app.models.dataset_model import ResultCollection, TraceData
|
||||
from python_app.models.gui_profile_schema import GuiProcessingStateModel
|
||||
from python_app.models.run_config_schema import GprModel
|
||||
|
||||
|
||||
class PlotController:
|
||||
def __init__(
|
||||
self, history: HistoryController, settings: Callable[[], GuiProcessingStateModel],
|
||||
geometry: Callable[[], GprModel], running: Callable[[], bool],
|
||||
warning: Callable[..., None],
|
||||
) -> None:
|
||||
self.history = history
|
||||
self._settings = settings
|
||||
self._geometry = geometry
|
||||
self._running = running
|
||||
self._warning = warning
|
||||
self.required_revision: int | None = None
|
||||
self.live_window = 50
|
||||
self.bscan = BscanRenderer()
|
||||
self.gpr = GprRenderer()
|
||||
self.sar3d = Sar3dRenderer()
|
||||
self.trace = TraceRenderer(warning)
|
||||
self.stack = QStackedWidget()
|
||||
for renderer in (self.bscan, self.gpr, self.sar3d, self.trace):
|
||||
self.stack.addWidget(renderer.widget)
|
||||
self.stack.setCurrentWidget(self.trace.widget)
|
||||
self._combo: QComboBox | None = None
|
||||
self._next: QPushButton | None = None
|
||||
|
||||
def bind_display_controls(self, combo: QComboBox, next_button: QPushButton) -> None:
|
||||
self._combo, self._next = combo, next_button
|
||||
combo.currentIndexChanged.connect(self.on_display_combo_changed)
|
||||
next_button.clicked.connect(self.select_next_combo)
|
||||
self.sync_display_combos()
|
||||
|
||||
@property
|
||||
def display_key(self) -> tuple[int, int] | None:
|
||||
return self._combo.currentData() if self._combo is not None else None
|
||||
|
||||
@property
|
||||
def capture_widget(self) -> QWidget | None:
|
||||
current = self.stack.currentWidget()
|
||||
return self.sar3d.grid if current is self.sar3d.widget else current
|
||||
|
||||
def set_mode(self, mode: str) -> None:
|
||||
renderer = {"bscan": self.bscan, "gpr": self.gpr, "legacy_gpr": self.gpr, "sar3d": self.sar3d}.get(mode, self.trace)
|
||||
self.stack.setCurrentWidget(renderer.widget)
|
||||
self.sync_display_combos()
|
||||
|
||||
def select_combo(self, input_pos: int, output_pos: int) -> None:
|
||||
key = input_pos, output_pos
|
||||
keys = sorted(self.bscan.history_by_combo)
|
||||
if key not in keys or self._combo is None:
|
||||
raise ValueError(f"Unknown channel combination: {input_pos}:{output_pos}")
|
||||
self._combo.setCurrentIndex(keys.index(key))
|
||||
|
||||
def select_next_combo(self) -> None:
|
||||
combo = self._combo
|
||||
if combo is not None and combo.count():
|
||||
combo.setCurrentIndex((combo.currentIndex() + 1) % combo.count())
|
||||
|
||||
def sync_display_combos(self) -> None:
|
||||
combo, next_button = self._combo, self._next
|
||||
if combo is None or next_button is None:
|
||||
return
|
||||
keys = sorted(self.bscan.history_by_combo)
|
||||
current = combo.currentData()
|
||||
if keys != [combo.itemData(i) for i in range(combo.count())]:
|
||||
with QSignalBlocker(combo):
|
||||
combo.clear()
|
||||
for key in keys:
|
||||
combo.addItem(f"{key[0]} : {key[1]}", key)
|
||||
combo.setCurrentIndex(keys.index(current) if current in keys else 0)
|
||||
enabled = bool(keys) and self._settings().selected_mode == "bscan"
|
||||
combo.setEnabled(enabled)
|
||||
next_button.setEnabled(enabled and len(keys) > 1)
|
||||
|
||||
def window_scans(self) -> int:
|
||||
return self.live_window if self._running() else max(1, self._settings().bscan.history_window_scans)
|
||||
|
||||
def sync_bscan(self) -> None:
|
||||
results = self.history.bscan_source()
|
||||
self.bscan.sync(results, self.window_scans())
|
||||
self.sync_display_combos()
|
||||
|
||||
def draw_bscan(self) -> bool:
|
||||
self.sync_bscan()
|
||||
window, retained = self.window_scans(), len(self.history.preprocessed)
|
||||
if retained < window and self.bscan.history_by_combo:
|
||||
self._warning(
|
||||
f"B-scan is set to show {window} sweeps but only {retained} are retained; "
|
||||
"the image shows what history there is.",
|
||||
details="Capture more sweeps, or reduce 'Scans to show (stopped)'.",
|
||||
once_key=f"bscan_window_underfilled_{window}",
|
||||
)
|
||||
return self.bscan.draw(self.display_key, self._settings().bscan)
|
||||
|
||||
def on_display_combo_changed(self, *_args: object) -> None:
|
||||
if self._settings().selected_mode == "bscan":
|
||||
self.draw_bscan()
|
||||
|
||||
def draw(self, collection: ResultCollection) -> bool:
|
||||
settings = self._settings()
|
||||
mode = settings.selected_mode
|
||||
if mode in {"gpr", "legacy_gpr", "sar3d"} and self.required_revision is not None:
|
||||
if collection.settings_revision != self.required_revision:
|
||||
return False
|
||||
if mode == "bscan":
|
||||
return self.draw_bscan()
|
||||
if mode in {"gpr", "legacy_gpr"}:
|
||||
return self.gpr.draw(collection, settings.legacy_gpr if mode == "legacy_gpr" else settings.gpr, self._geometry())
|
||||
if mode == "sar3d":
|
||||
return self.sar3d.draw(collection)
|
||||
return self.trace.draw(collection, settings.pass_through)
|
||||
|
||||
def redraw(self, *_args: object) -> None:
|
||||
mode = self._settings().selected_mode
|
||||
if mode == "bscan":
|
||||
self.draw_bscan()
|
||||
elif mode == "sar3d":
|
||||
self.sar3d.clear()
|
||||
if self.history.results:
|
||||
self.draw(self.history.results[-1])
|
||||
elif not self.history.results or not self.draw(self.history.results[-1]):
|
||||
self.clear_current()
|
||||
|
||||
def draw_trace(self, trace: TraceData, title: str) -> None:
|
||||
self.trace.draw_trace(trace, title, self._settings().pass_through)
|
||||
|
||||
def clear_bscan(self) -> None:
|
||||
self.bscan.clear()
|
||||
self.sync_display_combos()
|
||||
|
||||
def clear_gpr(self) -> None:
|
||||
settings = self._settings()
|
||||
self.gpr.clear(settings.legacy_gpr if settings.selected_mode == "legacy_gpr" else settings.gpr)
|
||||
|
||||
def clear_current(self) -> None:
|
||||
mode = self._settings().selected_mode
|
||||
if mode == "bscan":
|
||||
self.clear_bscan()
|
||||
elif mode in {"gpr", "legacy_gpr"}:
|
||||
self.clear_gpr()
|
||||
elif mode == "sar3d":
|
||||
self.sar3d.clear()
|
||||
else:
|
||||
self.trace.clear()
|
||||
|
||||
def clear(self) -> None:
|
||||
self.clear_bscan()
|
||||
self.clear_gpr()
|
||||
self.sar3d.clear()
|
||||
self.trace.clear()
|
||||
|
||||
@staticmethod
|
||||
def has_output(collection: ResultCollection) -> bool:
|
||||
return bool(collection.collection_payloads) or any(p.kind == 1 for b in collection.blocks for p in b.payloads)
|
||||
Reference in New Issue
Block a user