mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-11 07:51:17 +00:00
feat(gui): M-1 / PR-Q.7 — dashboard CRT confidence column + alias-fold tooltip (C-5)
The CRT extractor (PR-Q.5/PR-Q.6) tags every target with a velocity_confidence
("CONFIRMED" / "LIKELY" / "AMBIGUOUS" / "UNKNOWN") and an optional alias_set
of candidate v_true folds. Until now the operator-facing targets table on the
Main View tab dropped that signal, so a single-PRI-only AMBIGUOUS reading
looked identical to a 3-PRI CONFIRMED one.
Changes:
- Targets table column count 5 -> 6; new "Confidence" column between
Velocity and Magnitude.
- Module helper _confidence_display(label) -> (text, QColor):
CONFIRMED green (DARK_SUCCESS)
LIKELY amber (DARK_WARNING)
AMBIGUOUS red (DARK_ERROR), prefixed with "? " so the row stands
out even when the operator's eyes skip the colour.
UNKNOWN gray (DARK_TEXT) — legacy 32-bin / no CRT.
Unrecognised future labels fall through to UNKNOWN.
- Velocity cell carries a tooltip listing the CRT alias_set folds when
present, so hovering reveals all plausible v_true candidates.
- QColor pulled in from PyQt6.QtGui for the foreground tint.
Tests (TestDashboardConfidenceDisplay, +5):
- CONFIRMED/LIKELY/AMBIGUOUS/UNKNOWN each map to expected text + colour.
- AMBIGUOUS leads with "?" so it's visible without colour.
- Unrecognised label "BANANA" falls back to UNKNOWN/gray.
Regression: 237/237 (test_v7 120 + test_GUI_V65_Tk 117). Ruff clean.
This closes audit M-1 / task PR-Q.7. The C-5 thread is end-to-end functional:
RTL emits 3 sub-frames (PR-Q.1) -> cosim agrees (PR-Q.2) -> v7 models carry
per-subframe params (PR-Q.4) -> processing.py runs CRT (PR-Q.5) -> workers
route through it (PR-Q.6) -> dashboard surfaces the confidence (PR-Q.7).
This commit is contained in:
@@ -1581,6 +1581,52 @@ class TestWorkersRouteThroughCrt(unittest.TestCase):
|
|||||||
self.assertIs(worker._extract_targets, extract_targets_from_frame_crt)
|
self.assertIs(worker._extract_targets, extract_targets_from_frame_crt)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Test: PR-Q.7 / audit M-1 — dashboard confidence column display helper
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@unittest.skipUnless(_pyqt6_available(), "PyQt6 not installed")
|
||||||
|
class TestDashboardConfidenceDisplay(unittest.TestCase):
|
||||||
|
"""_confidence_display maps RadarTarget.velocity_confidence to (text, QColor)."""
|
||||||
|
|
||||||
|
def test_confirmed_is_green_no_prefix(self):
|
||||||
|
from v7.dashboard import _confidence_display
|
||||||
|
from v7.models import DARK_SUCCESS
|
||||||
|
text, color = _confidence_display("CONFIRMED")
|
||||||
|
self.assertEqual(text, "CONFIRMED")
|
||||||
|
self.assertEqual(color.name().upper(), DARK_SUCCESS.upper())
|
||||||
|
|
||||||
|
def test_likely_is_amber(self):
|
||||||
|
from v7.dashboard import _confidence_display
|
||||||
|
from v7.models import DARK_WARNING
|
||||||
|
text, color = _confidence_display("LIKELY")
|
||||||
|
self.assertEqual(text, "LIKELY")
|
||||||
|
self.assertEqual(color.name().upper(), DARK_WARNING.upper())
|
||||||
|
|
||||||
|
def test_ambiguous_gets_question_mark_prefix_and_red(self):
|
||||||
|
from v7.dashboard import _confidence_display
|
||||||
|
from v7.models import DARK_ERROR
|
||||||
|
text, color = _confidence_display("AMBIGUOUS")
|
||||||
|
self.assertTrue(text.startswith("?"),
|
||||||
|
"AMBIGUOUS must lead with '?' so it's visible without color")
|
||||||
|
self.assertIn("AMBIGUOUS", text)
|
||||||
|
self.assertEqual(color.name().upper(), DARK_ERROR.upper())
|
||||||
|
|
||||||
|
def test_unknown_falls_back_to_text_color(self):
|
||||||
|
from v7.dashboard import _confidence_display
|
||||||
|
from v7.models import DARK_TEXT
|
||||||
|
text, color = _confidence_display("UNKNOWN")
|
||||||
|
self.assertEqual(text, "UNKNOWN")
|
||||||
|
self.assertEqual(color.name().upper(), DARK_TEXT.upper())
|
||||||
|
|
||||||
|
def test_unrecognised_label_falls_through_to_unknown(self):
|
||||||
|
from v7.dashboard import _confidence_display
|
||||||
|
from v7.models import DARK_TEXT
|
||||||
|
text, color = _confidence_display("BANANA")
|
||||||
|
self.assertEqual(text, "UNKNOWN")
|
||||||
|
self.assertEqual(color.name().upper(), DARK_TEXT.upper())
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Helper: lazy import of v7.models
|
# Helper: lazy import of v7.models
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QPlainTextEdit, QStatusBar, QMessageBox,
|
QPlainTextEdit, QStatusBar, QMessageBox,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QLocale, QTimer, pyqtSignal, pyqtSlot, QObject
|
from PyQt6.QtCore import Qt, QLocale, QTimer, pyqtSignal, pyqtSlot, QObject
|
||||||
|
from PyQt6.QtGui import QColor
|
||||||
|
|
||||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
@@ -87,6 +88,31 @@ def _make_dspin() -> QDoubleSpinBox:
|
|||||||
return sb
|
return sb
|
||||||
|
|
||||||
|
|
||||||
|
# Confidence label colors for the targets table (PR-Q.7 / audit M-1).
|
||||||
|
# CONFIRMED green — 3 sub-frames agreed via CRT
|
||||||
|
# LIKELY amber — 2 of 3 sub-frames agreed
|
||||||
|
# AMBIGUOUS red — only 1 sub-frame saw it; v_unamb-bounded reading
|
||||||
|
# UNKNOWN gray — legacy 32-bin frame (no CRT was attempted)
|
||||||
|
_CONFIDENCE_COLORS: dict[str, str] = {
|
||||||
|
"CONFIRMED": DARK_SUCCESS,
|
||||||
|
"LIKELY": DARK_WARNING,
|
||||||
|
"AMBIGUOUS": DARK_ERROR,
|
||||||
|
"UNKNOWN": DARK_TEXT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _confidence_display(confidence: str) -> tuple[str, QColor]:
|
||||||
|
"""Map a RadarTarget.velocity_confidence string to (cell text, QColor).
|
||||||
|
|
||||||
|
AMBIGUOUS gets a leading "?" so it stands out in a long target list even
|
||||||
|
if the operator's eyes skip the color cue. Unknown confidence labels
|
||||||
|
(e.g. from a future model) fall back to "UNKNOWN" gray.
|
||||||
|
"""
|
||||||
|
label = confidence if confidence in _CONFIDENCE_COLORS else "UNKNOWN"
|
||||||
|
text = f"? {label}" if label == "AMBIGUOUS" else label
|
||||||
|
return text, QColor(_CONFIDENCE_COLORS[label])
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Range-Doppler Canvas (matplotlib)
|
# Range-Doppler Canvas (matplotlib)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -486,9 +512,10 @@ class RadarDashboard(QMainWindow):
|
|||||||
tg_layout = QVBoxLayout(targets_group)
|
tg_layout = QVBoxLayout(targets_group)
|
||||||
|
|
||||||
self._targets_table_main = QTableWidget()
|
self._targets_table_main = QTableWidget()
|
||||||
self._targets_table_main.setColumnCount(5)
|
self._targets_table_main.setColumnCount(6)
|
||||||
self._targets_table_main.setHorizontalHeaderLabels([
|
self._targets_table_main.setHorizontalHeaderLabels([
|
||||||
"Range (m)", "Velocity (m/s)", "Magnitude", "SNR (dB)", "Track ID",
|
"Range (m)", "Velocity (m/s)", "Confidence",
|
||||||
|
"Magnitude", "SNR (dB)", "Track ID",
|
||||||
])
|
])
|
||||||
self._targets_table_main.setAlternatingRowColors(True)
|
self._targets_table_main.setAlternatingRowColors(True)
|
||||||
self._targets_table_main.setSelectionBehavior(
|
self._targets_table_main.setSelectionBehavior(
|
||||||
@@ -1961,16 +1988,31 @@ class RadarDashboard(QMainWindow):
|
|||||||
for row, t in enumerate(targets):
|
for row, t in enumerate(targets):
|
||||||
self._targets_table_main.setItem(
|
self._targets_table_main.setItem(
|
||||||
row, 0, QTableWidgetItem(f"{t.range:.0f}"))
|
row, 0, QTableWidgetItem(f"{t.range:.0f}"))
|
||||||
self._targets_table_main.setItem(
|
|
||||||
row, 1, QTableWidgetItem(f"{t.velocity:.0f}"))
|
# PR-Q.7: velocity cell carries an alias-set tooltip so the operator
|
||||||
|
# can hover to see all CRT candidate folds; useful when confidence
|
||||||
|
# is LIKELY/AMBIGUOUS and the displayed velocity is just the best
|
||||||
|
# of several plausible v_true values.
|
||||||
|
vel_item = QTableWidgetItem(f"{t.velocity:.0f}")
|
||||||
|
if t.alias_set:
|
||||||
|
fold_strs = ", ".join(f"{v:+.1f}" for v in t.alias_set)
|
||||||
|
vel_item.setToolTip(f"CRT alias folds (m/s): {fold_strs}")
|
||||||
|
self._targets_table_main.setItem(row, 1, vel_item)
|
||||||
|
|
||||||
|
# PR-Q.7: confidence column. CONFIRMED = green, LIKELY = amber,
|
||||||
|
# AMBIGUOUS = red with leading "?", UNKNOWN = gray (legacy single-PRI).
|
||||||
|
conf_text, conf_color = _confidence_display(t.velocity_confidence)
|
||||||
|
conf_item = QTableWidgetItem(conf_text)
|
||||||
|
conf_item.setForeground(conf_color)
|
||||||
|
self._targets_table_main.setItem(row, 2, conf_item)
|
||||||
|
|
||||||
mag_val = 10 ** (t.snr / 10) if t.snr > 0 else 0
|
mag_val = 10 ** (t.snr / 10) if t.snr > 0 else 0
|
||||||
self._targets_table_main.setItem(
|
self._targets_table_main.setItem(
|
||||||
row, 2, QTableWidgetItem(f"{mag_val:.0f}"))
|
row, 3, QTableWidgetItem(f"{mag_val:.0f}"))
|
||||||
self._targets_table_main.setItem(
|
self._targets_table_main.setItem(
|
||||||
row, 3, QTableWidgetItem(f"{t.snr:.1f}"))
|
row, 4, QTableWidgetItem(f"{t.snr:.1f}"))
|
||||||
self._targets_table_main.setItem(
|
self._targets_table_main.setItem(
|
||||||
row, 4, QTableWidgetItem(str(t.track_id)))
|
row, 5, QTableWidgetItem(str(t.track_id)))
|
||||||
|
|
||||||
def _update_diagnostics(self):
|
def _update_diagnostics(self):
|
||||||
# Connection indicators
|
# Connection indicators
|
||||||
|
|||||||
Reference in New Issue
Block a user