Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
+38 -33
View File
@@ -20,6 +20,7 @@ never see time jump backwards. ``collection_id`` increases forever.
from __future__ import annotations
import argparse
from contextlib import ExitStack
import json
import logging
from pathlib import Path
@@ -39,7 +40,7 @@ from python_app.storage.npz.serialize import RAW_MAGIC, serialize_trace_collecti
logger = logging.getLogger(__name__)
_SNAPSHOT_NAME = re.compile(r"^\d+_id\d+_ns(\d+)$")
_SNAPSHOT_NAME = re.compile(r"^(\d+)_id\d+_ns(\d+)$")
class ReplayDataset:
@@ -62,11 +63,11 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
if not dataset_dir.is_dir():
raise RuntimeError(f"radar.replay.dataset_dir is not a directory: {dataset_dir}")
named: list[tuple[str, int]] = []
for entry in sorted(dataset_dir.iterdir()):
named: list[tuple[int, str, int]] = []
for entry in dataset_dir.iterdir():
match = _SNAPSHOT_NAME.match(entry.name)
if entry.is_dir() and match:
named.append((entry.name, int(match.group(1))))
named.append((int(match.group(1)), entry.name, int(match.group(2))))
if not named:
raise RuntimeError(
f"No snapshot folders (NNNN_idK_ns<ts>) found in {dataset_dir}"
@@ -74,7 +75,7 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
snapshots: list[list[TraceData]] = []
timestamps_ns: list[int] = []
for name, timestamp_ns in named:
for _, name, timestamp_ns in sorted(named):
folder = dataset_dir / name
# Recorded per-trace capture windows from the snapshot's meta.json: the
# sar3d segmented timing model reconstructs the acquisition segments
@@ -102,10 +103,12 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
f"input={combo.input} output={combo.output}: expected "
f"{s21_path.name} and {freq_path.name}. Check run.combos vs the dataset."
)
s21 = np.load(s21_path).astype(np.complex64, copy=False)
freq = np.load(freq_path).astype(np.float32, copy=False)
if s21.size != freq.size:
raise RuntimeError(f"{s21_path}: s21 size {s21.size} != freq size {freq.size}")
s21 = np.load(s21_path, allow_pickle=False).astype(np.complex64, copy=False)
freq = np.load(freq_path, allow_pickle=False).astype(np.float32, copy=False)
s11_path = folder / f"i{combo.input}_o{combo.output}_s11.npy"
s11 = np.load(s11_path, allow_pickle=False).astype(np.complex64, copy=False) if s11_path.is_file() else np.zeros_like(s21)
if freq.ndim != 1 or s21.shape != freq.shape or s11.shape != freq.shape:
raise RuntimeError(f"Snapshot {name}: frequency, S11 and S21 must be equally sized 1D arrays")
if expected_points and s21.size != expected_points:
raise RuntimeError(
f"{s21_path}: {s21.size} points, but radar.sweep.points = "
@@ -116,7 +119,7 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
TraceData(
combo=combo,
frequency_hz=freq,
s11=np.zeros_like(s21),
s11=s11,
s21=s21,
capture_start_ns=window[0],
capture_end_ns=window[1],
@@ -206,25 +209,34 @@ def main() -> int:
dataset.median_gap_ns / 1e9, replay.loop, rate_scale,
)
raw_writer = ShmRingWriter(
config.rings.raw.name,
config.rings.raw.capacity,
config.rings.raw.slot_size_bytes,
)
raw_tap_writer = ShmRingWriter(
config.rings.raw_tap.name,
config.rings.raw_tap.capacity,
config.rings.raw_tap.slot_size_bytes,
)
cleanup = ExitStack()
try:
raw_writer = ShmRingWriter(
config.rings.raw.name,
config.rings.raw.capacity,
config.rings.raw.slot_size_bytes,
)
cleanup.callback(raw_writer.close)
raw_tap_writer = ShmRingWriter(
config.rings.raw_tap.name,
config.rings.raw_tap.capacity,
config.rings.raw_tap.slot_size_bytes,
)
cleanup.callback(raw_tap_writer.close)
payload_size = len(serialize_trace_collection(
_build_collection(dataset, 0, 1, time.monotonic_ns()), RAW_MAGIC
))
for ring in (config.rings.raw, config.rings.raw_tap):
if ring.slot_size_bytes < payload_size:
raise ValueError(f"Replay requires {payload_size} bytes per slot; ring {ring.name} has {ring.slot_size_bytes}")
base_ns = time.monotonic_ns()
wall_start_s = time.monotonic()
first_ts = dataset.timestamps_ns[0]
cycle_span_ns = dataset.timestamps_ns[-1] - first_ts + dataset.median_gap_ns
collection_id = 1
publish_failures = 0
cycle = 0
while not stop_requested.is_set():
for index in range(len(dataset)):
@@ -246,15 +258,9 @@ def main() -> int:
collection = _build_collection(dataset, index, collection_id, frame_ns)
payload = serialize_trace_collection(collection, RAW_MAGIC)
if raw_writer.push(payload):
raw_tap_writer.push(payload) # best-effort GUI tap; never fatal
else:
publish_failures += 1
if publish_failures == 1 or publish_failures % 100 == 0:
logger.error(
"Replay payload %d B exceeds ring slot %d B; dropping collection %d (drops=%d)",
len(payload), raw_writer.slot_size_bytes, collection_id, publish_failures,
)
if not raw_writer.push(payload):
raise RuntimeError(f"Replay raw ring rejected collection {collection_id}")
raw_tap_writer.push(payload)
if collection_id == 1 or collection_id % 50 == 0:
logger.info(
"replay collection %d published (snapshot %d/%d, cycle %d)",
@@ -268,8 +274,7 @@ def main() -> int:
break
cycle += 1
finally:
raw_tap_writer.close()
raw_writer.close()
cleanup.close()
logger.info("replay raw producer stopped")
return 0