chore(repo): cosim_dir replay revival + ruff lint cleanup

cosim_dir revival:
- gen_realdata_hex.py: also emit decimated_range_{i,q}.npy (48x512)
  and doppler_map_{i,q}.npy (512x48) at production dimensions; the
  same Python pipeline that produces the RTL .hex stimuli now writes
  the .npy intermediates v7.replay COSIM_DIR loads. Replaces the
  workflow lost when golden_reference.py was deleted in e8b495c
- test_v7.py: update test_get_frame_cosim shape from pre-PR-O.6
  (64,32) to (NUM_RANGE_BINS, NUM_DOPPLER_BINS)
- check in 4 .npy reference files (~400 KB, deterministic SCENE_SEED=42)

Ruff lint cleanup (was 66 errors; now 0):
- pyproject.toml: ignore T20 in tb/cosim/**.py (CLI tools)
- compare_independent.py: drop redundant int() casts (RUF046),
  swap try/except scipy import for importlib.util.find_spec,
  remove dead duplicate np import, ASCII-ize comment unicode,
  wrap E501 format strings
- fpga_reference.py: drop unused fs arg from nco_reference,
  collapse if/else to ternary, mark _out_im unused
- v7/processing.py: ASCII-ize x in docstring, collapse if-branches
- {dashboard,software_fpga,workers,radar_protocol}.py: wrap E501
- test_v7.py: ASCII-ize comment unicode, _alias renames where unused

Result: test_v7 100/100 (0 skips on radar_venv, was 9 graceful
skips); 5 cosim_dir orphan tests now active and passing.
This commit is contained in:
Jason
2026-05-02 15:45:56 +05:45
parent 5a7e8b8689
commit 3d2ffc3f2c
14 changed files with 107 additions and 62 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ DATA_PACKET_SIZE = 11 # 1 + 4 + 2 + 2 + 1 + 1
STATUS_PACKET_SIZE = 26 # 1 + 24 + 1
NUM_RANGE_BINS = 512
NUM_DOPPLER_BINS = 48 # PR-F/PR-Q: 3 sub-frames * 16 (matches FPGA RP_NUM_DOPPLER_BINS)
NUM_DOPPLER_BINS = 48 # PR-F/PR-Q: 3 sub-frames * 16 (= FPGA RP_NUM_DOPPLER_BINS)
NUM_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS # 24576
WATERFALL_DEPTH = 64
+10 -10
View File
@@ -483,7 +483,7 @@ class TestWaveformConfig(unittest.TestCase):
from v7.models import WaveformConfig
wc = WaveformConfig()
# MEDIUM has the largest per-subframe v_unamb (smallest PRI).
# K=6 default ~266 m/s; well above UAS speeds 5080 m/s.
# K=6 default -> ~266 m/s; well above UAS speeds 50-80 m/s.
v6 = wc.extended_max_velocity_mps_crt()
self.assertAlmostEqual(v6, wc.max_velocity_medium_mps * 6, places=2)
# K=3 should give half of K=6.
@@ -669,7 +669,7 @@ class TestSoftwareFPGASignalChain(unittest.TestCase):
from v7.software_fpga import SoftwareFPGA
from radar_protocol import NUM_RANGE_BINS, NUM_DOPPLER_BINS
# Production dimensions: 48 chirps × 2048 samples.
# Production dimensions: 48 chirps x 2048 samples.
iq_i = np.zeros((NUM_DOPPLER_BINS, 2048), dtype=np.int64)
iq_q = np.zeros((NUM_DOPPLER_BINS, 2048), dtype=np.int64)
# Inject a single strong tone in bin 10 of every chirp.
@@ -803,12 +803,12 @@ class TestReplayEngineCosim(unittest.TestCase):
if not self._available():
self.skipTest("co-sim data not found")
from v7.replay import ReplayEngine
from radar_protocol import RadarFrame
from radar_protocol import RadarFrame, NUM_RANGE_BINS, NUM_DOPPLER_BINS
engine = ReplayEngine(self.COSIM_DIR)
frame = engine.get_frame(0)
self.assertIsInstance(frame, RadarFrame)
self.assertEqual(frame.range_doppler_i.shape, (64, 32))
self.assertEqual(frame.magnitude.shape, (64, 32))
self.assertEqual(frame.range_doppler_i.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
self.assertEqual(frame.magnitude.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
def test_get_frame_out_of_range(self):
if not self._available():
@@ -849,7 +849,7 @@ class TestReplayEngineRawIQ(unittest.TestCase):
from v7.software_fpga import SoftwareFPGA
from radar_protocol import RadarFrame, NUM_RANGE_BINS, NUM_DOPPLER_BINS
# Production dimensions: 48 chirps × 2048 samples per frame.
# Production dimensions: 48 chirps x 2048 samples per frame.
raw = (np.random.randn(2, NUM_DOPPLER_BINS, 2048)
+ 1j * np.random.randn(2, NUM_DOPPLER_BINS, 2048))
with tempfile.NamedTemporaryFile(suffix=".npy", delete=False) as f:
@@ -1082,7 +1082,7 @@ class TestUnfoldVelocityCRT(unittest.TestCase):
v_unamb, v_res = self._vu_vr()
v_true = -75.0
v_meas = [_fold_v(v_true, vu) for vu in v_unamb]
v_est, conf, alias = unfold_velocity_crt(v_meas, v_unamb, v_res)
v_est, conf, _alias = unfold_velocity_crt(v_meas, v_unamb, v_res)
self.assertAlmostEqual(v_est, v_true, places=1)
self.assertEqual(conf, "CONFIRMED")
@@ -1105,7 +1105,7 @@ class TestUnfoldVelocityCRT(unittest.TestCase):
v_true = 25.0
# SHORT + MEDIUM only (LONG dropped out, e.g. clutter).
v_meas = [_fold_v(v_true, v_unamb[0]), _fold_v(v_true, v_unamb[1])]
v_est, conf, alias = unfold_velocity_crt(
v_est, conf, _alias = unfold_velocity_crt(
v_meas, [v_unamb[0], v_unamb[1]], [v_res[0], v_res[1]],
)
self.assertAlmostEqual(v_est, v_true, places=1)
@@ -1117,7 +1117,7 @@ class TestUnfoldVelocityCRT(unittest.TestCase):
v_unamb, v_res = self._vu_vr()
# Random per-PRI values that do not correspond to any v_true.
v_meas = [10.0, -30.0, 35.0]
v_est, conf, alias = unfold_velocity_crt(v_meas, v_unamb, v_res)
v_est, conf, _alias = unfold_velocity_crt(v_meas, v_unamb, v_res)
self.assertEqual(conf, "AMBIGUOUS")
self.assertAlmostEqual(v_est, 10.0, places=2) # PRI-0 fallback
@@ -1130,7 +1130,7 @@ class TestUnfoldVelocityCRT(unittest.TestCase):
# Pick v_true near the advertised CRT ceiling.
v_true = wc.extended_max_velocity_mps_crt(max_alias_k=6) - 5.0 # ~261 m/s
v_meas = [_fold_v(v_true, vu) for vu in v_unamb]
v_est, conf, alias = unfold_velocity_crt(v_meas, v_unamb, v_res, max_alias_k=6)
v_est, conf, _alias = unfold_velocity_crt(v_meas, v_unamb, v_res, max_alias_k=6)
self.assertAlmostEqual(v_est, v_true, places=0) # within 1 m/s
# Should still be CONFIRMED for a real velocity at this scale.
self.assertIn(conf, ("CONFIRMED", "LIKELY"))
+1 -1
View File
@@ -94,7 +94,7 @@ def _make_dspin() -> QDoubleSpinBox:
# =============================================================================
class RangeDopplerCanvas(FigureCanvasQTAgg):
"""Matplotlib canvas showing the Range-Doppler map (NUM_RANGE_BINS x NUM_DOPPLER_BINS) with dark theme."""
"""Matplotlib canvas showing the Range-Doppler map (NUM_RANGE_BINS x NUM_DOPPLER_BINS) with dark theme.""" # noqa: E501
def __init__(self, _parent=None):
fig = Figure(figsize=(10, 6), facecolor=DARK_BG)
+2 -4
View File
@@ -573,7 +573,7 @@ def unfold_velocity_crt(
alias depth k_0 [-K, K] generates candidates
``v_true = v_meas_0 + k_0 · 2 · v_unamb_0``. A candidate is
*valid* when it folds back into all other active PRIs to within
``tol_factor × max(v_res)``.
``tol_factor * max(v_res)``.
Args:
v_meas_per_sf: signed velocity measurement per active sub-frame
@@ -652,9 +652,7 @@ def unfold_velocity_crt(
confidence = "AMBIGUOUS"
elif n_sf == 3 and n_cands == 1:
confidence = "CONFIRMED"
elif n_sf == 3 and n_cands == 2:
confidence = "LIKELY"
elif n_sf == 2 and n_cands == 1:
elif (n_sf == 3 and n_cands == 2) or (n_sf == 2 and n_cands == 1):
confidence = "LIKELY"
else: # n_sf == 2 and n_cands == 2
confidence = "AMBIGUOUS"
+5 -1
View File
@@ -192,7 +192,11 @@ class SoftwareFPGA:
# to math-generated twiddles otherwise).
range_i = np.zeros((n_chirps, n_samples), dtype=np.int64)
range_q = np.zeros((n_chirps, n_samples), dtype=np.int64)
twiddle_path = TWIDDLE_2048 if (n_samples == 2048 and os.path.exists(TWIDDLE_2048)) else None
twiddle_path = (
TWIDDLE_2048
if (n_samples == 2048 and os.path.exists(TWIDDLE_2048))
else None
)
for c in range(n_chirps):
range_i[c], range_q[c] = run_range_fft(
iq_i[c].astype(np.int64),
+5 -1
View File
@@ -196,7 +196,11 @@ class RadarDataWorker(QThread):
# for SHORT/MEDIUM sub-frame bins until PR-Q.5 replaces this path
# with extract_targets_from_frame_crt.
v_res = self._waveform.velocity_resolution_long_mps
n_doppler = frame.detections.shape[1] if frame.detections.ndim == 2 else self._waveform.n_doppler_bins
n_doppler = (
frame.detections.shape[1]
if frame.detections.ndim == 2
else self._waveform.n_doppler_bins
)
doppler_center = n_doppler // 2
for idx in det_indices: