Complete VIDEO-001 through VIDEO-004 software video studies

This commit is contained in:
LittleSam129
2026-09-16 18:27:35 +03:00
parent af7c05f2da
commit 95f664076a
403 changed files with 392009 additions and 0 deletions

113
tools/video_iq_replay.py Normal file
View File

@@ -0,0 +1,113 @@
"""VIDEO-001: лёгкий повтор сохранённого IQ через штатный приёмник Lab053.
Не обращается к SDR. Внутрь RX передаются только последовательные блоки IQ.
Ожидаемые SHA читаются лишь после строгого PHY/FEC/CRC и сборки JPEG.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import sys
import tempfile
import time
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
SAMPLE_RATE = 8_000_000
BLOCK_SAMPLES = 65_536
def replay(source: Path) -> dict:
from experiments.lab051_receiver_performance import ImageSink
from experiments.lab053_streaming_receiver import StreamingReceiver
receiver = StreamingReceiver(workers=1, candidate_capacity=4)
startup = dict(receiver.processor.startup)
sink = ImageSink()
images: list[dict] = []
bytes_read = 0
start = time.perf_counter()
def consume(packets) -> None:
for packet in packets:
item = sink.offer(packet, time.perf_counter() - start)
if item is not None:
images.append({"image_id": item["image_id"],
"sha256": hashlib.sha256(item["jpeg"]).hexdigest(),
"dimensions": item["dimensions"]})
try:
with source.open("rb") as stream:
while raw := stream.read(BLOCK_SAMPLES * 8):
if len(raw) % 8:
raise ValueError("IQ содержит неполный complex64 отсчёт")
bytes_read += len(raw)
consume(receiver.push_iq_block(np.frombuffer(raw, dtype=np.complex64)))
consume(receiver.finish())
elapsed = time.perf_counter() - start
packet_count = receiver.packet_count
candidate_count = receiver.candidate_count
finally:
receiver.close()
iq_seconds = bytes_read / 8 / SAMPLE_RATE
return {"startup": startup, "iq_bytes": bytes_read, "iq_seconds": iq_seconds,
"replay_wall_seconds": elapsed,
"replay_wall_over_iq_seconds": elapsed / iq_seconds,
"iq_seconds_over_replay_wall": iq_seconds / elapsed,
"total_with_jit_warmup_seconds": elapsed + startup["warmup_seconds"],
"accepted_packets": packet_count, "strict_candidates": candidate_count,
"completed_images": len(images), "images": images}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--numba-packages", required=True, type=Path)
parser.add_argument("--fixture-json", required=True, type=Path)
arguments = parser.parse_args()
if not arguments.source.is_file() or not arguments.fixture_json.is_file():
parser.error("IQ или зафиксированная ведомость JPEG отсутствует")
if not arguments.numba_packages.is_dir():
parser.error("Локальные пакеты Numba отсутствуют")
if arguments.source.stat().st_size % 8:
parser.error("IQ содержит неполный complex64 отсчёт")
sys.path.insert(0, str(arguments.numba_packages))
fixture = json.loads(arguments.fixture_json.read_text(encoding="utf-8"))
expected = {item["image_id"]: item["sha256"] for item in fixture["images"]}
with tempfile.TemporaryDirectory(prefix="video001_numba_") as cache:
os.environ["NUMBA_CACHE_DIR"] = cache
first = replay(arguments.source)
repeat = replay(arguments.source)
for result in (first, repeat):
result["exact_images_after_verdict"] = sum(
expected.get(item["image_id"]) == item["sha256"] for item in result["images"]
)
result["all_expected_images_exact"] = (
result["completed_images"] == len(expected)
and result["exact_images_after_verdict"] == len(expected)
)
result.pop("images")
document = {"status": "MEASURED_SAVED_IQ_NO_SDR", "source": str(arguments.source),
"source_bytes": arguments.source.stat().st_size,
"comparison": "Первый и повторный проходы в одном процессе; пустой JIT-кеш в начале",
"path_scope": "Время включает чтение файла, RX, сборку и JPEG decode; без графиков, послеблочной истории и сравнения SHA внутри измерения",
"first": first, "repeat": repeat, "rf_tx": 0}
arguments.output.parent.mkdir(parents=True, exist_ok=True)
arguments.output.write_text(json.dumps(document, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8")
print(json.dumps({label: {name: value[name] for name in ("startup", "replay_wall_seconds",
"iq_seconds_over_replay_wall", "accepted_packets", "completed_images",
"all_expected_images_exact")} for label, value in (("first", first), ("repeat", repeat))},
ensure_ascii=False), flush=True)
if __name__ == "__main__":
main()