Lab053: achieve real-time 5 fps software video receiver
This commit is contained in:
145
experiments/lab053_regression.py
Normal file
145
experiments/lab053_regression.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""Lab053: сравнение независимых решений Lab052/053 после полного приёма."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import numpy as np
|
||||
|
||||
from experiments import lab052_candidate_detection as baseline
|
||||
from experiments.lab053_receiver_profile import SOURCE, write_result
|
||||
from experiments.lab053_streaming_receiver import StreamingReceiver
|
||||
|
||||
|
||||
class VerdictObserver:
|
||||
def __init__(self):
|
||||
self.decisions = []
|
||||
self.frames = {}
|
||||
|
||||
def scores(self, *args):
|
||||
pass
|
||||
|
||||
def outcome(self, base, candidate, outcome):
|
||||
self.decisions.append((base + candidate.sample_offset, outcome.reason,
|
||||
base + outcome.refined_start_sample, outcome.accepted))
|
||||
if outcome.accepted:
|
||||
frame = outcome.frame
|
||||
self.frames[base + outcome.refined_start_sample] = {
|
||||
"end": base + outcome.next_sample, "timing": frame.timing_offset_samples,
|
||||
"cfo_hz": frame.estimated_cfo_hz, "phase_rad": frame.estimated_phase_rad,
|
||||
"polarity": frame.bpsk_polarity, "crc": outcome.crc,
|
||||
"packet_bytes": frame.reception.packet_bytes,
|
||||
"soft": frame.post_preamble_soft.copy()}
|
||||
|
||||
|
||||
def read_all(receiver, source=SOURCE):
|
||||
packets = []
|
||||
try:
|
||||
with source.open("rb") as capture:
|
||||
while raw := capture.read(receiver.block_samples * 8):
|
||||
packets.extend(receiver.push_iq_block(np.frombuffer(raw, np.complex64)))
|
||||
packets.extend(receiver.finish())
|
||||
return packets
|
||||
finally:
|
||||
if hasattr(receiver, "close"):
|
||||
receiver.close()
|
||||
|
||||
|
||||
def equivalence():
|
||||
old = VerdictObserver()
|
||||
read_all(baseline.StreamingReceiver(observer=old))
|
||||
results = []
|
||||
for workers in (1, 2, 4):
|
||||
new = VerdictObserver()
|
||||
receiver = StreamingReceiver(workers=workers, observer=new)
|
||||
read_all(receiver)
|
||||
same_starts = set(old.frames) == set(new.frames)
|
||||
rows = []
|
||||
for start in sorted(set(old.frames) & set(new.frames)):
|
||||
first, second = old.frames[start], new.frames[start]
|
||||
rows.append({"start_sample": start,
|
||||
**{f"same_{key}": first[key] == second[key] for key in ("end", "timing", "polarity", "crc", "packet_bytes")},
|
||||
"cfo_difference_hz": abs(first["cfo_hz"] - second["cfo_hz"]),
|
||||
"phase_difference_rad": abs(first["phase_rad"] - second["phase_rad"]),
|
||||
"maximum_soft_difference": float(np.max(np.abs(first["soft"] - second["soft"]))),
|
||||
"packet_sha256": hashlib.sha256(second["packet_bytes"]).hexdigest()})
|
||||
checks = {"same_240_starts": same_starts and len(rows) == 240,
|
||||
"same_candidate_policy_sequence": old.decisions == new.decisions,
|
||||
"same_phy_timing_crc_packet_bits": all(all(row[f"same_{key}"] for key in
|
||||
("end", "timing", "polarity", "crc", "packet_bytes")) for row in rows),
|
||||
"cfo_within_002_hz": all(row["cfo_difference_hz"] < .002 for row in rows),
|
||||
"phase_within_2e5_rad": all(row["phase_difference_rad"] < 2e-5 for row in rows),
|
||||
"soft_within_1e4": all(row["maximum_soft_difference"] < 1e-4 for row in rows)}
|
||||
result = {"workers": workers, "checks": checks, "passed": all(checks.values()),
|
||||
"strict_candidates": receiver.candidate_count, "accepted": receiver.packet_count,
|
||||
"maximum_cfo_difference_hz": max(row["cfo_difference_hz"] for row in rows),
|
||||
"maximum_phase_difference_rad": max(row["phase_difference_rad"] for row in rows),
|
||||
"maximum_soft_difference": max(row["maximum_soft_difference"] for row in rows), "packets": rows}
|
||||
results.append(result)
|
||||
print(json.dumps({key: value for key, value in result.items() if key != "packets"}), flush=True)
|
||||
write_result("equivalence.json", {"baseline": "committed Lab052", "workers": results,
|
||||
"passed": all(row["passed"] for row in results), "oracle_used_in_receiver": False, "rf_tx": 0})
|
||||
|
||||
|
||||
def sensitivity():
|
||||
physical = baseline.phy.lab045
|
||||
payload = bytes(range(256))
|
||||
plan = physical.build_transmission_plan(payload)
|
||||
raw = baseline.phy.build_parameterized_tx_cs8(plan, baseline.phy.PhyMode(500000))
|
||||
components = np.frombuffer(raw, np.int8).astype(np.float32)
|
||||
clean = components[0::2] + 1j * components[1::2]
|
||||
energy = float(np.sum(np.abs(clean) ** 2)) / plan.frame_symbol_count
|
||||
rows = []
|
||||
for ebn0 in (0, 1, 2, 3, 7):
|
||||
for cfo in (-100, 10, 100):
|
||||
for seed, scale in ((52031, .002), (52032, .2), (52033, 1.)):
|
||||
random_generator = np.random.default_rng(seed)
|
||||
sigma = np.sqrt(energy / (2 * 10 ** (ebn0 / 10)))
|
||||
phase = .73 + (seed % 2) * np.pi
|
||||
values = clean * np.exp(1j * (phase + 2*np.pi*cfo*np.arange(len(clean))/8000000))
|
||||
values += sigma * (random_generator.normal(size=len(clean)) + 1j*random_generator.normal(size=len(clean)))
|
||||
samples = np.concatenate((np.zeros(63217, np.complex64), (values * scale).astype(np.complex64),
|
||||
np.zeros(350000, np.complex64)))
|
||||
verdicts = []
|
||||
for constructor in (baseline.StreamingReceiver, StreamingReceiver):
|
||||
receiver = constructor()
|
||||
packets = []
|
||||
try:
|
||||
for start in range(0, len(samples), receiver.block_samples):
|
||||
packets.extend(receiver.push_iq_block(samples[start:start + receiver.block_samples]))
|
||||
packets.extend(receiver.finish())
|
||||
finally:
|
||||
if hasattr(receiver, "close"):
|
||||
receiver.close()
|
||||
verdicts.append(len(packets) == 1 and packets[0].payload == payload)
|
||||
rows.append({"ebn0_db": ebn0, "cfo_hz": cfo, "seed": seed, "scale": scale,
|
||||
"baseline_correct": verdicts[0], "optimized_correct": verdicts[1]})
|
||||
result = {"count": len(rows), "baseline_correct": sum(row["baseline_correct"] for row in rows),
|
||||
"optimized_correct": sum(row["optimized_correct"] for row in rows),
|
||||
"lost_baseline_packets": sum(row["baseline_correct"] and not row["optimized_correct"] for row in rows),
|
||||
"same_verdicts": all(row["baseline_correct"] == row["optimized_correct"] for row in rows),
|
||||
"cases": rows, "rf_tx": 0}
|
||||
write_result("sensitivity.json", result)
|
||||
print(json.dumps({key: value for key, value in result.items() if key != "cases"}), flush=True)
|
||||
|
||||
|
||||
def run_functional_tests() -> None:
|
||||
assert VerdictObserver().decisions == []
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--stage", choices=("equivalence", "sensitivity"), default="equivalence")
|
||||
arguments = parser.parse_args()
|
||||
{"equivalence": equivalence, "sensitivity": sensitivity}[arguments.stage]()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user