Rework radar system

This commit is contained in:
Ayzen
2026-10-06 12:32:26 +03:00
parent 552cdc688a
commit 5bb86b7165
299 changed files with 13537 additions and 27763 deletions
@@ -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)