Lab052: suppress false preamble candidates
This commit is contained in:
144
PROJECT_LOG.md
144
PROJECT_LOG.md
@@ -1859,3 +1859,147 @@ RF TX=0; HackRF,180 МГц,QPSK,новый FEC/кодек и аппаратны
|
|||||||
стоимости уникальных фоновых кандидатов в полном режиме5 изображений/с,
|
стоимости уникальных фоновых кандидатов в полном режиме5 изображений/с,
|
||||||
с неизменными PHY/FEC/CRC и независимыми положительными/отрицательными регрессиями.
|
с неизменными PHY/FEC/CRC и независимыми положительными/отрицательными регрессиями.
|
||||||
До отдельного разрешения не выполнять RF или аппаратные500 ксимв/с.
|
До отдельного разрешения не выполнять RF или аппаратные500 ксимв/с.
|
||||||
|
|
||||||
|
# Запись 041
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
8 сентября 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Lab052 — уменьшение числа ложных кандидатов преамбулы при 500 ксимв/с,
|
||||||
|
сохранение слабых пакетов и повторная проверка свежести JPEG
|
||||||
|
|
||||||
|
## Выполнено
|
||||||
|
|
||||||
|
Перед новой разработкой проверены root/main/HEAD/status, diff stat/check,
|
||||||
|
фактический код Lab051, отчёт, индекс и запись 040. Повторные шлюзы Lab051:
|
||||||
|
quick_gate PASS, pytest 688 passed, 0 failed. Verifier подтвердил scope.
|
||||||
|
По прямому разрешению нового задания показан staged scope из 13 файлов,
|
||||||
|
создан коммит 9f33607d3ae82992c595de37dd64e99d7e4dea73:
|
||||||
|
Lab051: accelerate 500 ksymbol/s streaming receiver.
|
||||||
|
9695 добавлений; код, тесты, документация и шесть итоговых JSON Lab051.
|
||||||
|
IQ-окна, промежуточные результаты и посторонние файлы не добавлены; push не было.
|
||||||
|
Lab052 начата от этого фактического HEAD.
|
||||||
|
|
||||||
|
Рабочий режим сохранён: BPSK, 500000 символов/с, Fs=8000000, 16 отсчётов/символ,
|
||||||
|
JPEG C 128×96 RGB q25, 1740–1893 байта. Управление ELRS/CRSF остаётся отдельно.
|
||||||
|
Ни один файл protocol, старые модули PHY/FEC/CRC или формат фрагмента не изменён.
|
||||||
|
|
||||||
|
Уточнена исходная метрика: исторические 147082 Lab051 — все строгие кандидаты,
|
||||||
|
включая 240 valid; ложных было 146842. Новые timing-окна 12239 также включают
|
||||||
|
настоящие. Прежняя длинная сетка зависела от несохранённых encode_seconds.
|
||||||
|
В Lab052 тот же генератор с seed50050/CFO10/фазой0,73/чередованием полярности
|
||||||
|
использует один сохранённый замер готовности JPEG; SHA всех изображений проверяется.
|
||||||
|
Все сравниваемые варианты получают одну и ту же новую воспроизводимую сетку.
|
||||||
|
Точное историческое размещение пакетов не объявляется восстановленным.
|
||||||
|
|
||||||
|
Профиль прежнего Lab051 на 6,044416 с IQ:1675842 локальных пика,
|
||||||
|
392785 выше первого порога,147005 строгих проверок,240true/146765false.
|
||||||
|
24281,089 ложных проверок на секунду IQ. Ложные кандидаты дают11387 уникальных
|
||||||
|
уточнённых позиций и135378 повторных. Низкий нормированный порог пропускает шум
|
||||||
|
независимо от его абсолютной амплитуды; повторная PRBS7 создаёт сильные боковые пики.
|
||||||
|
|
||||||
|
Построены распределения нормированной корреляции, median/MAD и квантили по блокам.
|
||||||
|
Медиана false score0,035464; главный true peak0,841691. В357 блоках чистого фона,
|
||||||
|
выделенных только после независимых решений, медиана блочных медиан0,016353,
|
||||||
|
MAD0,006184; фон не идеально стационарен. Адаптивный median/MAD-порог рассмотрен,
|
||||||
|
но не выбран: он не устраняет структуру повторных пиков, а чувствительность
|
||||||
|
при смене фона требует отдельного доказательства. Нормировка энергией каждой
|
||||||
|
половины использует только наблюдаемый IQ, без TX reference.
|
||||||
|
|
||||||
|
Первый вариант подавлял меньшие пики в радиусе преамбулы и давал0false на основном
|
||||||
|
потоке, но adversary воспроизвёл потерю слабого VALID рядом с сильной ложной
|
||||||
|
преамбулой без правильного заголовка. Этот вариант отвергнут. Исправление:
|
||||||
|
сначала общие дешёвые корреляции всех локальных кандидатов по частям, затем
|
||||||
|
ограниченная полная ширина группы; FAIL представителя сохраняет альтернативы.
|
||||||
|
Прежний контрпример и его перенос через границы блоков больше не теряют пакет.
|
||||||
|
|
||||||
|
Основное правило: обе половины преамбулы проходят нормированную корреляцию
|
||||||
|
не ниже0,08474038063392896. Порог выбран заранее из номинальной модели
|
||||||
|
Beta(1,N−1),N640,alpha0,01; точная вероятность после фильтрации/отбора пиков
|
||||||
|
не утверждается. Две FFT частей общие на блок, per-candidate timing не запускается
|
||||||
|
на отклонённом шуме. Исходный первый порог0.030964473262429237 сохранён.
|
||||||
|
Фаза/полярность неизвестны, используется модуль; новый фазовый CFO-gate не введён.
|
||||||
|
|
||||||
|
Группа имеет полную ширину≤20480 отсчётов от первого пика, без цепочного расширения.
|
||||||
|
Минимальный допустимый кадр26720 отсчётов;20480<26720−2×2008=22704.
|
||||||
|
Это разделяет центральные кандидаты двух последовательных неперекрывающихся
|
||||||
|
кадров в диапазоне прежней точности локализации. Представитель — наибольшая
|
||||||
|
корреляция, tie→правый; при отказе проверяется следующая альтернатива.
|
||||||
|
Только настоящий PHY/FEC/CRC PASS разрешает пропуск принятого физического кадра.
|
||||||
|
Граница IQ-блока может добавить проверки, но не скрывает следующий кандидат.
|
||||||
|
|
||||||
|
Сравнены0/1/2/3 части. Ложных строгих проверок145863/332/15/6, во всех вариантах
|
||||||
|
сохранены те же240 уникальных уточнённых стартов и30 JPEG SHA MATCH.
|
||||||
|
Выбраны две части: минимальная проверка распределённой структуры,9784,33×
|
||||||
|
сокращение false. Третья часть уменьшает15до6, но дороже общего этапа.
|
||||||
|
Группировка без дешёвого фильтра и с корректным возвратом альтернатив почти
|
||||||
|
не снижает нагрузку распределённого шума. Глобальный peak/second не используется.
|
||||||
|
|
||||||
|
Обнаружены734 лишних построения неизменной опорной формы на каждый проход.
|
||||||
|
Введены локальные постоянные кэши опоры,RRC taps,PRBS bits,171140 байт.
|
||||||
|
Массивы побитно сравнены с прежними, глобальные функции возвращаются в finally.
|
||||||
|
Сами PHY,16 samples/symbol,все1601 timing-offset,soft Viterbi и CRC не менялись.
|
||||||
|
|
||||||
|
Три чистых полных прохода:processing/RF1,257688/1,219199/1,219273,
|
||||||
|
в каждом240/240 пакетов,30/30 точных JPEG,15false,0 потерь. Итог — целиком
|
||||||
|
медианный третий проход:7,369795 с обработки /6,044416 с IQ=1,219273.
|
||||||
|
Свежий прежний Lab051 без наблюдателя на той же сетке41,633312 с,ratio6,887897;
|
||||||
|
полное ускорение5,649×, относительно исторической Lab051 —5,242×.
|
||||||
|
|
||||||
|
Итоговые счётчики:392785 первого этапа,388407 отклонены дешёво,4378 сохранены,
|
||||||
|
384 группы,243 строгих представителя+12 альтернатив,15false/240true.
|
||||||
|
4123 кандидата пропущены внутри уже принятого физического кадра.
|
||||||
|
Строгих проверок42,187699/с IQ,ложных2,481629/с; новых soft-окон488.
|
||||||
|
Настоящие строгие проверки3,996005 с,среднее16,650 мс; ложные всего28,884 мс.
|
||||||
|
Общая децимация/корреляция1,790240 с,проверка частей0,446616 с.
|
||||||
|
|
||||||
|
INPUT5fps/30images;RECEIVED30;STALE30;DISPLAYED=FRESH DISPLAYED=0;FRESH FPS=0.
|
||||||
|
Возраст декодированных, затем отброшенных JPEG234,520/834,077/1549,595 мс.
|
||||||
|
MAX_VIDEO_AGE200 мс. Реальная измеренная стоимость каждого push и сборки/decode
|
||||||
|
использована в прежней событийной шкале. Нулевая стоимость RX не подставлялась.
|
||||||
|
Цель реального времени и5 свежих кадров/с НЕ достигнута. Теперь ограничение —
|
||||||
|
настоящие пакеты вместе с децимацией,FFT и копированием IQ,а не ложные кандидаты.
|
||||||
|
Финальный отдельный cProfile:FFT1,650 с,FIR1,295 с,Viterbi1,397 с собственного
|
||||||
|
времени. Вложенные matched/refine не суммируются повторно.
|
||||||
|
|
||||||
|
Камера/экран UNKNOWN;capture lookup применяется после JPEG только для отчётной
|
||||||
|
модели вывода. В неизменном fragment format нет capture timestamp, независимая
|
||||||
|
live-проверка возраста не реализована. Отставание1,426779 с соответствовало бы
|
||||||
|
91313848 байтам внешнего IQ backlog. Такой буфер не хранится офлайн-источником;
|
||||||
|
bounded state не объявляется готовой live-очередью без overflow/gap policy.
|
||||||
|
|
||||||
|
Пик удерживаемого IQ3177728 байт(3,031 МиБ),проверяемый предел3398912 байт.
|
||||||
|
Инженерная оценка рабочих массивов136,161 МиБ,не универсальный предел C-аллокаций.
|
||||||
|
RSS выбранного процесса после потока149393408 байт,peak168804352(160,984 МиБ).
|
||||||
|
Короткий отдельный replay измерил6754 наблюдаемых ndarray(6681 владельцев),
|
||||||
|
37378942 суммарных байта владельцев иtracemalloc peak11586792 байта.
|
||||||
|
Число массивов — нижняя граница Python-наблюдения; tracing не входит в CPU benchmark.
|
||||||
|
|
||||||
|
Длинный тест2048 блоков,16,777216 с распределённого шума со сменой амплитуды:
|
||||||
|
4783206 пиков,1068038 кандидатов,одна строгая ложная проверка,0 принятых пакетов.
|
||||||
|
Во всех контрольных точках после прогрева IQ2653440 байт,децимация397216,
|
||||||
|
сохранённых кандидатов0. RSS после прогрева кэшей стабилизируется.
|
||||||
|
После finish IQ-буферы освобождены. Основной поток:не более23 eligible пиков
|
||||||
|
и3 групп на блок; история наблюдателя существует только вне рабочего RX.
|
||||||
|
|
||||||
|
45 слабых сценариев Eb/N0=0/1/2/3/7 дБ,CFO−100/+10/+100 Гц,триseed/масштаба:
|
||||||
|
старый и новый правильно приняли37/45,0 дополнительных потерь;8 не приняты обоими.
|
||||||
|
Старая Lab047-запись20 ксимв/с штатным Lab048:36/36,CRC PASS,JPEG SHA MATCH,
|
||||||
|
19 ошибок до FEC,0 после,0 байтовых ошибок. Аппаратных запусков не было.
|
||||||
|
|
||||||
|
30 целевых тестов PASS,verifier иadversary независимо подтвердили исправление
|
||||||
|
ложного подавления,границы блоков,плохую CRC,эквивалентность частей и finally.
|
||||||
|
ФинальныеJSON,выбор медианного прохода,UTF-8,ссылки и график проверены независимо.
|
||||||
|
quick_gate PASS:718 тестов,20 функциональных проверок,13 штатных пропусков.
|
||||||
|
Полный pytest:718 passed за77,15 с,0 failed,одно предупреждение .pytest_cache.
|
||||||
|
Новый helper наблюдения массивов отдельно выполнен;три модуля проверены
|
||||||
|
py_compile и run_functional_tests после его добавления. git diff --check PASS.
|
||||||
|
Обязательные регрессии в acceptance.json PASS;real-time/5-fps target явно FAIL.
|
||||||
|
Lab052 git add/commit иpush не выполнялись;индекс пуст. RF TX=0.
|
||||||
|
|
||||||
|
Следующий этап: STOP. Для отдельно согласованной Lab053 исследовать стоимость
|
||||||
|
настоящих пакетов, общей децимации/FFT/копирования IQ и задержку JPEG-пачки.
|
||||||
|
Не переходить автоматически к10fps,QPSK,180 МГц,аппаратным500 ксимв/с или RF.
|
||||||
|
|||||||
27
data/processed/lab052/acceptance.json
Normal file
27
data/processed/lab052/acceptance.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"checks": {
|
||||||
|
"median_whole_timeline": true,
|
||||||
|
"all_240_packets": true,
|
||||||
|
"all_30_exact_jpegs": true,
|
||||||
|
"same_240_unique_refined_starts": true,
|
||||||
|
"same_first_stage_counts": true,
|
||||||
|
"false_count_15": true,
|
||||||
|
"no_sensitivity_losses": true,
|
||||||
|
"bounded_long_iq": true,
|
||||||
|
"longer_than_main": true,
|
||||||
|
"long_noise_accepts_zero": true,
|
||||||
|
"finish_clears_iq_and_candidates": true,
|
||||||
|
"old20k_36_crc_and_sha": true,
|
||||||
|
"measured_cpu_sum": true,
|
||||||
|
"radio_protocol_files_unchanged": true
|
||||||
|
},
|
||||||
|
"required_regressions_pass": true,
|
||||||
|
"software_5fps_target_pass": false,
|
||||||
|
"real_time_capable": false,
|
||||||
|
"fresh_display_fps": 0.0,
|
||||||
|
"processing_per_rf_second": 1.2192732766011487,
|
||||||
|
"speedup_same_fixture": 5.649181959197251,
|
||||||
|
"false_candidate_reduction": 9784.333333333334,
|
||||||
|
"rf_tx": 0,
|
||||||
|
"protocol_check_command": "git status --porcelain --untracked-files=all -- protocol"
|
||||||
|
}
|
||||||
13
data/processed/lab052/allocations.json
Normal file
13
data/processed/lab052/allocations.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"observed_array_objects": 6754,
|
||||||
|
"observed_owned_arrays": 6681,
|
||||||
|
"observed_owned_bytes_total": 37378942,
|
||||||
|
"tracemalloc_retained_bytes": 6461,
|
||||||
|
"tracemalloc_peak_bytes": 11586792,
|
||||||
|
"array_count_is_lower_bound": true,
|
||||||
|
"iq_seconds": 0.063184125,
|
||||||
|
"packets": 1,
|
||||||
|
"interpretation": "observed Python ndarray lower bound; tracked RX allocation peak; input IQ prepared before tracing",
|
||||||
|
"timing_not_used_as_benchmark": true,
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
24914
data/processed/lab052/baseline_profile.json
Normal file
24914
data/processed/lab052/baseline_profile.json
Normal file
File diff suppressed because it is too large
Load Diff
7257
data/processed/lab052/baseline_stream.json
Normal file
7257
data/processed/lab052/baseline_stream.json
Normal file
File diff suppressed because it is too large
Load Diff
7275
data/processed/lab052/cpu_profile.json
Normal file
7275
data/processed/lab052/cpu_profile.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
data/processed/lab052/detector_distributions.png
Normal file
BIN
data/processed/lab052/detector_distributions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
5303
data/processed/lab052/detector_distributions.svg
Normal file
5303
data/processed/lab052/detector_distributions.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 239 KiB |
74
data/processed/lab052/initial_receiver_cpu.txt
Normal file
74
data/processed/lab052/initial_receiver_cpu.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
7786130 function calls (7783174 primitive calls) in 12.641 seconds
|
||||||
|
|
||||||
|
Ordered by: internal time
|
||||||
|
List reduced from 336 to 30 due to restriction <30>
|
||||||
|
|
||||||
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||||
|
7779 2.401 0.000 2.401 0.000 {built-in method scipy.fft._duccfft.pyduccfft.c2c}
|
||||||
|
240 1.355 0.006 1.363 0.006 lab051_streaming_receiver.py:139(fast_viterbi)
|
||||||
|
1467 1.290 0.001 1.301 0.001 _upfirdn.py:91(apply_filter)
|
||||||
|
312654 1.160 0.000 2.039 0.000 numeric.py:2337(isclose)
|
||||||
|
738 0.665 0.001 12.616 0.017 lab050_streaming_receiver.py:133(push_iq_block)
|
||||||
|
974 0.519 0.001 2.622 0.003 bpsk_radio.py:1079(root_raised_cosine_taps)
|
||||||
|
734 0.515 0.001 3.251 0.004 lab044_hackrf_bpsk_packet.py:189(_scale_and_carrier)
|
||||||
|
2746 0.327 0.000 0.327 0.000 {method 'copy' of 'numpy.ndarray' objects}
|
||||||
|
1455 0.267 0.000 0.269 0.000 lab043_pluto_to_rtlsdr.py:516(pilot_prbs7)
|
||||||
|
1563270 0.251 0.000 0.360 0.000 numeric.py:2429(<genexpr>)
|
||||||
|
2593 0.228 0.000 3.133 0.001 _signaltools.py:485(_freq_domain_conv)
|
||||||
|
1934 0.160 0.000 0.160 0.000 {method 'cumsum' of 'numpy.ndarray' objects}
|
||||||
|
7779 0.155 0.000 0.261 0.000 helper.py:146(_fix_shape)
|
||||||
|
16722 0.153 0.000 0.153 0.000 {built-in method numpy.zeros}
|
||||||
|
1624470 0.152 0.000 0.163 0.000 {built-in method builtins.isinstance}
|
||||||
|
29235 0.144 0.000 0.149 0.000 {built-in method numpy.array}
|
||||||
|
313134 0.128 0.000 0.251 0.000 _ufunc_config.py:440(__enter__)
|
||||||
|
480 0.125 0.000 1.649 0.003 lab051_streaming_receiver.py:258(matched_window)
|
||||||
|
12394 0.113 0.000 0.113 0.000 {method 'reduce' of 'numpy.ufunc' objects}
|
||||||
|
480 0.095 0.000 2.613 0.005 lab051_streaming_receiver.py:287(extract)
|
||||||
|
734 0.093 0.000 0.093 0.000 {built-in method scipy.ndimage._nd_image.min_or_max_filter1d}
|
||||||
|
784660 0.076 0.000 0.076 0.000 {built-in method builtins.abs}
|
||||||
|
734 0.074 0.000 0.540 0.001 lab043_hackrf_bpsk.py:248(_normalized_sliding_correlation)
|
||||||
|
313134 0.074 0.000 0.074 0.000 {built-in method numpy._core._multiarray_umath._make_extobj}
|
||||||
|
313134 0.071 0.000 0.124 0.000 _ufunc_config.py:456(__exit__)
|
||||||
|
885 0.070 0.000 0.075 0.000 _arraypad_impl.py:86(_pad_simple)
|
||||||
|
480 0.070 0.000 0.210 0.000 lab043_hackrf_bpsk.py:530(_evaluate_symbols)
|
||||||
|
5894 0.061 0.000 0.061 0.000 {method 'reshape' of 'numpy.ndarray' objects}
|
||||||
|
734 0.060 0.000 11.916 0.016 lab052_candidate_detection.py:142(_scan)
|
||||||
|
734 0.057 0.000 2.708 0.004 lab043_pluto_to_rtlsdr.py:458(shape_frame_like_lab042)
|
||||||
|
|
||||||
|
|
||||||
|
7786130 function calls (7783174 primitive calls) in 12.641 seconds
|
||||||
|
|
||||||
|
Ordered by: cumulative time
|
||||||
|
List reduced from 336 to 30 due to restriction <30>
|
||||||
|
|
||||||
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||||
|
738 0.665 0.001 12.616 0.017 lab050_streaming_receiver.py:133(push_iq_block)
|
||||||
|
734 0.060 0.000 11.916 0.016 lab052_candidate_detection.py:142(_scan)
|
||||||
|
240 0.022 0.000 5.697 0.024 lab046c_offline_recovery.py:637(decode_candidate_from_memmap)
|
||||||
|
734 0.004 0.000 3.548 0.005 lab051_streaming_receiver.py:185(__init__)
|
||||||
|
734 0.007 0.000 3.544 0.005 lab044_hackrf_bpsk_packet.py:257(_preamble_reference_samples)
|
||||||
|
734 0.515 0.001 3.251 0.004 lab044_hackrf_bpsk_packet.py:189(_scale_and_carrier)
|
||||||
|
2593 0.228 0.000 3.133 0.001 _signaltools.py:485(_freq_domain_conv)
|
||||||
|
7779 0.012 0.000 2.869 0.000 _backend.py:19(__ua_function__)
|
||||||
|
7779 0.020 0.000 2.847 0.000 _basic_backend.py:52(_execute_nD)
|
||||||
|
7779 0.030 0.000 2.824 0.000 basic.py:126(c2cn)
|
||||||
|
2188 0.036 0.000 2.806 0.001 _signaltools.py:589(fftconvolve)
|
||||||
|
734 0.057 0.000 2.708 0.004 lab043_pluto_to_rtlsdr.py:458(shape_frame_like_lab042)
|
||||||
|
974 0.519 0.001 2.622 0.003 bpsk_radio.py:1079(root_raised_cosine_taps)
|
||||||
|
480 0.095 0.000 2.613 0.005 lab051_streaming_receiver.py:287(extract)
|
||||||
|
7779 2.401 0.000 2.401 0.000 {built-in method scipy.fft._duccfft.pyduccfft.c2c}
|
||||||
|
734 0.025 0.000 2.057 0.003 lab052_candidate_detection.py:112(coarse_scores)
|
||||||
|
312654 1.160 0.000 2.039 0.000 numeric.py:2337(isclose)
|
||||||
|
5186 0.005 0.000 1.855 0.000 _basic_backend.py:113(fftn)
|
||||||
|
480 0.125 0.000 1.649 0.003 lab051_streaming_receiver.py:258(matched_window)
|
||||||
|
240 0.023 0.000 1.508 0.006 lab045_hackrf_bpsk_fec.py:476(analyze_post_preamble_soft)
|
||||||
|
240 0.052 0.000 1.497 0.006 lab051_streaming_receiver.py:222(refine)
|
||||||
|
734 0.030 0.000 1.415 0.002 lab051_streaming_receiver.py:113(get)
|
||||||
|
1467 0.003 0.000 1.383 0.001 lab051_streaming_receiver.py:120(filtered)
|
||||||
|
1467 0.026 0.000 1.379 0.001 _signaltools.py:3884(resample_poly)
|
||||||
|
240 1.355 0.006 1.363 0.006 lab051_streaming_receiver.py:139(fast_viterbi)
|
||||||
|
1467 0.005 0.000 1.331 0.001 _upfirdn.py:108(upfirdn)
|
||||||
|
1467 1.290 0.001 1.301 0.001 _upfirdn.py:91(apply_filter)
|
||||||
|
2593 0.002 0.000 0.999 0.000 _basic_backend.py:120(ifftn)
|
||||||
|
405 0.022 0.000 0.797 0.002 _signaltools.py:850(oaconvolve)
|
||||||
|
240 0.001 0.000 0.661 0.003 lab043_hackrf_bpsk.py:366(_matched_filter_taps)
|
||||||
115
data/processed/lab052/memory.json
Normal file
115
data/processed/lab052/memory.json
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"blocks": 2048,
|
||||||
|
"iq_seconds": 16.777216,
|
||||||
|
"background_scales": [
|
||||||
|
0.002,
|
||||||
|
0.2,
|
||||||
|
20,
|
||||||
|
0.02
|
||||||
|
],
|
||||||
|
"accepted_noise_packets": 0,
|
||||||
|
"peak_retained_iq_bytes": 3177728,
|
||||||
|
"iq_limit_bytes": 3398912,
|
||||||
|
"retained_after_finish_bytes": 0,
|
||||||
|
"constant_template_bytes": 171140,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"peak_cluster_count": 1,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 4783206,
|
||||||
|
"first_stage_candidates": 1068038,
|
||||||
|
"cheap_stage_checked": 1068038,
|
||||||
|
"cheap_stage_rejected": 1068037,
|
||||||
|
"clusters": 1,
|
||||||
|
"representative_checks": 1
|
||||||
|
},
|
||||||
|
"strict_candidates": 1,
|
||||||
|
"processing_seconds": 8.453774801455438,
|
||||||
|
"checkpoints": [
|
||||||
|
{
|
||||||
|
"block": 256,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 0,
|
||||||
|
"counter_keys": 5,
|
||||||
|
"rss_bytes": 137297920,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 512,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 139948032,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 768,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140001280,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 1024,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140533760,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 1280,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140533760,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 1536,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140541952,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 1792,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140550144,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block": 2048,
|
||||||
|
"iq_bytes": 2653440,
|
||||||
|
"decimated_bytes": 397216,
|
||||||
|
"retained_candidates": 0,
|
||||||
|
"peak_eligible_candidates": 1,
|
||||||
|
"counter_keys": 7,
|
||||||
|
"rss_bytes": 140550144,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"process_memory": {
|
||||||
|
"rss_bytes": 135602176,
|
||||||
|
"peak_rss_bytes": 168501248
|
||||||
|
},
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
74
data/processed/lab052/receiver_cpu.txt
Normal file
74
data/processed/lab052/receiver_cpu.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
1173653 function calls (1170697 primitive calls) in 8.087 seconds
|
||||||
|
|
||||||
|
Ordered by: internal time
|
||||||
|
List reduced from 324 to 30 due to restriction <30>
|
||||||
|
|
||||||
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||||
|
10251 1.650 0.000 1.650 0.000 {built-in method scipy.fft._duccfft.pyduccfft.c2c}
|
||||||
|
240 1.397 0.006 1.405 0.006 lab051_streaming_receiver.py:139(fast_viterbi)
|
||||||
|
1467 1.295 0.001 1.308 0.001 _upfirdn.py:91(apply_filter)
|
||||||
|
738 0.553 0.001 8.065 0.011 lab050_streaming_receiver.py:133(push_iq_block)
|
||||||
|
2701 0.313 0.000 0.313 0.000 {method 'copy' of 'numpy.ndarray' objects}
|
||||||
|
14414 0.167 0.000 0.167 0.000 {built-in method numpy.zeros}
|
||||||
|
3417 0.157 0.000 2.311 0.001 _signaltools.py:485(_freq_domain_conv)
|
||||||
|
734 0.152 0.000 7.483 0.010 lab052_candidate_detection.py:181(_scan)
|
||||||
|
2687 0.145 0.000 0.145 0.000 {method 'cumsum' of 'numpy.ndarray' objects}
|
||||||
|
11504 0.113 0.000 0.113 0.000 {method 'reduce' of 'numpy.ufunc' objects}
|
||||||
|
488 0.098 0.000 0.865 0.002 lab051_streaming_receiver.py:258(matched_window)
|
||||||
|
734 0.095 0.000 0.531 0.001 lab052_candidate_detection.py:125(split_scores)
|
||||||
|
35184 0.091 0.000 0.097 0.000 {built-in method numpy.array}
|
||||||
|
495 0.085 0.000 1.733 0.004 lab051_streaming_receiver.py:287(extract)
|
||||||
|
488 0.075 0.000 0.225 0.000 lab043_hackrf_bpsk.py:530(_evaluate_symbols)
|
||||||
|
734 0.069 0.000 7.321 0.010 lab052_candidate_detection.py:185(_scan_with_templates)
|
||||||
|
10251 0.068 0.000 0.222 0.000 helper.py:146(_fix_shape)
|
||||||
|
734 0.062 0.000 0.372 0.001 lab043_hackrf_bpsk.py:248(_normalized_sliding_correlation)
|
||||||
|
240 0.059 0.000 0.083 0.000 bit_interleaver.py:41(build_permutation)
|
||||||
|
972 0.052 0.000 0.055 0.000 _arraypad_impl.py:86(_pad_simple)
|
||||||
|
488 0.051 0.000 0.533 0.001 lab051_streaming_receiver.py:41(fast_preamble_timing)
|
||||||
|
734 0.049 0.000 1.457 0.002 lab051_streaming_receiver.py:113(get)
|
||||||
|
255 0.049 0.000 1.084 0.004 lab051_streaming_receiver.py:222(refine)
|
||||||
|
13668 0.045 0.000 0.107 0.000 helper.py:47(_init_nd_shape_and_axes)
|
||||||
|
488 0.043 0.000 0.119 0.000 lab043_hackrf_bpsk.py:454(_estimate_cfo_gain)
|
||||||
|
2933 0.040 0.000 2.010 0.001 _signaltools.py:589(fftconvolve)
|
||||||
|
31006 0.040 0.000 0.040 0.000 {built-in method numpy.asarray}
|
||||||
|
5366 0.038 0.000 0.038 0.000 {method 'reshape' of 'numpy.ndarray' objects}
|
||||||
|
10251 0.034 0.000 2.061 0.000 basic.py:126(c2cn)
|
||||||
|
1467 0.032 0.000 1.401 0.001 _signaltools.py:3884(resample_poly)
|
||||||
|
|
||||||
|
|
||||||
|
1173653 function calls (1170697 primitive calls) in 8.087 seconds
|
||||||
|
|
||||||
|
Ordered by: cumulative time
|
||||||
|
List reduced from 324 to 30 due to restriction <30>
|
||||||
|
|
||||||
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||||
|
738 0.553 0.001 8.065 0.011 lab050_streaming_receiver.py:133(push_iq_block)
|
||||||
|
734 0.152 0.000 7.483 0.010 lab052_candidate_detection.py:181(_scan)
|
||||||
|
734 0.069 0.000 7.321 0.010 lab052_candidate_detection.py:185(_scan_with_templates)
|
||||||
|
255 0.026 0.000 4.417 0.017 lab046c_offline_recovery.py:637(decode_candidate_from_memmap)
|
||||||
|
3417 0.157 0.000 2.311 0.001 _signaltools.py:485(_freq_domain_conv)
|
||||||
|
10251 0.014 0.000 2.110 0.000 _backend.py:19(__ua_function__)
|
||||||
|
10251 0.021 0.000 2.086 0.000 _basic_backend.py:52(_execute_nD)
|
||||||
|
10251 0.034 0.000 2.061 0.000 basic.py:126(c2cn)
|
||||||
|
2933 0.040 0.000 2.010 0.001 _signaltools.py:589(fftconvolve)
|
||||||
|
734 0.025 0.000 1.932 0.003 lab052_candidate_detection.py:111(coarse_scores)
|
||||||
|
495 0.085 0.000 1.733 0.004 lab051_streaming_receiver.py:287(extract)
|
||||||
|
10251 1.650 0.000 1.650 0.000 {built-in method scipy.fft._duccfft.pyduccfft.c2c}
|
||||||
|
240 0.026 0.000 1.558 0.006 lab045_hackrf_bpsk_fec.py:476(analyze_post_preamble_soft)
|
||||||
|
734 0.049 0.000 1.457 0.002 lab051_streaming_receiver.py:113(get)
|
||||||
|
1467 0.004 0.000 1.406 0.001 lab051_streaming_receiver.py:120(filtered)
|
||||||
|
240 1.397 0.006 1.405 0.006 lab051_streaming_receiver.py:139(fast_viterbi)
|
||||||
|
1467 0.032 0.000 1.401 0.001 _signaltools.py:3884(resample_poly)
|
||||||
|
6834 0.005 0.000 1.360 0.000 _basic_backend.py:113(fftn)
|
||||||
|
1467 0.005 0.000 1.342 0.001 _upfirdn.py:108(upfirdn)
|
||||||
|
1467 1.295 0.001 1.308 0.001 _upfirdn.py:91(apply_filter)
|
||||||
|
255 0.049 0.000 1.084 0.004 lab051_streaming_receiver.py:222(refine)
|
||||||
|
488 0.098 0.000 0.865 0.002 lab051_streaming_receiver.py:258(matched_window)
|
||||||
|
3417 0.002 0.000 0.733 0.000 _basic_backend.py:120(ifftn)
|
||||||
|
484 0.022 0.000 0.712 0.001 _signaltools.py:850(oaconvolve)
|
||||||
|
488 0.002 0.000 0.535 0.001 lab051_streaming_receiver.py:283(timing)
|
||||||
|
488 0.051 0.000 0.533 0.001 lab051_streaming_receiver.py:41(fast_preamble_timing)
|
||||||
|
734 0.095 0.000 0.531 0.001 lab052_candidate_detection.py:125(split_scores)
|
||||||
|
734 0.062 0.000 0.372 0.001 lab043_hackrf_bpsk.py:248(_normalized_sliding_correlation)
|
||||||
|
2701 0.313 0.000 0.313 0.000 {method 'copy' of 'numpy.ndarray' objects}
|
||||||
|
488 0.075 0.000 0.225 0.000 lab043_hackrf_bpsk.py:530(_evaluate_symbols)
|
||||||
198
data/processed/lab052/regression20k/result.json
Normal file
198
data/processed/lab052/regression20k/result.json
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
{
|
||||||
|
"stage": "LAB048_OFFLINE_RECEIVER_PERFORMANCE",
|
||||||
|
"baseline": {
|
||||||
|
"capture_seconds": 15.0,
|
||||||
|
"full_processing_seconds": 37.423910000012256,
|
||||||
|
"strict_stage_seconds": 32.45996900013415,
|
||||||
|
"average_strict_candidate_seconds": 0.47043433333527757,
|
||||||
|
"approximate_peak_bytes": 1545755352
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"capture_seconds": 15.0,
|
||||||
|
"full_processing_seconds": 13.213345700060017,
|
||||||
|
"coarse_search_seconds": 4.5940155000425875,
|
||||||
|
"strict_stage_seconds": 8.609627199941315,
|
||||||
|
"average_strict_candidate_seconds": 0.12477720579625094,
|
||||||
|
"approximate_peak_bytes": 1545755352,
|
||||||
|
"stage_measurements": {
|
||||||
|
"cs8_to_complex64": {
|
||||||
|
"calls": 113,
|
||||||
|
"cumulative_seconds": 1.3091708993306383
|
||||||
|
},
|
||||||
|
"refine_start": {
|
||||||
|
"calls": 69,
|
||||||
|
"cumulative_seconds": 2.8543391003040597
|
||||||
|
},
|
||||||
|
"matched_filter": {
|
||||||
|
"calls": 105,
|
||||||
|
"cumulative_seconds": 3.088835699832998
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"calls": 105,
|
||||||
|
"cumulative_seconds": 1.2946644995827228
|
||||||
|
},
|
||||||
|
"cfo_phase_soft": {
|
||||||
|
"calls": 105,
|
||||||
|
"cumulative_seconds": 0.0416537004057318
|
||||||
|
},
|
||||||
|
"phy_header": {
|
||||||
|
"calls": 105,
|
||||||
|
"cumulative_seconds": 0.004346300149336457
|
||||||
|
},
|
||||||
|
"post_preamble_fec_crc": {
|
||||||
|
"calls": 36,
|
||||||
|
"cumulative_seconds": 0.6405917998636141
|
||||||
|
},
|
||||||
|
"viterbi": {
|
||||||
|
"calls": 36,
|
||||||
|
"cumulative_seconds": 0.6221252002287656
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memory_arrays": [
|
||||||
|
{
|
||||||
|
"name": "coarse_iq_window",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
16000000
|
||||||
|
],
|
||||||
|
"bytes": 128000000,
|
||||||
|
"mib": 122.0703125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "coarse_decimated",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
800000
|
||||||
|
],
|
||||||
|
"bytes": 6400000,
|
||||||
|
"mib": 6.103515625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "coarse_downconversion_vector",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
800000
|
||||||
|
],
|
||||||
|
"bytes": 6400000,
|
||||||
|
"mib": 6.103515625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "coarse_correlation_scores",
|
||||||
|
"dtype": "float32",
|
||||||
|
"shape": [
|
||||||
|
773561
|
||||||
|
],
|
||||||
|
"bytes": 3094244,
|
||||||
|
"mib": 2.9509010314941406
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "strict_full_iq_window",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
2306400
|
||||||
|
],
|
||||||
|
"bytes": 18451200,
|
||||||
|
"mib": 17.596435546875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "strict_cached_carrier_vector",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
2306400
|
||||||
|
],
|
||||||
|
"bytes": 18451200,
|
||||||
|
"mib": 17.596435546875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "strict_baseband",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
2306400
|
||||||
|
],
|
||||||
|
"bytes": 18451200,
|
||||||
|
"mib": 17.596435546875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "strict_matched_output",
|
||||||
|
"dtype": "complex64",
|
||||||
|
"shape": [
|
||||||
|
2310400
|
||||||
|
],
|
||||||
|
"bytes": 18483200,
|
||||||
|
"mib": 17.626953125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matched_filter_taps",
|
||||||
|
"dtype": "float32",
|
||||||
|
"shape": [
|
||||||
|
4001
|
||||||
|
],
|
||||||
|
"bytes": 16004,
|
||||||
|
"mib": 0.015262603759765625
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"speedup": 2.8322811534282546,
|
||||||
|
"processing_to_capture_ratio": 0.8808897133373345,
|
||||||
|
"real_time_capable": true,
|
||||||
|
"threshold": 0.030964473262429237,
|
||||||
|
"threshold_changed": false,
|
||||||
|
"coarse": {
|
||||||
|
"total_local_peaks": 185828,
|
||||||
|
"first_stage_candidates": 32414,
|
||||||
|
"skipped_as_owned_event": 32345,
|
||||||
|
"exact_duplicates": 0
|
||||||
|
},
|
||||||
|
"strict": {
|
||||||
|
"candidates_processed": 69,
|
||||||
|
"rejected_during_refinement": 0,
|
||||||
|
"refinement_pass": 69,
|
||||||
|
"timing_pass": 69,
|
||||||
|
"carrier_pass": 69,
|
||||||
|
"phy_header_pass": 36,
|
||||||
|
"phy_header_fail": 33,
|
||||||
|
"fec_attempts": 36,
|
||||||
|
"fec_pass": 36,
|
||||||
|
"fec_fail": 0,
|
||||||
|
"protocol_parse_pass": 36,
|
||||||
|
"crc_pass": 36,
|
||||||
|
"crc_fail": 0,
|
||||||
|
"valid_frames": 36
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"valid_fragments": 36,
|
||||||
|
"missing_fragments": [],
|
||||||
|
"object_complete": true
|
||||||
|
},
|
||||||
|
"after_independent_verdict": {
|
||||||
|
"stage": "AFTER_COMPLETED_INDEPENDENT_VERDICT_ONLY",
|
||||||
|
"expected_tx_frames": 36,
|
||||||
|
"matched_valid_frames": 36,
|
||||||
|
"missed_tx_frames": [],
|
||||||
|
"false_crc_pass": 0,
|
||||||
|
"duplicate_valid_detections": 0,
|
||||||
|
"total_pre_fec_coded_bit_errors": 19,
|
||||||
|
"total_post_fec_protocol_bit_errors": 0,
|
||||||
|
"jpeg_size": 8724,
|
||||||
|
"recovered_sha256": "e913dab9ac32795250880a4be67fbbbf89a4f96536842476ab591d1acaafc02a",
|
||||||
|
"tx_sha256": "e913dab9ac32795250880a4be67fbbbf89a4f96536842476ab591d1acaafc02a",
|
||||||
|
"sha_match": true,
|
||||||
|
"byte_errors": 0,
|
||||||
|
"bit_errors": 0,
|
||||||
|
"jpeg_decode": "PASS",
|
||||||
|
"width": 320,
|
||||||
|
"height": 240
|
||||||
|
},
|
||||||
|
"receiver_oracles": {
|
||||||
|
"expected_frame_count": false,
|
||||||
|
"tx_coded_bits": false,
|
||||||
|
"tx_fragment_indexes": false,
|
||||||
|
"tx_frame_offsets": false,
|
||||||
|
"tx_image": false,
|
||||||
|
"tx_packets": false
|
||||||
|
},
|
||||||
|
"phy_changed": false,
|
||||||
|
"fec_changed": false,
|
||||||
|
"bpsk_changed": false,
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
124
data/processed/lab052/rejected_nms_comparison.json
Normal file
124
data/processed/lab052/rejected_nms_comparison.json
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 0,
|
||||||
|
"radius_samples": 20480,
|
||||||
|
"part_threshold": null
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 263.9658273381295,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 390639,
|
||||||
|
"clusters": 900,
|
||||||
|
"cheap_stage_checked": 796,
|
||||||
|
"inside_accepted_frame": 104,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 556,
|
||||||
|
"strict_checks_per_rf_second": 131.691796196688,
|
||||||
|
"cheap_seconds": 0.0009583993814885616,
|
||||||
|
"cluster_seconds": 0.15876410226337612,
|
||||||
|
"peak_cluster_count": 7,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1243896
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 2.6724659422897794,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 1,
|
||||||
|
"radius_samples": 20480,
|
||||||
|
"part_threshold": 0.05995104488778001
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 1298.8053097345132,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 390639,
|
||||||
|
"clusters": 900,
|
||||||
|
"cheap_stage_checked": 796,
|
||||||
|
"inside_accepted_frame": 104,
|
||||||
|
"cheap_stage_rejected": 443,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 113,
|
||||||
|
"strict_checks_per_rf_second": 58.40101012240058,
|
||||||
|
"cheap_seconds": 0.03248489915858954,
|
||||||
|
"cluster_seconds": 0.16109760117251426,
|
||||||
|
"peak_cluster_count": 7,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1243896
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 2.059989517566315,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 2,
|
||||||
|
"radius_samples": 20480,
|
||||||
|
"part_threshold": 0.08474038063392896
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": null,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 390639,
|
||||||
|
"clusters": 900,
|
||||||
|
"cheap_stage_checked": 796,
|
||||||
|
"inside_accepted_frame": 104,
|
||||||
|
"cheap_stage_rejected": 556,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 0,
|
||||||
|
"strict_checks_per_rf_second": 39.706069205031554,
|
||||||
|
"cheap_seconds": 0.035114401835016906,
|
||||||
|
"cluster_seconds": 0.16016869980376214,
|
||||||
|
"peak_cluster_count": 7,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1243896
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 1.8833212008846116,
|
||||||
|
"fresh_display_fps": 0.0,
|
||||||
|
"false_candidate_reduction_fraction": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 3,
|
||||||
|
"radius_samples": 20480,
|
||||||
|
"part_threshold": 0.10381329338988134
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": null,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 390639,
|
||||||
|
"clusters": 900,
|
||||||
|
"cheap_stage_checked": 796,
|
||||||
|
"inside_accepted_frame": 104,
|
||||||
|
"cheap_stage_rejected": 556,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 0,
|
||||||
|
"strict_checks_per_rf_second": 39.706069205031554,
|
||||||
|
"cheap_seconds": 0.03812550043221563,
|
||||||
|
"cluster_seconds": 0.16675639932509512,
|
||||||
|
"peak_cluster_count": 7,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1243896
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 1.8967992111861114,
|
||||||
|
"fresh_display_fps": 0.0,
|
||||||
|
"false_candidate_reduction_fraction": 1.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rf_tx": 0,
|
||||||
|
"status": "REJECTED: strong invalid preamble suppresses neighboring valid packet",
|
||||||
|
"timing_includes_observer": true
|
||||||
|
}
|
||||||
121
data/processed/lab052/rule_comparison.json
Normal file
121
data/processed/lab052/rule_comparison.json
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 0,
|
||||||
|
"width_samples": 20480,
|
||||||
|
"part_threshold": null
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 1.0061838848782763,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 392785,
|
||||||
|
"cheap_stage_checked": 392785,
|
||||||
|
"cheap_stage_rejected": 0,
|
||||||
|
"clusters": 2777,
|
||||||
|
"inside_accepted_frame": 246682,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 145863,
|
||||||
|
"strict_checks_per_rf_second": 24171.56595442802,
|
||||||
|
"cheap_seconds": 0.0041361990151926875,
|
||||||
|
"cluster_seconds": 0.009281201055273414,
|
||||||
|
"peak_cluster_count": 15,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1236912
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 5.978309517500444,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 1,
|
||||||
|
"width_samples": 20480,
|
||||||
|
"part_threshold": 0.05995104488778001
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 442.06325301204816,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 392785,
|
||||||
|
"cheap_stage_checked": 392785,
|
||||||
|
"cheap_stage_rejected": 366683,
|
||||||
|
"clusters": 1354,
|
||||||
|
"inside_accepted_frame": 25530,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 332,
|
||||||
|
"strict_checks_per_rf_second": 94.63279827199187,
|
||||||
|
"cheap_seconds": 0.32703390275128186,
|
||||||
|
"cluster_seconds": 0.005597901064902544,
|
||||||
|
"peak_cluster_count": 4,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1330708
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 1.5535515426894586,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 2,
|
||||||
|
"width_samples": 20480,
|
||||||
|
"part_threshold": 0.08474038063392896
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 9784.333333333334,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 392785,
|
||||||
|
"cheap_stage_checked": 392785,
|
||||||
|
"cheap_stage_rejected": 388407,
|
||||||
|
"clusters": 384,
|
||||||
|
"inside_accepted_frame": 4123,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 15,
|
||||||
|
"strict_checks_per_rf_second": 42.187698530346026,
|
||||||
|
"cheap_seconds": 0.530722099239938,
|
||||||
|
"cluster_seconds": 0.0033054009545594454,
|
||||||
|
"peak_cluster_count": 3,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1480664
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 1.3386680530144077,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": {
|
||||||
|
"parts": 3,
|
||||||
|
"width_samples": 20480,
|
||||||
|
"part_threshold": 0.10381329338988134
|
||||||
|
},
|
||||||
|
"received_images": 30,
|
||||||
|
"object_jpeg_integrity": true,
|
||||||
|
"true_candidates_lost": 0,
|
||||||
|
"false_candidate_reduction": 24460.833333333332,
|
||||||
|
"detector": {
|
||||||
|
"total_local_peaks": 1675842,
|
||||||
|
"first_stage_candidates": 392785,
|
||||||
|
"cheap_stage_checked": 392785,
|
||||||
|
"cheap_stage_rejected": 390636,
|
||||||
|
"clusters": 281,
|
||||||
|
"inside_accepted_frame": 1903,
|
||||||
|
"strict_true_candidates": 240,
|
||||||
|
"strict_false_candidates": 6,
|
||||||
|
"strict_checks_per_rf_second": 40.69872093515734,
|
||||||
|
"cheap_seconds": 0.8188061001710594,
|
||||||
|
"cluster_seconds": 0.0032444989774376154,
|
||||||
|
"peak_cluster_count": 2,
|
||||||
|
"candidate_state_after_finish": 0,
|
||||||
|
"detection_arrays_peak_estimate_bytes": 1630812
|
||||||
|
},
|
||||||
|
"processing_per_rf_second": 1.410843181335602,
|
||||||
|
"fresh_display_fps": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
905
data/processed/lab052/sensitivity.json
Normal file
905
data/processed/lab052/sensitivity.json
Normal file
@@ -0,0 +1,905 @@
|
|||||||
|
{
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1045,
|
||||||
|
"CRC_FAIL": 31
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"CRC_FAIL": 2,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 71
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1029,
|
||||||
|
"CRC_NOT_REACHED": 11,
|
||||||
|
"TRUNCATED_SYMBOLS": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"CRC_NOT_REACHED": 2,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1115,
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 33,
|
||||||
|
"TRUNCATED_SYMBOLS": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 974,
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 14,
|
||||||
|
"TRUNCATED_SYMBOLS": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 2,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1046,
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 14,
|
||||||
|
"TRUNCATED_SYMBOLS": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 3,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1034,
|
||||||
|
"TRUNCATED_SYMBOLS": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 77
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 0,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 967,
|
||||||
|
"TRUNCATED_SYMBOLS": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 98
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 65
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 76
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 101
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 1009,
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 13,
|
||||||
|
"TRUNCATED_SYMBOLS": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 0,
|
||||||
|
"payload_match": false,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_CHECKSUM_MISMATCH": 3,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 75
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 67
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 1,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 74
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 79
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 68
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 83
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 58
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 70
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"TRUNCATED_SYMBOLS": 1,
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 85
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 57
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 2,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 67
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 71
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 49
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 64
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 74
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 53
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 64
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 76
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 53
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 3,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 63
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 42
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": -100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 51
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 42
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 10,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 51
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52031,
|
||||||
|
"amplitude_scale": 0.002,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 48
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52032,
|
||||||
|
"amplitude_scale": 0.2,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 42
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ebn0_db": 7,
|
||||||
|
"cfo_hz": 100,
|
||||||
|
"seed": 52033,
|
||||||
|
"amplitude_scale": 1.0,
|
||||||
|
"baseline": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {
|
||||||
|
"PHY_HEADER_MAGIC_MISMATCH": 44
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"accepted": 1,
|
||||||
|
"payload_match": true,
|
||||||
|
"reasons": {}
|
||||||
|
},
|
||||||
|
"lost_baseline_packet": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 45,
|
||||||
|
"baseline_correct": 37,
|
||||||
|
"optimized_correct": 37,
|
||||||
|
"lost_baseline_packets": 0,
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
186
data/processed/lab052/source_fixture.json
Normal file
186
data/processed/lab052/source_fixture.json
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"seed": 50050,
|
||||||
|
"description": "Lab051 generator; frozen measured JPEG readiness",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"image_id": 1,
|
||||||
|
"bytes": 1740,
|
||||||
|
"encode_seconds": 0.00024159997701644897,
|
||||||
|
"sha256": "15723773a8788c9d610c874707ad2d513582322ee1d42009b0b39d3de336e10d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 2,
|
||||||
|
"bytes": 1790,
|
||||||
|
"encode_seconds": 0.000175100052729249,
|
||||||
|
"sha256": "66404b14ca9e3a6627871ad9d3b5dd4311915d7820a99dfa182fc3a20fbe9d85"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 3,
|
||||||
|
"bytes": 1821,
|
||||||
|
"encode_seconds": 0.00018400000408291817,
|
||||||
|
"sha256": "166d33d72820bf3385e0d30823d991b453147c84ca75f788ab8819ad5e3c3ced"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 4,
|
||||||
|
"bytes": 1844,
|
||||||
|
"encode_seconds": 0.0001865000231191516,
|
||||||
|
"sha256": "38052148ffbc024d2ba28049ea20b1172eebe9c990cb2fd94b25d2eaf9b46584"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 5,
|
||||||
|
"bytes": 1871,
|
||||||
|
"encode_seconds": 0.00015390000771731138,
|
||||||
|
"sha256": "1e135c2854bef0f7dffb84cf8eb71e200c958d7d7426d24d16bb36d471b52c1d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 6,
|
||||||
|
"bytes": 1889,
|
||||||
|
"encode_seconds": 0.00016799999866634607,
|
||||||
|
"sha256": "b59b7e834374fa00757abcb57282a1b7c46c08b41e0adb1cb5803135b009cb7f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 7,
|
||||||
|
"bytes": 1893,
|
||||||
|
"encode_seconds": 0.00022439996246248484,
|
||||||
|
"sha256": "8f6452262ea5a82d195de8cd23e3d21ab186ce5a20b031acf8295c30daa98572"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 8,
|
||||||
|
"bytes": 1877,
|
||||||
|
"encode_seconds": 0.00017830007709562778,
|
||||||
|
"sha256": "eb0f69a3e57c1860d98e79591ee9b472ac3a0929883be2a2992397f594d176f4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 9,
|
||||||
|
"bytes": 1844,
|
||||||
|
"encode_seconds": 0.00019099994096904993,
|
||||||
|
"sha256": "22c41e53d1d086baf886e6f65c011e1360ef26ad7781eced4655664cb7adcf06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 10,
|
||||||
|
"bytes": 1826,
|
||||||
|
"encode_seconds": 0.00017690006643533707,
|
||||||
|
"sha256": "e907decbbf66cc21c6b6e767b6c9baef856754d3cc95625cca38fd07aa5408c1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 11,
|
||||||
|
"bytes": 1878,
|
||||||
|
"encode_seconds": 0.00019090005662292242,
|
||||||
|
"sha256": "ab681a9cce96a8aaa71f3a612634748a2df743f1c76092f5a674e0dd36ca7caf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 12,
|
||||||
|
"bytes": 1805,
|
||||||
|
"encode_seconds": 0.00017190002836287022,
|
||||||
|
"sha256": "9c3d4258ddf1085f8da6ca87b6c3071aa031d58a5c42739a5527a27e6c2f17af"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 13,
|
||||||
|
"bytes": 1819,
|
||||||
|
"encode_seconds": 0.0001813999842852354,
|
||||||
|
"sha256": "6615eb4fe58fb4d0854c3c856e65a0990eba2361fc977c524acf9f0a5ab7accb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 14,
|
||||||
|
"bytes": 1866,
|
||||||
|
"encode_seconds": 0.0002503000432625413,
|
||||||
|
"sha256": "8fbb4fa0fce1e3deedb3fd838d69835b7670d73d793b792b2ffde69d733bbb40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 15,
|
||||||
|
"bytes": 1892,
|
||||||
|
"encode_seconds": 0.00020739994943141937,
|
||||||
|
"sha256": "9109553288f7b07311be951bd36cc389034496b6665b874dd92cfea84ac60d13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 16,
|
||||||
|
"bytes": 1886,
|
||||||
|
"encode_seconds": 0.00019369996152818203,
|
||||||
|
"sha256": "5b264bae760e7103f645f4a4203c435dfb8bd7555ec679b750d4c4cefe642f3a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 17,
|
||||||
|
"bytes": 1881,
|
||||||
|
"encode_seconds": 0.00017360004130750895,
|
||||||
|
"sha256": "df7d2804878be2b11c0233cee2401504d3545fad2f856fedb5e3aedfc1a0bf36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 18,
|
||||||
|
"bytes": 1862,
|
||||||
|
"encode_seconds": 0.00019259995315223932,
|
||||||
|
"sha256": "9fc4dded148ed750d53cadf0abe059cb01acab50999ebf9f521b2685c77c3025"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 19,
|
||||||
|
"bytes": 1838,
|
||||||
|
"encode_seconds": 0.00017380004283040762,
|
||||||
|
"sha256": "820d8d0916c53b1937d002e6b0632100b7a2d0a216f1022cdc55ba5fd0844cb5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 20,
|
||||||
|
"bytes": 1859,
|
||||||
|
"encode_seconds": 0.0001799999736249447,
|
||||||
|
"sha256": "ef9903b6c8d9ab6f572a7fa54af7570f6c321346117f9c4e156e1b71c97c46fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 21,
|
||||||
|
"bytes": 1892,
|
||||||
|
"encode_seconds": 0.00019259995315223932,
|
||||||
|
"sha256": "6605b523265e1229959f94fdad35d672037a902e9c7c585e22aafb875fb9e287"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 22,
|
||||||
|
"bytes": 1783,
|
||||||
|
"encode_seconds": 0.00022350007202476263,
|
||||||
|
"sha256": "8cf09da5a66ca3b3b18c1e4db63c8d57ed5044ba6f758368f7bcfb04f86a3839"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 23,
|
||||||
|
"bytes": 1826,
|
||||||
|
"encode_seconds": 0.0001976999919861555,
|
||||||
|
"sha256": "282ee75b1d6c2963fec0ecaed77f5663d9a49bcfa1561c5b0f40ea93089e06f1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 24,
|
||||||
|
"bytes": 1870,
|
||||||
|
"encode_seconds": 0.00019099994096904993,
|
||||||
|
"sha256": "ba6c6a8aeae49674442d1079d15f4b0fa4202e7cc72cb66d2853c312ced16132"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 25,
|
||||||
|
"bytes": 1872,
|
||||||
|
"encode_seconds": 0.0001868000254034996,
|
||||||
|
"sha256": "bd7841541d47d9a9844e6302825db98f3c72e5258946c3d2d0a2d2b7f0cee92e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 26,
|
||||||
|
"bytes": 1872,
|
||||||
|
"encode_seconds": 0.00018989993259310722,
|
||||||
|
"sha256": "70cdf837f756084e3d5a6dd438b615d71e669936c4d17c9ed7be88f4b4dcf965"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 27,
|
||||||
|
"bytes": 1870,
|
||||||
|
"encode_seconds": 0.0001943999668583274,
|
||||||
|
"sha256": "a31da69de44bff031631997cc27724025f293528b4d97f2825d34a704ec224af"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 28,
|
||||||
|
"bytes": 1848,
|
||||||
|
"encode_seconds": 0.00018319999799132347,
|
||||||
|
"sha256": "4380eae166bc37a7de7367642559b948cf1cc77939d1c84406de7cff372b6a76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 29,
|
||||||
|
"bytes": 1871,
|
||||||
|
"encode_seconds": 0.00016330007929354906,
|
||||||
|
"sha256": "c2e1a913dc48d622e1097f31543208ce610ce42d266857d8a76961f143dd9a4b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"image_id": 30,
|
||||||
|
"bytes": 1869,
|
||||||
|
"encode_seconds": 0.0001724000321701169,
|
||||||
|
"sha256": "bbbd15eac04b06ea05ec548f4c95b577b8ffc280c00fc473fb2a6914c40f7939"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
107
data/processed/lab052/statistics.json
Normal file
107
data/processed/lab052/statistics.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"classification": "strict PHY/FEC/CRC verdict first; no diagnostic feedback into RX",
|
||||||
|
"baseline_true_unique_starts": 240,
|
||||||
|
"optimized_true_unique_starts": 240,
|
||||||
|
"missing_true_starts": [],
|
||||||
|
"unexpected_true_starts": [],
|
||||||
|
"true_main_peak_scores": {
|
||||||
|
"count": 240,
|
||||||
|
"min": 0.8300548791885376,
|
||||||
|
"median": 0.8416910767555237,
|
||||||
|
"mad": 0.0021253228187561035,
|
||||||
|
"p90": 0.8455655992031097,
|
||||||
|
"p99": 0.8483891654014587,
|
||||||
|
"p999": 0.8496067104935646,
|
||||||
|
"max": 0.8499454259872437
|
||||||
|
},
|
||||||
|
"false_strict_scores": {
|
||||||
|
"count": 146765,
|
||||||
|
"min": 0.03096448816359043,
|
||||||
|
"median": 0.03546393662691116,
|
||||||
|
"mad": 0.0029666051268577576,
|
||||||
|
"p90": 0.04409027695655823,
|
||||||
|
"p99": 0.0558026286959648,
|
||||||
|
"p999": 0.5325132031440795,
|
||||||
|
"max": 0.8059508800506592
|
||||||
|
},
|
||||||
|
"baseline_false_refined_positions": 11387,
|
||||||
|
"baseline_false_repeated_refinements": 135378,
|
||||||
|
"noise_only_blocks": 357,
|
||||||
|
"noise_block_statistics": {
|
||||||
|
"median": {
|
||||||
|
"count": 357,
|
||||||
|
"min": 0.006450756685808301,
|
||||||
|
"median": 0.016353150829672813,
|
||||||
|
"mad": 0.00045966170728206635,
|
||||||
|
"p90": 0.016945500299334525,
|
||||||
|
"p99": 0.017328095547854902,
|
||||||
|
"p999": 0.017409235779196026,
|
||||||
|
"max": 0.017421899363398552
|
||||||
|
},
|
||||||
|
"mad": {
|
||||||
|
"count": 357,
|
||||||
|
"min": 0.002472435589879751,
|
||||||
|
"median": 0.006183675490319729,
|
||||||
|
"mad": 0.0002206573262810707,
|
||||||
|
"p90": 0.006455647945404053,
|
||||||
|
"p99": 0.006683134622871876,
|
||||||
|
"p999": 0.006718997411429882,
|
||||||
|
"max": 0.006726322695612907
|
||||||
|
},
|
||||||
|
"p90": {
|
||||||
|
"count": 357,
|
||||||
|
"min": 0.011724081635475166,
|
||||||
|
"median": 0.029762874729931357,
|
||||||
|
"mad": 0.0008111353963613475,
|
||||||
|
"p90": 0.0307584797218442,
|
||||||
|
"p99": 0.03153721953928471,
|
||||||
|
"p999": 0.03181600074619056,
|
||||||
|
"max": 0.03183051086962224
|
||||||
|
},
|
||||||
|
"p99": {
|
||||||
|
"count": 357,
|
||||||
|
"min": 0.016392295099794868,
|
||||||
|
"median": 0.041875139921903615,
|
||||||
|
"mad": 0.001360640600323683,
|
||||||
|
"p90": 0.043799061246216305,
|
||||||
|
"p99": 0.04486697037369015,
|
||||||
|
"p999": 0.045995346079021705,
|
||||||
|
"max": 0.04610396962612869
|
||||||
|
},
|
||||||
|
"p999": {
|
||||||
|
"count": 357,
|
||||||
|
"min": 0.019085338560864334,
|
||||||
|
"median": 0.05054646224901104,
|
||||||
|
"mad": 0.002620417140424082,
|
||||||
|
"p90": 0.05440113909915092,
|
||||||
|
"p99": 0.05857364904597424,
|
||||||
|
"p999": 0.06542779265359067,
|
||||||
|
"max": 0.06649338830262444
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"split_minimum_true_strict": {
|
||||||
|
"count": 240,
|
||||||
|
"min": 0.8250381350517273,
|
||||||
|
"median": 0.8373404443264008,
|
||||||
|
"mad": 0.0029025375843048096,
|
||||||
|
"p90": 0.8425169467926026,
|
||||||
|
"p99": 0.8459141814708709,
|
||||||
|
"p999": 0.8461976568698883,
|
||||||
|
"max": 0.8462259769439697
|
||||||
|
},
|
||||||
|
"split_minimum_other_candidates": {
|
||||||
|
"count": 392545,
|
||||||
|
"min": 3.158521940349601e-05,
|
||||||
|
"median": 0.02823474630713463,
|
||||||
|
"mad": 0.008037906140089035,
|
||||||
|
"p90": 0.04402520880103111,
|
||||||
|
"p99": 0.08912204891443251,
|
||||||
|
"p999": 0.662676521778107,
|
||||||
|
"max": 0.7688561081886292
|
||||||
|
},
|
||||||
|
"strict_false_reduction": 9784.333333333334,
|
||||||
|
"strict_false_reduction_fraction": 0.9998977957960004,
|
||||||
|
"first_stage_threshold_unchanged": 0.030964473262429237,
|
||||||
|
"part_threshold": 0.08474038063392896,
|
||||||
|
"rf_tx": 0
|
||||||
|
}
|
||||||
7276
data/processed/lab052/stream.json
Normal file
7276
data/processed/lab052/stream.json
Normal file
File diff suppressed because it is too large
Load Diff
7275
data/processed/lab052/stream_repeat_1.json
Normal file
7275
data/processed/lab052/stream_repeat_1.json
Normal file
File diff suppressed because it is too large
Load Diff
7275
data/processed/lab052/stream_repeat_2.json
Normal file
7275
data/processed/lab052/stream_repeat_2.json
Normal file
File diff suppressed because it is too large
Load Diff
7275
data/processed/lab052/stream_repeat_3.json
Normal file
7275
data/processed/lab052/stream_repeat_3.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -308,6 +308,46 @@
|
|||||||
кандидатов в полном потоке 5 кадров/с с тем же PHY/FEC/CRC.
|
кандидатов в полном потоке 5 кадров/с с тем же PHY/FEC/CRC.
|
||||||
- **RF / Git:** RF TX=0; Lab051 git add/commit и push не выполнялись. STOP.
|
- **RF / Git:** RF TX=0; Lab051 git add/commit и push не выполнялись. STOP.
|
||||||
|
|
||||||
|
## Lab052
|
||||||
|
|
||||||
|
- **Цель:** снизить число ложных строгих проверок на 500 ксимв/с при сохранении
|
||||||
|
BPSK, PRBS7, PHY/FEC/CRC, фрагментации и всех принимаемых настоящих пакетов.
|
||||||
|
- **База:** Lab051 закреплена разрешённым коммитом
|
||||||
|
`9f33607d3ae82992c595de37dd64e99d7e4dea73`; 13 файлов, без push.
|
||||||
|
- **Профиль:** 1675842 локальных пика, 392785 кандидатов первого каскада;
|
||||||
|
прежний RX выполнил 147005 строгих проверок: 146765 ложных и 240 истинных.
|
||||||
|
Исторические 147082 Lab051 означали все кандидаты, а не только ложные.
|
||||||
|
- **Алгоритм:** общий дешёвый расчёт корреляций двух половин преамбулы,
|
||||||
|
локальная нормировка каждой половины; ограниченная полная ширина группы
|
||||||
|
20480 отсчётов, максимум первым, альтернативы сохраняются после FAIL.
|
||||||
|
Первый порог прежний; VALID только после штатного PHY/FEC/CRC.
|
||||||
|
- **Отвергнутый вариант:** безусловное подавление соседних меньших пиков
|
||||||
|
теряло слабый пакет рядом с сильной ложной преамбулой. Контрпример adversary
|
||||||
|
исправлен и закреплён тестами, включая границы блоков и плохую CRC.
|
||||||
|
- **Кандидаты:** 388407 отклонены дешёво, 384 группы, 243 представителя +12
|
||||||
|
альтернативных строгих проверок; 15 ложных, 240 настоящих, 0 потерь.
|
||||||
|
Сокращение ложных строгих проверок 9784,33×. Все 240 уникальных стартов совпали.
|
||||||
|
- **Полный поток:** C/5 JPEG в секунду, 30 изображений, 240/240 пакетов,
|
||||||
|
30/30 JPEG SHA MATCH. Медианный полный проход 7,369795 с /6,044416 с IQ
|
||||||
|
=1,219273. Возраст 234,520/834,077/1549,595 мс, все 30 просрочены,
|
||||||
|
**FRESH DISPLAY FPS=0; REAL-TIME CAPABLE=NO**.
|
||||||
|
- **Остаточная нагрузка:** настоящие пакеты 3,996005 с, общая децимация/
|
||||||
|
корреляция 1,790240 с. Все ложные проверки вместе лишь 28,884 мс.
|
||||||
|
- **Память:** удерживаемый IQ 3,031 МиБ, инженерная оценка массивов 136,161 МиБ,
|
||||||
|
измеренный process RSS peak 160,984 МиБ. Шумовой тест 16,78 с не накапливает
|
||||||
|
IQ/кандидатов; внешний live-backlog остаётся нереализованным.
|
||||||
|
- **Регрессии:** 37/45 слабых сценариев у обоих приёмников, 0 дополнительных
|
||||||
|
потерь. Старая запись 20 ксимв/с: 36/36, CRC PASS, JPEG SHA MATCH.
|
||||||
|
- **Код:** [обнаружитель](../experiments/lab052_candidate_detection.py),
|
||||||
|
[опыты](../experiments/lab052_detector_study.py),
|
||||||
|
[статистика](../experiments/lab052_detection_statistics.py),
|
||||||
|
[30 тестов](../tests/test_lab052_candidate_detection.py).
|
||||||
|
- **Отчёт:** [состояние](lab052_current_state.md),
|
||||||
|
[основной поток](../data/processed/lab052/stream.json). Журнал: запись 041.
|
||||||
|
- **Далее:** отдельное исследование истинных пакетов, общей обработки IQ и
|
||||||
|
задержки JPEG-пачки; текущий этап завершить на программных результатах.
|
||||||
|
- **RF / Git:** RF TX=0; Lab052 git add/commit и push не выполнялись. STOP.
|
||||||
|
|
||||||
## Правило хранения данных
|
## Правило хранения данных
|
||||||
|
|
||||||
Большие записи IQ, потоки `cs8`, бинарные объекты и другие аппаратные исходные
|
Большие записи IQ, потоки `cs8`, бинарные объекты и другие аппаратные исходные
|
||||||
|
|||||||
390
docs/lab052_current_state.md
Normal file
390
docs/lab052_current_state.md
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
# Lab052 — снижение числа ложных кандидатов при 500 ксимв/с
|
||||||
|
|
||||||
|
Дата: 8 сентября 2026 года. SOFTWARE ONLY. RF TX = 0.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Ложные строгие проверки сокращены с **146765 до 15**, в **9784,33 раза**
|
||||||
|
(99,9898%). Сохранены все **240 уникальных физических пакетов**, получены
|
||||||
|
**30/30 JPEG с точными байтами**. Программное исследование завершено.
|
||||||
|
|
||||||
|
**Реальное время и 5 свежих изображений/с не достигнуты.** Медианный из трёх
|
||||||
|
полных проходов: **7,369795 с обработки / 6,044416 с IQ = 1,219273**.
|
||||||
|
Возраст декодированных изображений: **234,520 / 834,077 / 1549,595 мс**
|
||||||
|
(min / median / max). При MAX_VIDEO_AGE=200 мс все 30 просрочены,
|
||||||
|
**DISPLAYED=FRESH DISPLAYED=0, FRESH DISPLAY FPS=0**.
|
||||||
|
|
||||||
|
В этих условиях ложные проверки уже не определяют скорость: они занимают
|
||||||
|
28,884 мс за весь опыт, 0,392% полной обработки. Дальнейшее удаление только
|
||||||
|
этих проверок не закроет оставшееся отставание.
|
||||||
|
|
||||||
|
Рабочая точка: BPSK, 500000 символов/с, Fs=8000000 отсчётов/с, 16 отсчётов/символ.
|
||||||
|
JPEG C: 128×96 RGB, quality 25, 1740–1893 байта. PRBS7, PHY, FEC, CRC,
|
||||||
|
интерливер, формат пакета и фрагмента не менялись. Файлы protocol не изменены.
|
||||||
|
Управление RadioMaster → ExpressLRS → штатный ELRS RX → CRSF → контроллер
|
||||||
|
остаётся отдельным физическим каналом; в SDR-поток управление не добавлялось.
|
||||||
|
Временные подмены старых функций рассчитаны на один последовательный RX-worker;
|
||||||
|
потокобезопасность и одновременная работа нескольких таких приёмников не реализованы.
|
||||||
|
|
||||||
|
## Git и аудит
|
||||||
|
|
||||||
|
Проверены root/main/HEAD/status, diff stat/check, отчёт Lab051, запись 040,
|
||||||
|
индекс лабораторных, три новых вычислительных модуля, тесты и итоговые JSON.
|
||||||
|
До новой реализации quick_gate PASS; pytest **688 passed, 0 failed**.
|
||||||
|
Verifier независимо подтвердил состав и согласованность данных.
|
||||||
|
|
||||||
|
По прямому разрешению пункта 0 задания показаны staged stat/status и создан:
|
||||||
|
|
||||||
|
`9f33607d3ae82992c595de37dd64e99d7e4dea73`
|
||||||
|
|
||||||
|
`Lab051: accelerate 500 ksymbol/s streaming receiver`
|
||||||
|
|
||||||
|
Ровно 13 файлов, 9695 добавлений: код/тесты/отчёт Lab051, журнал и индекс,
|
||||||
|
baseline_profile.json, benchmark.json, stream.json, memory.json, replay.json,
|
||||||
|
regression20k/result.json. IQ-окна, промежуточные замеры, JPEG и посторонние
|
||||||
|
файлы не добавлялись. Push не выполнялся. Это committed HEAD всей Lab052.
|
||||||
|
|
||||||
|
## Что означают исходные 147 тысяч
|
||||||
|
|
||||||
|
В исходном JSON Lab051 **147082 — все строгие кандидаты**, включая 240 принятых;
|
||||||
|
ложных было **146842**. Число 12239 — новые soft/timing-окна, включая настоящие.
|
||||||
|
Исторический processing/RF=6,391045, fresh FPS=0. Называть 147082 ложными неверно.
|
||||||
|
|
||||||
|
Lab051 не сохраняла измеренные encode_seconds каждого JPEG. Её длинная временная
|
||||||
|
сетка немного менялась между запусками. Поэтому в Lab052 тот же генератор,
|
||||||
|
noise seed=50050, coded-bit Eb/N0=7 дБ, CFO=+10 Гц, фаза 0,73, чередование
|
||||||
|
полярности и CS8-квантование используют **однократно измеренную и сохранённую
|
||||||
|
готовность JPEG** в source_fixture.json. Проверяются SHA всех 30 JPEG.
|
||||||
|
Все новые варианты получают одну и ту же последовательность IQ; сравнение
|
||||||
|
потерь и кандидатов ниже относится к этой общей сетке. Историческая сетка
|
||||||
|
Lab051 не объявляется побитно воспроизведённой.
|
||||||
|
|
||||||
|
Свежий профиль прежнего Lab051, после независимых решений:
|
||||||
|
|
||||||
|
| Метрика | Значение |
|
||||||
|
|---|---:|
|
||||||
|
| IQ / Fs | 6,044416 с / 8 Мвыб/с |
|
||||||
|
| TOTAL LOCAL PEAKS | 1675842 |
|
||||||
|
| FIRST-STAGE CANDIDATES | 392785 |
|
||||||
|
| STRICT CANDIDATES | 147005 |
|
||||||
|
| TRUE CANDIDATES | 240 |
|
||||||
|
| FALSE CANDIDATES | 146765 |
|
||||||
|
| CANDIDATES / RF SECOND | 24320,795 |
|
||||||
|
| FALSE CANDIDATES / RF SECOND | 24281,089 |
|
||||||
|
| Уникальных уточнённых позиций ложных кандидатов | 11387 |
|
||||||
|
| Повторных уточнённых позиций | 135378 |
|
||||||
|
|
||||||
|
Отдельный свежий проход старого Lab051 **без наблюдателя**, на этой же
|
||||||
|
зафиксированной сетке: **41,633312 с, processing/RF=6,887897**, 240/240 пакетов
|
||||||
|
и 30/30 точных JPEG. Сравнение с итоговыми 7,369795 с даёт около **5,65×**
|
||||||
|
ускорения всего приёмника. Относительно исторических 38,630132 с — **5,24×**.
|
||||||
|
Инструментированный профиль и чистый контроль хранятся раздельно; настольная
|
||||||
|
нагрузка и прогрев влияют на временные числа.
|
||||||
|
|
||||||
|
Сначала детектор получает каждый локальный максимум выше низкого фиксированного
|
||||||
|
порога. Только затем пропускает область уже принятого пакета. На продолжительном
|
||||||
|
шуме число проб растёт с числом входных отсчётов. Нормировка на энергию означает,
|
||||||
|
что малая абсолютная мощность шума сама по себе не уменьшает корреляционную
|
||||||
|
статистику. Более 135 тысяч проверок вновь приходят к уже встречавшимся позициям.
|
||||||
|
|
||||||
|
PRBS7 повторяется через 127 символов, то есть через 2032 отсчёта в рабочем режиме.
|
||||||
|
Преамбула содержит 1280 символов. Поэтому рядом с ней возможны сильные боковые
|
||||||
|
корреляционные пики, помимо случайных шумовых максимумов. Только проверка
|
||||||
|
структуры и последующий PHY/FEC/CRC различают их надёжнее одного большого пика.
|
||||||
|
|
||||||
|
## Распределения и фон
|
||||||
|
|
||||||
|
Истинные главные пики определены после CRC PASS: берётся максимум в прежней
|
||||||
|
области уточнения около восстановленного старта. Это диагностическая метрика,
|
||||||
|
не дополнительный вход алгоритма. Числа истинных кадров также сверены по
|
||||||
|
множеству всех 240 уникальных уточнённых стартов, а не только по счётчику.
|
||||||
|
|
||||||
|
| Корреляционная статистика | min | median | p99 | max |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| Главный пик настоящей преамбулы, 240 | 0,830055 | 0,841691 | 0,848389 | 0,849945 |
|
||||||
|
| Ложные строгие кандидаты, 146765 | 0,030964 | 0,035464 | 0,055803 | 0,805951 |
|
||||||
|
| Меньшая корреляция половин у принятых кандидатов | 0,825038 | 0,837340 | 0,845914 | 0,846226 |
|
||||||
|
|
||||||
|
Отдельно после приёма выделены 357 блоков, чьи корреляционные окна не пересекают
|
||||||
|
ни одного декодированного физического кадра. Медиана блочных медиан =0,016353,
|
||||||
|
медиана MAD =0,006184, медиана квантилей 99% =0,041875.
|
||||||
|
Диапазон медиан 0,006451–0,017422 показывает, что фон не идеально стационарен.
|
||||||
|
MAD — медиана абсолютного отклонения от медианы; здесь это мера разброса.
|
||||||
|
Полные min/median/MAD/p90/p99/p999/max по блокам сохранены в JSON.
|
||||||
|
|
||||||
|
Адаптацию порога по median/MAD рассмотрели, но не выбрали: она не устраняет
|
||||||
|
повторные структурные пики и потребовала бы отдельно доказывать чувствительность
|
||||||
|
при смене фона. Основное правило использует **локальную энергию каждой половины**
|
||||||
|
наблюдаемого IQ. Глобальный median/second или TX reference в решении не участвуют.
|
||||||
|
На меняющемся фоне и в слабых положительных регрессиях дополнительные потери
|
||||||
|
не наблюдались; универсальная чувствительность при любом нестационарном канале
|
||||||
|
этим конечным исследованием не доказана.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Выбранное правило
|
||||||
|
|
||||||
|
1. Прежняя грубая PRBS7-корреляция на двух отсчётах/символ и прежний первый
|
||||||
|
порог **0.030964473262429237**. Все локальные пики считаются одинаково с baseline.
|
||||||
|
2. На уже децимированном общем входе блока считаются корреляции двух половин
|
||||||
|
опорной формы. Полная форма содержит 2644 отсчёта, каждая половина — 1322.
|
||||||
|
Для каждой позиции обе величины нормируются **своими** энергиями IQ и опоры.
|
||||||
|
Требуются обе корреляции ≥**0.08474038063392896**. Одиночная сильная половина
|
||||||
|
этого условия не выполняет. Неизвестная фаза/полярность сохраняется через
|
||||||
|
модуль; согласование фаз половин, сужающее CFO, не вводилось.
|
||||||
|
3. Прошедшие пики объединяются в группы с полной шириной ≤20480 отсчётов
|
||||||
|
**от первого пика**, без продления по цепочке следующих соседей. На границе
|
||||||
|
блока группа обрывается: это может добавить работу, но не скрывает следующий пик.
|
||||||
|
4. Представитель — максимум нормированной корреляции; при равенстве правый пик.
|
||||||
|
Если он не проходит строгий тракт, проверяется следующая альтернатива группы.
|
||||||
|
Только реальный CRC PASS разрешает прежний пропуск принятого физического кадра.
|
||||||
|
5. VALID остаётся исключительно прежним **PHY → FEC → CRC PASS**.
|
||||||
|
|
||||||
|
Корреляции частей — общие FFT на блок, не новое timing-окно для каждого пика.
|
||||||
|
Это дополнительные согласованные фильтры частей преамбулы на децимированном
|
||||||
|
сигнале. Повторно использовать полный RRC-выход для предварительного решения
|
||||||
|
не потребовалось: его создание на шумовых блоках дороже этой проверки.
|
||||||
|
Полный RRC/timing и все 1601 прежних смещения остались в строгом тракте.
|
||||||
|
|
||||||
|
Порог половины выбран до положительных сравнений из номинальной шумовой модели
|
||||||
|
`|rho|² ~ Beta(1,N−1)`, N=640, alpha=0,01 на часть:
|
||||||
|
`sqrt(1−alpha**(1/(N−1)))`. Это инженерная исходная настройка, **не** доказанная
|
||||||
|
вероятность ложного принятия после фильтрации и выбора максимумов. Корреляция
|
||||||
|
отсчётов и отбор пиков нарушают независимость; фактические отказы измерены отдельно.
|
||||||
|
Ни порог, ни ширина не выбираются по TX offsets.
|
||||||
|
|
||||||
|
Обоснование ширины для двух разных последовательных физических кадров:
|
||||||
|
|
||||||
|
`MIN_FRAME_SAMPLES=(1280+144+2*(12*8+6)+32+10)*16=26720`.
|
||||||
|
|
||||||
|
Точность прежнего локального уточнения ±2000 плюс сетка децимации 8:
|
||||||
|
`20480 < 26720−2*2008 = 22704`.
|
||||||
|
Поэтому два центральных кандидата неперекрывающихся минимальных кадров,
|
||||||
|
локализованных в этом диапазоне, не попадают в одну группу. Это обоснование
|
||||||
|
для последовательного потока, не обещание разрешить любые радиоколлизии.
|
||||||
|
|
||||||
|
## Почему первый вариант был отвергнут
|
||||||
|
|
||||||
|
Изначальное подавление всех меньших пиков в радиусе одной преамбулы дало
|
||||||
|
0 ложных строгих проверок на основном опыте. Adversary воспроизвёл потерю:
|
||||||
|
ложная сильная преамбула на отсчёте 10000 без правильного заголовка подавляла
|
||||||
|
настоящий слабый пакет на 30000. Lab051 его принимала, новый вариант — нет.
|
||||||
|
Одного обоснования минимальной дистанцией двух **настоящих** кадров недостаточно
|
||||||
|
для случая сильного ложного пика. Этот вариант **отвергнут**.
|
||||||
|
|
||||||
|
Исправление: дешёвая проверка всех локальных кандидатов до выбора представителя,
|
||||||
|
ограниченная полная ширина группы и сохранение альтернатив после FAIL.
|
||||||
|
Контрпример, его сдвиги через границу IQ-блока и сильный пакет с плохой CRC
|
||||||
|
перепроверены независимо. Корректный слабый пакет теперь сохраняется.
|
||||||
|
Итог Lab052 — **15**, а не предварительные 0 ложных проверок.
|
||||||
|
|
||||||
|
## Сравнение числа частей
|
||||||
|
|
||||||
|
Во всех четырёх вариантах сохранены те же 240 уникальных стартов и 30 точных JPEG.
|
||||||
|
Наблюдатель собирал полные распределения; время этих проходов не является
|
||||||
|
основным измерением производительности.
|
||||||
|
|
||||||
|
| Правило | Порог части | Ложные строгие проверки | Сокращение | Потеря настоящих |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| Только группы, с альтернативами после FAIL | — | 145863 | 1,006× | 0 |
|
||||||
|
| Одна часть, контроль скалярного порога | 0,059951 | 332 | 442,06× | 0 |
|
||||||
|
| **Две части, основной вариант** | **0,084740** | **15** | **9784,33×** | **0** |
|
||||||
|
| Три части | 0,103813 | 6 | 24460,83× | 0 |
|
||||||
|
|
||||||
|
Одна часть — только сравнительный контроль, не принятый ответ «поднять порог».
|
||||||
|
Третья часть убирает ещё девять проверок, но увеличивает стоимость общего
|
||||||
|
этапа: 0,531 → 0,819 с в инструментированных проходах. Две части — минимальный
|
||||||
|
вариант с проверкой распределённой структуры и большим запасом к ориентиру 50×.
|
||||||
|
Группировка сама по себе с корректным возвратом альтернатив почти не помогает
|
||||||
|
на распределённом шуме: отказы нельзя использовать для безусловного удаления соседей.
|
||||||
|
|
||||||
|
## Окончательные вычисления и свежесть
|
||||||
|
|
||||||
|
Три отдельных полных прохода без наблюдателя: processing/RF
|
||||||
|
**1,257688 / 1,219199 / 1,219273**, в каждом 240/240 пакетов, 30/30 JPEG,
|
||||||
|
15 ложных проверок и fresh FPS=0. Выбран целиком третий проход — медианный
|
||||||
|
по суммарному времени, без смешивания временных отметок разных запусков.
|
||||||
|
|
||||||
|
| Стадия / счётчик | Итог |
|
||||||
|
|---|---:|
|
||||||
|
| TOTAL LOCAL PEAKS | 1675842 |
|
||||||
|
| FIRST-STAGE / CHEAP CHECKED | 392785 |
|
||||||
|
| CHEAP REJECTED | 388407 |
|
||||||
|
| CHEAP RETAINED | 4378 |
|
||||||
|
| CLUSTERS, включая группы внутри уже принятых кадров | 384 |
|
||||||
|
| REPRESENTATIVE / ALTERNATIVE STRICT CHECKS | 243 / 12 |
|
||||||
|
| Пропущено внутри принятого физического кадра | 4123 |
|
||||||
|
| STRICT FALSE / TRUE | 15 / 240 |
|
||||||
|
| STRICT CHECKS / RF SECOND | 42,187699 |
|
||||||
|
| FALSE CHECKS / RF SECOND | 2,481629 |
|
||||||
|
| Новые soft/timing-окна | 488 |
|
||||||
|
| Грубая корреляция и децимация | 1,790240 с |
|
||||||
|
| Общие проверки частей | 0,446616 с |
|
||||||
|
| Группировка | 0,003113 с |
|
||||||
|
| Строгие настоящие / ложные | 3,996005 / 0,028884 с |
|
||||||
|
| Среднее настоящего кандидата | 16,650 мс |
|
||||||
|
| Суммарно RX / сборка / JPEG decode | 7,358399 / 0,006008 / 0,005388 с |
|
||||||
|
| Полная обработка | 7,369795 с |
|
||||||
|
| PROCESSING / RF | 1,219273 |
|
||||||
|
| INPUT FPS / FRAMES | 5 / 30 |
|
||||||
|
| RX PACKETS / IMAGES | 240 / 30 |
|
||||||
|
| DROPPED BEFORE TX / STALE AFTER DECODE | 0 / 30 |
|
||||||
|
| DISPLAYED / FRESH DISPLAYED / FRESH FPS | 0 / 0 / 0 |
|
||||||
|
| RAW RECEIVED FPS в первые 6 с шкалы работника | 4 |
|
||||||
|
| Возраст декодированных JPEG min / median / max | 234,520 / 834,077 / 1549,595 мс |
|
||||||
|
| MAX_VIDEO_AGE | 200 мс |
|
||||||
|
|
||||||
|
После устранения основной массы ложных кандидатов обнаружена лишняя подготовка:
|
||||||
|
старый адаптер очищал кэш и 734 раза строил ту же опорную форму. В начальном
|
||||||
|
cProfile это занимало 3,544 с. В Lab052 три неизменных массива — опора, RRC taps
|
||||||
|
и PRBS7 bits — хранятся в самом приёмнике. Они побитно сравнены со старым
|
||||||
|
расчётом; функции возвращаются в finally до выхода старого контекста режима.
|
||||||
|
Это не новая PHY и не передача данных TX в RX.
|
||||||
|
|
||||||
|
Финальный отдельный cProfile только вокруг push/finish показывает следующие
|
||||||
|
узкие места: FFT 1,650 с собственного времени, FIR-децимация 1,295 с,
|
||||||
|
Viterbi 1,397 с; уточнение 1,084 с и matched 0,865 с включают свои FFT,
|
||||||
|
поэтому эти строки нельзя складывать повторно. Копирование ndarray — 0,313 с,
|
||||||
|
прочие расходы push — также измерены. Данные cProfile не заменяют чистый замер.
|
||||||
|
|
||||||
|
Теперь основное ограничение — **настоящие пакеты вместе с общей обработкой IQ**.
|
||||||
|
40 пакетов/с ×16,650 мс требуют около 666 мс обработки в секунду; к ним добавляются
|
||||||
|
децимация/корреляция, общий промежуточный этап, копирование и организация блоков.
|
||||||
|
Во время JPEG-пачки пакет приходит каждые 11,532 мс, быстрее средней строгой
|
||||||
|
обработки настоящего пакета. Нужны и дальнейшее снижение общей стоимости,
|
||||||
|
и проверка задержки пачки. Одного среднего processing/RF<1 недостаточно для
|
||||||
|
доказательства MAX_VIDEO_AGE=200 мс при неравномерной нагрузке.
|
||||||
|
|
||||||
|
Событийная шкала повторно использует Lab051:
|
||||||
|
`worker_start=max(previous_worker_end, block_arrival); worker_end=start+measured_push`.
|
||||||
|
Сборка/JPEG decode добавляют измеренные затраты. RF и CPU перекрываются;
|
||||||
|
стоимость RX не заменяется нулём. Генерация IQ, сравнение SHA и JSON I/O исключены.
|
||||||
|
Восстановленные байты сверяются с TX только после принятия, сборки и JPEG decode.
|
||||||
|
|
||||||
|
Возраст относится к **декодированным, затем отброшенным** изображениям.
|
||||||
|
Камера и физический экран не измерялись, их задержки UNKNOWN. Capture lookup
|
||||||
|
используется после вердикта для отчётной модели вывода. В неизменном формате
|
||||||
|
фрагмента нет capture timestamp; независимый live-контроль возраста не реализован.
|
||||||
|
Максимальное отставание 1,426779 с соответствует примерно 91313848 байтам
|
||||||
|
внешнего complex64 IQ. Этот backlog вычислен, но не хранится офлайн-источником.
|
||||||
|
REAL-TIME CAPABLE=NO; 5 FPS SOFTWARE REAL-TIME TARGET=FAIL.
|
||||||
|
|
||||||
|
## Память и длительное состояние
|
||||||
|
|
||||||
|
- Пик удерживаемого IQ: **3177728 байт = 3,031 МиБ**; проверяемый предел 3398912.
|
||||||
|
- Инженерная оценка рабочих массивов: **142775456 байт = 136,161 МиБ**.
|
||||||
|
Это прежний запас Lab051, включающий буферы; не доказанный универсальный
|
||||||
|
предел C-аллокаций и не отдельная величина для сложения с RSS.
|
||||||
|
- Оценка массивов именно обнаружителя в опыте: **1480664 байта**;
|
||||||
|
постоянные новые шаблоны — **171140 байт**. Временные FFT отдельно целиком
|
||||||
|
не перечислены, они покрываются общей инженерной оценкой с запасом.
|
||||||
|
- RSS выбранного полного прохода: **149393408 байт** после потока;
|
||||||
|
process RSS peak **168804352 байта = 160,984 МиБ**. Это весь процесс Python,
|
||||||
|
включая библиотеки, генератор и аллокатор, а не только массивы RX.
|
||||||
|
- В основном опыте одновременно максимум **23** прошедших дешёвый этап пика
|
||||||
|
и **3** группы на блок; между вызовами и после finish состояние кандидатов =0.
|
||||||
|
- Предел числа кандидатов задаётся числом локальных максимумов ограниченного
|
||||||
|
IQ-окна. Списки групп и альтернатив уничтожаются на выходе, не копятся в сессии.
|
||||||
|
|
||||||
|
Дополнительно измерен отдельный RX replay длительностью 0,063184 с IQ с одним
|
||||||
|
независимо восстановленным пакетом. Наблюдаются 6754 ndarray, из них 6681 владеют
|
||||||
|
данными; суммарные байты наблюдаемых владельцев за проход — 37378942 (не пик).
|
||||||
|
**tracemalloc peak=11586792 байта**, после выхода остаётся 6461 байт отслеживаемых
|
||||||
|
выделений. Входной IQ подготовлен до tracing. Это измерение отслеживаемой памяти
|
||||||
|
RX, включая Python, и нижняя граница числа массивов в локальных переменных;
|
||||||
|
временные C/SciPy-аллокации полностью не перечислены. Короткий replay не заменяет
|
||||||
|
RSS полного опыта. Подсчёт ndarray и tracemalloc выполняются в разных проходах;
|
||||||
|
tracemalloc измеряет следующий прогретый проход. Его tracing-время не используется
|
||||||
|
для оценки скорости, а пик не объявляется верхней границей холодного RX.
|
||||||
|
|
||||||
|
Длинный тест: **2048 блоков, 16,777216 с** распределённого шума с амплитудными
|
||||||
|
масштабами 0,002 / 0,2 / 20 / 0,02. Получены 4783206 локальных пиков,
|
||||||
|
1068038 кандидатов первого каскада; 1068037 отклонены дешёво, один дошёл
|
||||||
|
до строгого отказа. **Ни одного принятого шумового пакета**.
|
||||||
|
После прогрева в контрольных точках IQ=2653440 байт, децимация=397216 байт,
|
||||||
|
сохранённых кандидатов=0. RSS сначала растёт при прогреве кэшей, затем
|
||||||
|
стабилизируется; последние четыре точки 140533760–140550144 байта.
|
||||||
|
После finish оба IQ-буфера освобождены, константы ограниченного размера остаются.
|
||||||
|
|
||||||
|
Полная диагностическая история NPZ/JSON принадлежит конечному эксперименту,
|
||||||
|
не приёмнику. Она выключена в окончательном измерении скорости и длинном тесте.
|
||||||
|
Ограниченный RX не подменяет отсутствующую живую входную очередь с overflow/gap
|
||||||
|
policy: аппаратный драйвер и переполнения в этой лабораторной не реализованы.
|
||||||
|
|
||||||
|
## Регрессии и проверка
|
||||||
|
|
||||||
|
45 дополнительных слабых сценариев: Eb/N0=0/1/2/3/7 дБ, CFO=−100/+10/+100 Гц,
|
||||||
|
три seed, масштабы амплитуды 0,002/0,2/1 и разные фазы/полярности.
|
||||||
|
Старый и новый RX правильно приняли **37/45**; **0** потерянных пакетов baseline.
|
||||||
|
Восемь случаев не принимаются обоими, поэтому это не объявляется 45/45 успешным
|
||||||
|
декодированием. Основной поток отдельно даёт 240/240 и 30/30 SHA MATCH.
|
||||||
|
|
||||||
|
30 быстрых целевых тестов покрывают шумовые группы, равные пики, отсутствие
|
||||||
|
цепочного расширения, два минимальных соседних кадра, слабый кадр, одну сильную
|
||||||
|
ложную половину, фазу/масштаб, распределённый и меняющийся шум, границы блоков,
|
||||||
|
освобождение состояния, эквивалентность общих корреляций отдельным vdot,
|
||||||
|
ложную преамбулу и восстановление функций. Verifier и adversary независимо
|
||||||
|
подтвердили исправление воспроизведённой потери; обхода PHY/FEC/CRC не найдено.
|
||||||
|
|
||||||
|
Сохранённая Lab047-запись **20 ксимв/с** повторно разобрана прежним Lab048:
|
||||||
|
**36/36, CRC PASS, JPEG SHA MATCH**, 19 ошибок до FEC, 0 после, 0 байтовых ошибок.
|
||||||
|
SHA: `e913dab9ac32795250880a4be67fbbbf89a4f96536842476ab591d1acaafc02a`.
|
||||||
|
Обработка 15 с старого IQ заняла 13,213346 с. Новый адаптер принимает только
|
||||||
|
500 ксимв/с; режим 20 ксимв/с, его порог и вычислительный тракт не менялись.
|
||||||
|
|
||||||
|
Итоговый quick_gate PASS: **718 тестов, 20 функциональных проверок,
|
||||||
|
13 штатных пропусков**. Полный pytest: **718 passed за 77,15 с, 0 failed**;
|
||||||
|
одно предупреждение доступа к .pytest_cache не влияет на результат тестов.
|
||||||
|
Дополнительный helper измерения массивов выполнен отдельно, все три модуля
|
||||||
|
после его добавления проверены py_compile и своими run_functional_tests.
|
||||||
|
git diff --check PASS. Машинная сверка итоговых JSON — в acceptance.json:
|
||||||
|
обязательные регрессии PASS, real-time и 5-fps target явно FAIL.
|
||||||
|
|
||||||
|
## Файлы и воспроизведение
|
||||||
|
|
||||||
|
- [Обнаружитель и потоковый адаптер](../experiments/lab052_candidate_detection.py)
|
||||||
|
- [Генератор сравнений, измерений и регрессий](../experiments/lab052_detector_study.py)
|
||||||
|
- [Статистика и график](../experiments/lab052_detection_statistics.py)
|
||||||
|
- [Целевые тесты](../tests/test_lab052_candidate_detection.py)
|
||||||
|
- [Профиль старого RX](../data/processed/lab052/baseline_profile.json)
|
||||||
|
- [Старый RX без наблюдателя на том же источнике](../data/processed/lab052/baseline_stream.json)
|
||||||
|
- [Сравнение правил](../data/processed/lab052/rule_comparison.json)
|
||||||
|
- [Статистика после решений](../data/processed/lab052/statistics.json)
|
||||||
|
- [Итоговый полный поток](../data/processed/lab052/stream.json)
|
||||||
|
- [Длинное состояние](../data/processed/lab052/memory.json)
|
||||||
|
- [Измеренные массивы и tracemalloc](../data/processed/lab052/allocations.json)
|
||||||
|
- [Слабые сигналы](../data/processed/lab052/sensitivity.json)
|
||||||
|
- [Отдельный профиль CPU](../data/processed/lab052/receiver_cpu.txt)
|
||||||
|
- [Старая запись 20 ксимв/с](../data/processed/lab052/regression20k/result.json)
|
||||||
|
- [Сверка критериев и отрицательного результата скорости](../data/processed/lab052/acceptance.json)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python experiments/lab052_detector_study.py --stage profile
|
||||||
|
python experiments/lab052_detector_study.py --stage baseline
|
||||||
|
python experiments/lab052_detector_study.py --stage rules
|
||||||
|
python experiments/lab052_detector_study.py --stage stream
|
||||||
|
python experiments/lab052_detector_study.py --stage cpu
|
||||||
|
python experiments/lab052_detector_study.py --stage memory
|
||||||
|
python experiments/lab052_detector_study.py --stage sensitivity
|
||||||
|
python experiments/lab052_detection_statistics.py
|
||||||
|
python -m pytest tests/test_lab052_candidate_detection.py -q -p no:cacheprovider
|
||||||
|
python tools/quick_gate.py
|
||||||
|
python -m pytest tests/ -q
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Старый IQ: команда Lab048 с параметрами `--run-saved-capture`,
|
||||||
|
`--capture C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\rx_stream.cs8`,
|
||||||
|
`--original-result C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\independent_result.json`,
|
||||||
|
`--tx-image data/processed/lab047/tx_image.jpg`,
|
||||||
|
`--output-directory data/processed/lab052/regression20k`.
|
||||||
|
|
||||||
|
Первый запуск фиксирует измеренную готовность JPEG; последующие используют
|
||||||
|
source_fixture.json. Между запусками меняются фактические CPU timings и RSS,
|
||||||
|
поэтому временные числа могут отличаться. Основной приём и набор кандидатов
|
||||||
|
на зафиксированном источнике воспроизводятся. Большой новый IQ-файл не создаётся.
|
||||||
|
|
||||||
|
Lab052 git add/commit и push не выполнялись. RF TX=0; HackRF и другое
|
||||||
|
оборудование не перечислялись и не запускались. QPSK, 180 МГц, аппаратные
|
||||||
|
500 ксимв/с и переход к 10 fps не выполнялись. Следующий рекомендуемый этап —
|
||||||
|
отдельная оптимизация истинных пакетов/общего IQ и задержки JPEG-пачки. STOP.
|
||||||
287
experiments/lab052_candidate_detection.py
Normal file
287
experiments/lab052_candidate_detection.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
"""Lab052: локальные группы пиков и дешёвая проверка частей PRBS7.
|
||||||
|
|
||||||
|
Кандидат никогда не становится пакетом без прежних PHY/FEC/CRC. Параметры
|
||||||
|
заданы длинами PHY и моделью шума, без ожидаемых стартов и содержимого TX.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
if __package__ in (None, ""):
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.signal import fftconvolve
|
||||||
|
|
||||||
|
from experiments import lab051_streaming_receiver as previous
|
||||||
|
|
||||||
|
phy = previous.phy
|
||||||
|
offline = previous.offline
|
||||||
|
SAMPLES_PER_SYMBOL = 16
|
||||||
|
PREAMBLE_SAMPLES = phy.lab045.PREAMBLE_BIT_COUNT * SAMPLES_PER_SYMBOL
|
||||||
|
COARSE_ERROR_SAMPLES = 2000 + 8 # исходное уточнение ±2000 плюс сетка децимации
|
||||||
|
MIN_FRAME_SAMPLES = (phy.lab045.PREAMBLE_BIT_COUNT + phy.lab045.PHY_HEADER_BIT_COUNT
|
||||||
|
+ phy.lab045.conv.encoded_bit_count(phy.lab045.MIN_PACKET_BYTES * 8)
|
||||||
|
+ 2 * phy.lab045.GUARD_SYMBOL_COUNT + phy.lab045.RRC_SPAN_SYMBOLS) * SAMPLES_PER_SYMBOL
|
||||||
|
CLUSTER_WIDTH_SAMPLES = PREAMBLE_SAMPLES
|
||||||
|
PART_FALSE_ALARM = .01
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DetectorRule:
|
||||||
|
"""parts=0 оставляет только группировку; 1/2/3 нужны для сравнения правил."""
|
||||||
|
|
||||||
|
parts: int = 2
|
||||||
|
cluster_width_samples: int = CLUSTER_WIDTH_SAMPLES
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if type(self.parts) is not int or not 0 <= self.parts <= 3:
|
||||||
|
raise ValueError("Нужно от нуля до трёх частей")
|
||||||
|
if (type(self.cluster_width_samples) is not int or self.cluster_width_samples < 0
|
||||||
|
or self.cluster_width_samples % 8
|
||||||
|
or self.cluster_width_samples >= MIN_FRAME_SAMPLES - 2 * COARSE_ERROR_SAMPLES):
|
||||||
|
raise ValueError("Окно должно различать два минимальных физических кадра")
|
||||||
|
|
||||||
|
|
||||||
|
def peak_groups(indexes: np.ndarray, width: int):
|
||||||
|
"""Полная ширина от первого пика, без продолжения по цепочке соседей.
|
||||||
|
|
||||||
|
На границе IQ-блока группа обрывается: возможна лишняя проверка, но ни
|
||||||
|
один следующий пик не теряется из-за представителя из другого блока.
|
||||||
|
"""
|
||||||
|
cursor = 0
|
||||||
|
while cursor < len(indexes):
|
||||||
|
end = int(np.searchsorted(indexes, indexes[cursor] + width, side="right"))
|
||||||
|
yield indexes[cursor:end]
|
||||||
|
cursor = end
|
||||||
|
|
||||||
|
|
||||||
|
def cluster_peak_indexes(scores: np.ndarray, width: int, threshold: float) -> np.ndarray:
|
||||||
|
"""Первые представители ограниченных групп; равенство → правый пик."""
|
||||||
|
peaks = offline._all_local_peak_indexes(scores)
|
||||||
|
peaks = peaks[scores[peaks] >= threshold]
|
||||||
|
return np.asarray([group[len(group) - 1 - int(np.argmax(scores[group[::-1]]))]
|
||||||
|
for group in peak_groups(peaks, width)], dtype=np.int64)
|
||||||
|
|
||||||
|
|
||||||
|
def part_correlations(values: np.ndarray, reference: np.ndarray, count: int) -> np.ndarray:
|
||||||
|
"""Несколько vdot на уже децимированном согласованном входе, без timing.
|
||||||
|
|
||||||
|
Модуль сохраняет неизвестную фазу/полярность и не вводит ограничения CFO
|
||||||
|
через согласование фаз между половинами. Каждая часть нормируется своей
|
||||||
|
энергией, поэтому усиление и смена мощности фона не требуют TX reference.
|
||||||
|
"""
|
||||||
|
if values.size != reference.size or count < 1:
|
||||||
|
return np.zeros(max(count, 0))
|
||||||
|
boundaries = np.linspace(0, reference.size, count + 1, dtype=int)
|
||||||
|
output = np.zeros(count)
|
||||||
|
for number, (low, high) in enumerate(zip(boundaries[:-1], boundaries[1:])):
|
||||||
|
part = values[low:high]
|
||||||
|
expected = reference[low:high]
|
||||||
|
energy = float(np.vdot(part, part).real) * float(np.vdot(expected, expected).real)
|
||||||
|
output[number] = abs(np.vdot(expected, part)) / math.sqrt(max(energy, 1e-30))
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def part_threshold(count: int) -> float:
|
||||||
|
"""Номинальная H0-модель: независимые комплексные отсчёты по символам.
|
||||||
|
|
||||||
|
|rho|²~Beta(1,N-1), alpha=0.01 на часть. Это исходная инженерная настройка,
|
||||||
|
не обещание точной вероятности после RRC/выбора пиков: фон проверяется отдельно.
|
||||||
|
Она фиксируется до положительных опытов и не подгоняется под их offsets.
|
||||||
|
"""
|
||||||
|
symbols = phy.lab045.PREAMBLE_BIT_COUNT // count
|
||||||
|
return math.sqrt(1 - PART_FALSE_ALARM ** (1 / (symbols - 1)))
|
||||||
|
|
||||||
|
|
||||||
|
class CandidateFrontEnd(previous.BlockFrontEnd):
|
||||||
|
"""Общая корреляция — согласованный фильтр преамбулы на двух выборках/символ.
|
||||||
|
|
||||||
|
Промежуточному этапу достаточно его исходных данных; полный общий RRC-
|
||||||
|
выход создаётся прежним строгим трактом только при прошедшем кандидате.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def coarse_scores(self, samples, reference):
|
||||||
|
raw = self.decimator.get(samples, self.base)
|
||||||
|
mean = np.mean(raw, dtype=np.complex128).astype(np.complex64)
|
||||||
|
needed = math.ceil(self.owned_end / 8) + reference.size + 1
|
||||||
|
self.detection_values = raw[:needed].copy()
|
||||||
|
self.detection_values -= mean
|
||||||
|
self.detection_values *= previous.detection_rotation(self.detection_values.size, 8)
|
||||||
|
if self.detection_values.size < reference.size:
|
||||||
|
return np.empty(0, np.float32)
|
||||||
|
scores = previous.lab043._normalized_sliding_correlation(self.detection_values, reference).astype(np.float32)
|
||||||
|
scores[~np.isfinite(scores)] = 0
|
||||||
|
np.clip(scores, 0, 1, out=scores)
|
||||||
|
return scores
|
||||||
|
|
||||||
|
def split_scores(self, reference, count, score_count):
|
||||||
|
"""Общие корреляции частей для всех кандидатов, без per-candidate FFT.
|
||||||
|
|
||||||
|
Проверка выполняется ДО выбора представителя: сильный случайный пик
|
||||||
|
одной половины не может скрыть согласованный слабый кандидат рядом.
|
||||||
|
"""
|
||||||
|
output = np.empty((score_count, count), np.float32)
|
||||||
|
boundaries = np.linspace(0, reference.size, count + 1, dtype=int)
|
||||||
|
energy = np.concatenate(([0.], np.cumsum(np.abs(self.detection_values) ** 2, dtype=np.float64)))
|
||||||
|
for number, (low, high) in enumerate(zip(boundaries[:-1], boundaries[1:])):
|
||||||
|
expected = reference[low:high]
|
||||||
|
correlation = fftconvolve(self.detection_values[low:], np.conj(expected[::-1]), mode="valid")[:score_count]
|
||||||
|
local_energy = energy[high:high + score_count] - energy[low:low + score_count]
|
||||||
|
denominator = np.sqrt(np.maximum(local_energy * float(np.vdot(expected, expected).real), 1e-30))
|
||||||
|
output[:, number] = np.abs(correlation) / denominator
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingReceiver(previous.StreamingReceiver):
|
||||||
|
"""Ограниченный IQ и кандидаты только одного блока, API получает только IQ."""
|
||||||
|
|
||||||
|
def __init__(self, symbol_rate: int = 500000, *, block_samples: int = 65536,
|
||||||
|
rule: DetectorRule = DetectorRule(), observer=None):
|
||||||
|
super().__init__(symbol_rate, block_samples=block_samples)
|
||||||
|
self.rule, self.observer = rule, observer
|
||||||
|
self.detection_statistics = Counter()
|
||||||
|
self.cheap_seconds = self.cluster_seconds = 0.0
|
||||||
|
self.true_strict_seconds = self.false_strict_seconds = 0.0
|
||||||
|
self.peak_cluster_count = self.peak_eligible_count = self.candidate_state_count = 0
|
||||||
|
self.working_detection_arrays_peak_bytes = 0
|
||||||
|
with previous.previous.streaming_mode(self.mode):
|
||||||
|
self.raw_reference = phy.lab045.lab044._preamble_reference_samples().copy()
|
||||||
|
self.matched_taps = previous.lab043._matched_filter_taps().copy()
|
||||||
|
self.preamble_bits = phy.lab045.lab044.preamble_bits().copy()
|
||||||
|
for values in (self.raw_reference, self.matched_taps, self.preamble_bits):
|
||||||
|
values.flags.writeable = False
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def constant_templates(self):
|
||||||
|
"""Старый адаптер очищает lru на каждом push; эти три константы локальны.
|
||||||
|
|
||||||
|
Возвращаем именно функции до выхода streaming_mode, чтобы его
|
||||||
|
cache_clear и соседний старый режим работали по прежним правилам.
|
||||||
|
"""
|
||||||
|
targets = ((phy.lab045.lab044, "_preamble_reference_samples", lambda: self.raw_reference),
|
||||||
|
(previous.lab043, "_matched_filter_taps", lambda: self.matched_taps),
|
||||||
|
(phy.lab045.lab044, "preamble_bits", lambda: self.preamble_bits))
|
||||||
|
originals = [(owner, name, getattr(owner, name)) for owner, name, function in targets]
|
||||||
|
try:
|
||||||
|
for owner, name, function in targets:
|
||||||
|
setattr(owner, name, function)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
for owner, name, function in reversed(originals):
|
||||||
|
setattr(owner, name, function)
|
||||||
|
|
||||||
|
def _scan(self, owned_end):
|
||||||
|
with self.constant_templates():
|
||||||
|
return self._scan_with_templates(owned_end)
|
||||||
|
|
||||||
|
def _scan_with_templates(self, owned_end):
|
||||||
|
width = self.rule.cluster_width_samples // self.decimation
|
||||||
|
front = CandidateFrontEnd(self.buffer, owned_end - self.base_sample,
|
||||||
|
self.front_statistics, decimator=self.decimator, base=self.base_sample)
|
||||||
|
started = time.perf_counter()
|
||||||
|
scores = front.coarse_scores(self.buffer, self.reference)
|
||||||
|
self.coarse_seconds += time.perf_counter() - started
|
||||||
|
threshold = np.float32(phy.lab047.FIXED_HARDWARE_CANDIDATE_THRESHOLD)
|
||||||
|
peaks = offline._all_local_peak_indexes(scores)
|
||||||
|
offsets = self.base_sample + peaks * self.decimation
|
||||||
|
owned = (offsets >= self.scan_sample) & (offsets < owned_end)
|
||||||
|
owned_peaks = peaks[owned]
|
||||||
|
first_stage = owned_peaks[scores[owned_peaks] >= threshold]
|
||||||
|
self.detection_statistics["total_local_peaks"] += len(owned_peaks)
|
||||||
|
self.detection_statistics["first_stage_candidates"] += len(first_stage)
|
||||||
|
self.peak_candidate_count = max(self.peak_candidate_count, len(first_stage))
|
||||||
|
if self.observer is not None:
|
||||||
|
self.observer.scores(scores, self.base_sample, self.scan_sample, owned_end, self.decimation)
|
||||||
|
started = time.perf_counter()
|
||||||
|
parts = (front.split_scores(self.reference, self.rule.parts, len(scores))
|
||||||
|
if self.rule.parts and len(scores) else np.empty((len(scores), 0)))
|
||||||
|
passed = (np.all(parts[first_stage] >= part_threshold(self.rule.parts), axis=1)
|
||||||
|
if self.rule.parts else np.ones(len(first_stage), dtype=bool))
|
||||||
|
self.cheap_seconds += time.perf_counter() - started
|
||||||
|
self.detection_statistics["cheap_stage_checked"] += len(first_stage)
|
||||||
|
self.detection_statistics["cheap_stage_rejected"] += int(np.sum(~passed))
|
||||||
|
indexes = first_stage[passed]
|
||||||
|
if self.observer is not None and hasattr(self.observer, "cheap"):
|
||||||
|
for index, keep in zip(first_stage, passed):
|
||||||
|
self.observer.cheap(self.base_sample + int(index) * self.decimation,
|
||||||
|
float(scores[index]), parts[index], bool(keep))
|
||||||
|
started = time.perf_counter()
|
||||||
|
groups = list(peak_groups(indexes, width))
|
||||||
|
self.cluster_seconds += time.perf_counter() - started
|
||||||
|
self.detection_statistics["clusters"] += len(groups)
|
||||||
|
self.peak_cluster_count = max(self.peak_cluster_count, len(groups))
|
||||||
|
self.candidate_state_count = len(indexes)
|
||||||
|
self.peak_eligible_count = max(self.peak_eligible_count, len(indexes))
|
||||||
|
self.working_detection_arrays_peak_bytes = max(self.working_detection_arrays_peak_bytes,
|
||||||
|
front.detection_values.nbytes + scores.nbytes * 4 + parts.nbytes + (len(peaks) + len(indexes)) * 24)
|
||||||
|
emitted = []
|
||||||
|
try:
|
||||||
|
with previous.optimized_context(front):
|
||||||
|
# Внутри группы — самый сильный первым. Отказ не даёт права
|
||||||
|
# подавить альтернативы: пробуем следующий. Только CRC PASS
|
||||||
|
# разрешает прежний пропуск уже принятого физического кадра.
|
||||||
|
ordered = [group[np.argsort(scores[group], kind="stable")[::-1]] for group in groups]
|
||||||
|
attempted_groups = set()
|
||||||
|
for group_number, index in ((number, item) for number, group in enumerate(ordered) for item in group):
|
||||||
|
absolute = self.base_sample + int(index) * self.decimation
|
||||||
|
# Сетка грубого поиска может округлить следующий стык на
|
||||||
|
# одну выборку назад. Не терять соседний физический кадр.
|
||||||
|
if absolute < self.accepted_until - self.decimation:
|
||||||
|
self.detection_statistics["inside_accepted_frame"] += 1
|
||||||
|
continue
|
||||||
|
self.detection_statistics["alternative_checks" if group_number in attempted_groups
|
||||||
|
else "representative_checks"] += 1
|
||||||
|
attempted_groups.add(group_number)
|
||||||
|
self.candidate_count += 1
|
||||||
|
candidate = offline.CoarseCandidate(int(index) * self.decimation, float(scores[index]))
|
||||||
|
started = time.perf_counter()
|
||||||
|
outcome = offline.decode_candidate_from_memmap(
|
||||||
|
self.buffer.view(np.float32), self.buffer.size, candidate, self.candidate_count,
|
||||||
|
read_iq_window=lambda raw, low, high: raw[2 * low:2 * high].view(np.complex64))
|
||||||
|
elapsed = time.perf_counter() - started
|
||||||
|
self.strict_seconds += elapsed
|
||||||
|
if self.observer is not None:
|
||||||
|
self.observer.outcome(self.base_sample, candidate, outcome)
|
||||||
|
if outcome.accepted and outcome.crc == phy.lab045.CRC_PASS and outcome.frame is not None:
|
||||||
|
self.true_strict_seconds += elapsed
|
||||||
|
self.accepted_until = self.base_sample + outcome.next_sample
|
||||||
|
self.packet_count += 1
|
||||||
|
emitted.append(previous.previous.ReceivedPacket(
|
||||||
|
self.base_sample + outcome.refined_start_sample, self.accepted_until,
|
||||||
|
self.total_samples, bytes(outcome.frame.reception.payload_bytes)))
|
||||||
|
else:
|
||||||
|
self.false_strict_seconds += elapsed
|
||||||
|
self.rejected_reasons[outcome.reason] += 1
|
||||||
|
finally:
|
||||||
|
self.candidate_state_count = 0
|
||||||
|
self.blocks_processed += 1
|
||||||
|
self.scan_sample = owned_end
|
||||||
|
retain_start = max(0, self.scan_sample - self.margin)
|
||||||
|
trim = retain_start - self.base_sample
|
||||||
|
if trim > 0:
|
||||||
|
self.buffer = self.buffer[trim:].copy()
|
||||||
|
self.base_sample = retain_start
|
||||||
|
return emitted
|
||||||
|
|
||||||
|
|
||||||
|
def run_functional_tests() -> None:
|
||||||
|
assert CLUSTER_WIDTH_SAMPLES < MIN_FRAME_SAMPLES - 2 * COARSE_ERROR_SAMPLES
|
||||||
|
assert .08 < part_threshold(2) < .09
|
||||||
|
assert StreamingReceiver().finish() == []
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
run_functional_tests()
|
||||||
|
print("Lab052 detector functional checks PASS; RF TX=0")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
157
experiments/lab052_detection_statistics.py
Normal file
157
experiments/lab052_detection_statistics.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""Lab052: распределения и численные проверки после независимых решений RX."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
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.lab052_detector_study import OUTPUT, quantiles, write_result
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
from experiments.lab051_receiver_profile import array_observation
|
||||||
|
|
||||||
|
|
||||||
|
def measure_arrays() -> dict:
|
||||||
|
"""Отдельный короткий RX replay, без смешивания tracing с замером скорости.
|
||||||
|
|
||||||
|
ndarray в Python-locals даёт нижнюю границу наблюдаемых массивов; C/SciPy
|
||||||
|
могут иметь невидимые временные выделения. tracemalloc peak — отдельная
|
||||||
|
измеренная величина, не RSS и не универсальная верхняя граница памяти.
|
||||||
|
"""
|
||||||
|
physical = detection.phy.lab045
|
||||||
|
payload = bytes(range(256))
|
||||||
|
plan = physical.build_transmission_plan(payload)
|
||||||
|
raw = detection.phy.build_parameterized_tx_cs8(plan, detection.phy.PhyMode(500000))
|
||||||
|
components = np.frombuffer(raw, np.int8).astype(np.float32)
|
||||||
|
values = components[0::2] + 1j * components[1::2]
|
||||||
|
random_generator = np.random.default_rng(52040)
|
||||||
|
sigma = np.sqrt(float(np.sum(abs(values)**2)) / plan.frame_symbol_count / (2 * 10**.7))
|
||||||
|
values += sigma * (random_generator.normal(size=len(values)) + 1j*random_generator.normal(size=len(values)))
|
||||||
|
samples = .2 * (random_generator.normal(size=63217 + len(values) + 350000)
|
||||||
|
+ 1j * random_generator.normal(size=63217 + len(values) + 350000))
|
||||||
|
samples[63217:63217 + len(values)] = values
|
||||||
|
samples = samples.astype(np.complex64)
|
||||||
|
|
||||||
|
def replay():
|
||||||
|
receiver = detection.StreamingReceiver()
|
||||||
|
packets = []
|
||||||
|
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())
|
||||||
|
assert len(packets) == 1 and packets[0].payload == payload
|
||||||
|
|
||||||
|
result = {**array_observation(replay), "iq_seconds": len(samples) / 8000000,
|
||||||
|
"packets": 1, "interpretation": "observed Python ndarray lower bound; tracked RX allocation peak; input IQ prepared before tracing",
|
||||||
|
"timing_not_used_as_benchmark": True, "rf_tx": 0}
|
||||||
|
write_result("allocations.json", result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def analyze() -> dict:
|
||||||
|
baseline = json.loads((OUTPUT / "baseline_profile.json").read_text(encoding="utf-8"))
|
||||||
|
optimized = json.loads((OUTPUT / "parts2.json").read_text(encoding="utf-8"))
|
||||||
|
before = np.load(OUTPUT / "baseline_observations.npz")
|
||||||
|
after = np.load(OUTPUT / "parts2_observations.npz")
|
||||||
|
peaks, outcomes = before["peaks"], before["outcomes"]
|
||||||
|
true = outcomes[outcomes[:, 3] == 1]
|
||||||
|
false = outcomes[outcomes[:, 3] == 0]
|
||||||
|
starts = np.sort(true[:, 2].astype(np.int64))
|
||||||
|
new_starts = np.sort(after["outcomes"][after["outcomes"][:, 3] == 1, 2].astype(np.int64))
|
||||||
|
missing = np.setdiff1d(starts, new_starts)
|
||||||
|
unexpected = np.setdiff1d(new_starts, starts)
|
||||||
|
main_scores = []
|
||||||
|
for start in starts:
|
||||||
|
low, high = np.searchsorted(peaks[:, 0], [start - detection.COARSE_ERROR_SAMPLES,
|
||||||
|
start + detection.COARSE_ERROR_SAMPLES], side="left")
|
||||||
|
main_scores.append(float(peaks[low:high, 1].max()))
|
||||||
|
# Окна чистого фона выделяются ТОЛЬКО после независимых принятых кадров.
|
||||||
|
# В этом опыте decoded PHY packet 268B => waveform 92256 samples.
|
||||||
|
frame_samples = (detection.phy.lab045.PREAMBLE_BIT_COUNT + detection.phy.lab045.PHY_HEADER_BIT_COUNT
|
||||||
|
+ detection.phy.lab045.conv.encoded_bit_count((256 + detection.phy.lab045.MIN_PACKET_BYTES) * 8)
|
||||||
|
+ 42) * 16
|
||||||
|
blocks = baseline["detection_profile"]["blocks"]
|
||||||
|
noise_blocks = []
|
||||||
|
for block in blocks:
|
||||||
|
low, high = block["start_sample"], block["end_sample"] + (1280 + 42) * 16
|
||||||
|
if not np.any((starts < high) & (starts + frame_samples > low)):
|
||||||
|
noise_blocks.append(block)
|
||||||
|
noise_statistics = {name: quantiles([block["score_statistics"][name] for block in noise_blocks])
|
||||||
|
for name in ("median", "mad", "p90", "p99", "p999")}
|
||||||
|
cheap = np.load(OUTPUT / "parts2_cheap.npy")
|
||||||
|
accepted_coarse = after["outcomes"][after["outcomes"][:, 3] == 1, 0]
|
||||||
|
is_true = np.isin(cheap[:, 0], accepted_coarse)
|
||||||
|
part_min = cheap[:, 3:].min(axis=1)
|
||||||
|
false_count = len(false)
|
||||||
|
new_false = optimized["detector"]["strict_false_candidates"]
|
||||||
|
result = {
|
||||||
|
"classification": "strict PHY/FEC/CRC verdict first; no diagnostic feedback into RX",
|
||||||
|
"baseline_true_unique_starts": len(np.unique(starts)),
|
||||||
|
"optimized_true_unique_starts": len(np.unique(new_starts)),
|
||||||
|
"missing_true_starts": missing.tolist(), "unexpected_true_starts": unexpected.tolist(),
|
||||||
|
"true_main_peak_scores": quantiles(main_scores), "false_strict_scores": quantiles(false[:, 1]),
|
||||||
|
"baseline_false_refined_positions": len(np.unique(false[:, 2])),
|
||||||
|
"baseline_false_repeated_refinements": len(false) - len(np.unique(false[:, 2])),
|
||||||
|
"noise_only_blocks": len(noise_blocks), "noise_block_statistics": noise_statistics,
|
||||||
|
"split_minimum_true_strict": quantiles(part_min[is_true]),
|
||||||
|
"split_minimum_other_candidates": quantiles(part_min[~is_true]),
|
||||||
|
"strict_false_reduction": false_count / new_false,
|
||||||
|
"strict_false_reduction_fraction": 1 - new_false / false_count,
|
||||||
|
"first_stage_threshold_unchanged": detection.phy.lab047.FIXED_HARDWARE_CANDIDATE_THRESHOLD,
|
||||||
|
"part_threshold": detection.part_threshold(2), "rf_tx": 0,
|
||||||
|
}
|
||||||
|
write_result("statistics.json", result)
|
||||||
|
# Научные статические рисунки сохраняются самостоятельными артефактами.
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
figure, axes = plt.subplots(2, 2, figsize=(12, 8), constrained_layout=True)
|
||||||
|
axis = axes[0, 0]
|
||||||
|
bins = np.linspace(0, 1, 101)
|
||||||
|
axis.hist(false[:, 1], bins, histtype="step", label="Ложные строгие кандидаты", log=True)
|
||||||
|
axis.hist(main_scores, bins, histtype="step", label="Главный пик настоящей преамбулы", log=True)
|
||||||
|
axis.axvline(result["first_stage_threshold_unchanged"], color="gray", linestyle="--", label="Первый порог")
|
||||||
|
axis.set(xlabel="Нормированная корреляция", ylabel="Количество", title="Классификация после PHY/FEC/CRC")
|
||||||
|
axis.legend(fontsize=8)
|
||||||
|
axis = axes[0, 1]
|
||||||
|
for name, label in (("median", "Медиана"), ("mad", "MAD"), ("p99", "Квантиль 99%")):
|
||||||
|
axis.plot([block["start_sample"] / 8000000 for block in noise_blocks],
|
||||||
|
[block["score_statistics"][name] for block in noise_blocks], ".", markersize=2, label=label)
|
||||||
|
axis.set(xlabel="Время IQ, с", ylabel="Корреляционная статистика", title="Блоки чистого фона, определённые после RX")
|
||||||
|
axis.legend(fontsize=8)
|
||||||
|
axis = axes[1, 0]
|
||||||
|
axis.hist(part_min[~is_true], bins, histtype="step", log=True, label="Остальные локальные кандидаты")
|
||||||
|
axis.hist(part_min[is_true], bins, histtype="step", log=True, label="Кандидаты с последующим CRC PASS")
|
||||||
|
axis.axvline(detection.part_threshold(2), color="gray", linestyle="--", label="Порог каждой половины")
|
||||||
|
axis.set(xlabel="Меньшая корреляция двух половин", ylabel="Количество", title="Дешёвая проверка обеих частей")
|
||||||
|
axis.legend(fontsize=8)
|
||||||
|
axis = axes[1, 1]
|
||||||
|
rule_data = json.loads((OUTPUT / "rule_comparison.json").read_text(encoding="utf-8"))["rules"]
|
||||||
|
counts = [row["detector"]["strict_false_candidates"] for row in rule_data]
|
||||||
|
bars = axis.bar(["Группы", "1 часть", "2 части", "3 части"], counts)
|
||||||
|
axis.set_yscale("log")
|
||||||
|
for bar, count in zip(bars, counts):
|
||||||
|
axis.text(bar.get_x() + bar.get_width()/2, count*1.15, str(count), ha="center", fontsize=9)
|
||||||
|
axis.set(ylabel="Ложные строгие проверки", title="Все четыре варианта сохранили 240 пакетов")
|
||||||
|
figure.savefig(OUTPUT / "detector_distributions.png", dpi=160)
|
||||||
|
figure.savefig(OUTPUT / "detector_distributions.svg")
|
||||||
|
plt.close(figure)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def run_functional_tests() -> None:
|
||||||
|
assert detection.part_threshold(2) < detection.part_threshold(3)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
result = analyze()
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
|
print(json.dumps(measure_arrays(), ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
374
experiments/lab052_detector_study.py
Normal file
374
experiments/lab052_detector_study.py
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
"""Lab052: воспроизводимый профиль обнаружителя, только программный IQ.
|
||||||
|
|
||||||
|
Диагностическая история принадлежит эксперименту. Приёмник не получает её
|
||||||
|
обратно; эталон используется только после независимых решений PHY/FEC/CRC.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import cProfile
|
||||||
|
from collections import Counter
|
||||||
|
from dataclasses import replace
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
if __package__ in (None, ""):
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from experiments import lab051_streaming_receiver as previous
|
||||||
|
from experiments import lab051_receiver_performance as performance
|
||||||
|
from experiments.lab051_receiver_profile import process_memory
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
OUTPUT = ROOT / "data/processed/lab052"
|
||||||
|
|
||||||
|
|
||||||
|
def write_result(name: str, value: dict) -> None:
|
||||||
|
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
(OUTPUT / name).write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def sequence_fixture():
|
||||||
|
"""Тот же генератор Lab051, готовность JPEG фиксируется первым замером.
|
||||||
|
|
||||||
|
Прежний опыт не сохранял encode_seconds, поэтому побитную историческую
|
||||||
|
временную сетку восстановить нельзя. Все новые сравнения используют одну
|
||||||
|
сохранённую сетку, один seed и те же байты JPEG, включая реальную encode cost.
|
||||||
|
"""
|
||||||
|
with Image.open(ROOT / "data/raw/lab011_scene.jpg") as source:
|
||||||
|
sequence = performance.video.encode_sequence(source, performance.video.PROFILES[2], 30)
|
||||||
|
path = OUTPUT / "source_fixture.json"
|
||||||
|
if path.exists():
|
||||||
|
fixture = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
for item, recorded in zip(sequence, fixture["images"], strict=True):
|
||||||
|
assert hashlib.sha256(item.jpeg).hexdigest() == recorded["sha256"]
|
||||||
|
return [replace(item, encode_seconds=recorded["encode_seconds"])
|
||||||
|
for item, recorded in zip(sequence, fixture["images"], strict=True)]
|
||||||
|
write_result(path.name, {"seed": 50050, "description": "Lab051 generator; frozen measured JPEG readiness",
|
||||||
|
"images": [{"image_id": item.image_id, "bytes": len(item.jpeg),
|
||||||
|
"encode_seconds": item.encode_seconds,
|
||||||
|
"sha256": hashlib.sha256(item.jpeg).hexdigest()} for item in sequence]})
|
||||||
|
return sequence
|
||||||
|
|
||||||
|
|
||||||
|
def quantiles(values) -> dict:
|
||||||
|
values = np.asarray(values, dtype=np.float64)
|
||||||
|
if not values.size:
|
||||||
|
return {"count": 0}
|
||||||
|
median = float(np.median(values))
|
||||||
|
return {"count": values.size, "min": float(values.min()), "median": median,
|
||||||
|
"mad": float(np.median(np.abs(values - median))),
|
||||||
|
"p90": float(np.quantile(values, .9)), "p99": float(np.quantile(values, .99)),
|
||||||
|
"p999": float(np.quantile(values, .999)), "max": float(values.max())}
|
||||||
|
|
||||||
|
|
||||||
|
class DetectionObserver:
|
||||||
|
"""Полная история только для конечного лабораторного профиля, вне RX."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.peaks = []
|
||||||
|
self.blocks = []
|
||||||
|
self.outcomes = []
|
||||||
|
self.cheap_rows = []
|
||||||
|
|
||||||
|
def scores(self, scores, base, low, high, decimation):
|
||||||
|
indexes = previous.offline._all_local_peak_indexes(scores)
|
||||||
|
offsets = base + indexes * decimation
|
||||||
|
selected = (offsets >= low) & (offsets < high)
|
||||||
|
self.peaks.append(np.column_stack((offsets[selected], scores[indexes[selected]])))
|
||||||
|
first = max(0, (low - base + decimation - 1) // decimation)
|
||||||
|
last = min(len(scores), (high - base + decimation - 1) // decimation)
|
||||||
|
self.blocks.append({"start_sample": low, "end_sample": high,
|
||||||
|
"score_statistics": quantiles(scores[first:last]),
|
||||||
|
"peak_statistics": quantiles(scores[indexes[selected]])})
|
||||||
|
|
||||||
|
def outcome(self, base, candidate, outcome):
|
||||||
|
self.outcomes.append((base + candidate.sample_offset, candidate.score,
|
||||||
|
base + outcome.refined_start_sample, int(outcome.accepted)))
|
||||||
|
|
||||||
|
def cheap(self, absolute, score, parts, passed):
|
||||||
|
self.cheap_rows.append([absolute, score, int(passed), *parts])
|
||||||
|
|
||||||
|
def save(self, name, result):
|
||||||
|
peaks = np.concatenate(self.peaks) if self.peaks else np.empty((0, 2))
|
||||||
|
outcomes = np.asarray(self.outcomes).reshape(-1, 4)
|
||||||
|
np.savez_compressed(OUTPUT / f"{name}_observations.npz", peaks=peaks, outcomes=outcomes)
|
||||||
|
if self.cheap_rows:
|
||||||
|
np.save(OUTPUT / f"{name}_cheap.npy", np.asarray(self.cheap_rows))
|
||||||
|
threshold = previous.phy.lab047.FIXED_HARDWARE_CANDIDATE_THRESHOLD
|
||||||
|
valid = outcomes[:, 3] == 1
|
||||||
|
# Сначала строгий вердикт, затем классификация корреляционной статистики.
|
||||||
|
result["detection_profile"] = {
|
||||||
|
"total_local_peaks": len(peaks), "first_stage_candidates": int(np.sum(peaks[:, 1] >= threshold)),
|
||||||
|
"strict_candidates": len(outcomes), "strict_true_candidates": int(np.sum(valid)),
|
||||||
|
"strict_false_candidates": int(np.sum(~valid)),
|
||||||
|
"candidates_per_rf_second": len(outcomes) / result["iq_seconds"],
|
||||||
|
"false_candidates_per_rf_second": int(np.sum(~valid)) / result["iq_seconds"],
|
||||||
|
"all_local_peaks": quantiles(peaks[:, 1]),
|
||||||
|
"strict_true_scores": quantiles(outcomes[valid, 1]),
|
||||||
|
"strict_false_scores": quantiles(outcomes[~valid, 1]),
|
||||||
|
"blocks": self.blocks,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileBaseline(previous.StreamingReceiver):
|
||||||
|
"""Штатный Lab051 с наблюдением после корреляции и после решения."""
|
||||||
|
|
||||||
|
def __init__(self, observer):
|
||||||
|
super().__init__()
|
||||||
|
self.observer = observer
|
||||||
|
|
||||||
|
def _scan(self, owned_end):
|
||||||
|
front = previous.BlockFrontEnd(self.buffer, owned_end - self.base_sample, self.front_statistics,
|
||||||
|
decimator=self.decimator, base=self.base_sample)
|
||||||
|
original_scores = front.coarse_scores
|
||||||
|
|
||||||
|
def scores(samples, reference):
|
||||||
|
values = original_scores(samples, reference)
|
||||||
|
self.observer.scores(values, self.base_sample, self.scan_sample, owned_end, self.decimation)
|
||||||
|
return values
|
||||||
|
|
||||||
|
front.coarse_scores = scores
|
||||||
|
original_decode = previous.offline.decode_candidate_from_memmap
|
||||||
|
|
||||||
|
def decode(raw, count, candidate, number, **kwargs):
|
||||||
|
outcome = original_decode(raw, count, candidate, number, **kwargs)
|
||||||
|
self.observer.outcome(self.base_sample, candidate, outcome)
|
||||||
|
return outcome
|
||||||
|
|
||||||
|
try:
|
||||||
|
previous.offline.decode_candidate_from_memmap = decode
|
||||||
|
with previous.optimized_context(front):
|
||||||
|
return previous.previous.StreamingReceiver._scan(self, owned_end)
|
||||||
|
finally:
|
||||||
|
previous.offline.decode_candidate_from_memmap = original_decode
|
||||||
|
|
||||||
|
|
||||||
|
def measured_stream(sequence, factory):
|
||||||
|
"""Повторно использовать исходную событийную шкалу с фактическими push times."""
|
||||||
|
instances = []
|
||||||
|
|
||||||
|
def create():
|
||||||
|
receiver = factory()
|
||||||
|
instances.append(receiver)
|
||||||
|
return receiver
|
||||||
|
|
||||||
|
original = performance.fast
|
||||||
|
try:
|
||||||
|
performance.fast = SimpleNamespace(StreamingReceiver=create)
|
||||||
|
result = performance.run_stream(sequence)
|
||||||
|
finally:
|
||||||
|
performance.fast = original
|
||||||
|
receiver = instances[0]
|
||||||
|
result["coarse_seconds"] = receiver.coarse_seconds
|
||||||
|
result["strict_seconds"] = receiver.strict_seconds
|
||||||
|
result["peak_candidates_in_block"] = receiver.peak_candidate_count
|
||||||
|
if hasattr(receiver, "detection_statistics"):
|
||||||
|
result["detector"] = {**dict(receiver.detection_statistics),
|
||||||
|
"strict_true_candidates": receiver.packet_count,
|
||||||
|
"strict_false_candidates": receiver.candidate_count - receiver.packet_count,
|
||||||
|
"strict_checks_per_rf_second": receiver.candidate_count / result["iq_seconds"],
|
||||||
|
"cheap_seconds": receiver.cheap_seconds, "cluster_seconds": receiver.cluster_seconds,
|
||||||
|
"true_strict_seconds": receiver.true_strict_seconds,
|
||||||
|
"false_strict_seconds": receiver.false_strict_seconds,
|
||||||
|
"peak_cluster_count": receiver.peak_cluster_count,
|
||||||
|
"peak_eligible_count": receiver.peak_eligible_count,
|
||||||
|
"candidate_state_after_finish": receiver.candidate_state_count,
|
||||||
|
"detection_arrays_peak_estimate_bytes": receiver.working_detection_arrays_peak_bytes}
|
||||||
|
return result, receiver
|
||||||
|
|
||||||
|
|
||||||
|
def profile_baseline(sequence):
|
||||||
|
observer = DetectionObserver()
|
||||||
|
result, receiver = measured_stream(sequence, lambda: ProfileBaseline(observer))
|
||||||
|
observer.save("baseline", result)
|
||||||
|
result["timing_includes_diagnostic_observer"] = True
|
||||||
|
write_result("baseline_profile.json", result)
|
||||||
|
print(json.dumps({key: value for key, value in result.items() if key in (
|
||||||
|
"candidate_count", "received_images", "rx_physical_packets", "processing_per_rf_second",
|
||||||
|
"coarse_seconds", "strict_seconds")}), flush=True)
|
||||||
|
print(json.dumps({key: value for key, value in result["detection_profile"].items() if key != "blocks"}), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def unprofiled_baseline(sequence):
|
||||||
|
result, receiver = measured_stream(sequence, previous.StreamingReceiver)
|
||||||
|
result["timing_includes_diagnostic_observer"] = False
|
||||||
|
write_result("baseline_stream.json", result)
|
||||||
|
print(json.dumps({key: result[key] for key in ("candidate_count", "processing_seconds",
|
||||||
|
"processing_per_rf_second", "rx_physical_packets", "received_images", "object_jpeg_integrity")}), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def run_functional_tests() -> None:
|
||||||
|
assert quantiles([1, 2, 3])["median"] == 2
|
||||||
|
|
||||||
|
|
||||||
|
def compare_rules(sequence):
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
baseline = json.loads((OUTPUT / "baseline_profile.json").read_text(encoding="utf-8"))
|
||||||
|
results = []
|
||||||
|
for parts in (0, 1, 2, 3):
|
||||||
|
observer = DetectionObserver()
|
||||||
|
rule = detection.DetectorRule(parts=parts)
|
||||||
|
result, receiver = measured_stream(sequence, lambda: detection.StreamingReceiver(rule=rule, observer=observer))
|
||||||
|
observer.save(f"parts{parts}", result)
|
||||||
|
result["rule"] = {"parts": parts, "width_samples": rule.cluster_width_samples,
|
||||||
|
"part_threshold": detection.part_threshold(parts) if parts else None}
|
||||||
|
result["true_candidates_lost"] = baseline["rx_physical_packets"] - result["rx_physical_packets"]
|
||||||
|
false_count = result["detector"]["strict_false_candidates"]
|
||||||
|
baseline_false = baseline["detection_profile"]["strict_false_candidates"]
|
||||||
|
result["false_candidate_reduction"] = baseline_false / false_count if false_count else None
|
||||||
|
result["false_candidate_reduction_fraction"] = 1 - false_count / baseline_false
|
||||||
|
result["timing_includes_diagnostic_observer"] = True
|
||||||
|
write_result(f"parts{parts}.json", result)
|
||||||
|
compact = {key: result[key] for key in ("rule", "received_images", "object_jpeg_integrity",
|
||||||
|
"true_candidates_lost", "false_candidate_reduction", "detector",
|
||||||
|
"processing_per_rf_second", "fresh_display_fps")}
|
||||||
|
results.append(compact)
|
||||||
|
print(json.dumps(compact), flush=True)
|
||||||
|
write_result("rule_comparison.json", {"rules": results, "rf_tx": 0})
|
||||||
|
|
||||||
|
|
||||||
|
def optimized_stream(sequence):
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
results = []
|
||||||
|
for number in range(1, 4):
|
||||||
|
result, receiver = measured_stream(sequence, detection.StreamingReceiver)
|
||||||
|
result["timing_includes_diagnostic_observer"] = False
|
||||||
|
write_result(f"stream_repeat_{number}.json", result)
|
||||||
|
results.append(result)
|
||||||
|
print(json.dumps({"repeat": number, "ratio": result["processing_per_rf_second"],
|
||||||
|
"fresh_fps": result["fresh_display_fps"], "packets": result["rx_physical_packets"]}), flush=True)
|
||||||
|
result = sorted(results, key=lambda row: row["processing_seconds"])[1]
|
||||||
|
result["selection"] = "median processing time of three complete measured passes; one whole timeline"
|
||||||
|
write_result("stream.json", result)
|
||||||
|
print(json.dumps({key: value for key, value in result.items() if key not in (
|
||||||
|
"blocks", "images", "rss_samples", "fresh_image_age_seconds")}), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def profile_cpu(sequence):
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
import pstats
|
||||||
|
from io import StringIO
|
||||||
|
profiler = cProfile.Profile()
|
||||||
|
|
||||||
|
class ProfiledReceiver(detection.StreamingReceiver):
|
||||||
|
def push_iq_block(self, samples):
|
||||||
|
return profiler.runcall(super().push_iq_block, samples)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
return profiler.runcall(super().finish)
|
||||||
|
|
||||||
|
result, receiver = measured_stream(sequence, ProfiledReceiver)
|
||||||
|
profiler.dump_stats(str(OUTPUT / "receiver_cpu.prof"))
|
||||||
|
report = StringIO()
|
||||||
|
stats = pstats.Stats(profiler, stream=report).strip_dirs()
|
||||||
|
stats.sort_stats("tottime").print_stats(30)
|
||||||
|
stats.sort_stats("cumtime").print_stats(30)
|
||||||
|
(OUTPUT / "receiver_cpu.txt").write_text(report.getvalue(), encoding="utf-8")
|
||||||
|
result["timing_includes_cprofile"] = True
|
||||||
|
write_result("cpu_profile.json", result)
|
||||||
|
print(report.getvalue(), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def long_state_test(sequence=None, block_count=2048):
|
||||||
|
"""16,78 с распределённого шума, меняющаяся мощность, без истории в RX."""
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
receiver = detection.StreamingReceiver()
|
||||||
|
random_generator = np.random.default_rng(52030)
|
||||||
|
checkpoints = []
|
||||||
|
accepted = 0
|
||||||
|
for number in range(block_count):
|
||||||
|
components = random_generator.standard_normal((2, receiver.block_samples), dtype=np.float32)
|
||||||
|
scale = (.002, .2, 20., .02)[(number // 257) % 4]
|
||||||
|
values = (components[0] + 1j * components[1]) * scale
|
||||||
|
accepted += len(receiver.push_iq_block(values))
|
||||||
|
assert receiver.buffer.size <= receiver.buffer_limit_samples
|
||||||
|
assert receiver.candidate_state_count == 0
|
||||||
|
if (number + 1) % 256 == 0:
|
||||||
|
checkpoints.append({"block": number + 1, "iq_bytes": receiver.buffer.nbytes,
|
||||||
|
"decimated_bytes": receiver.decimator.values.nbytes,
|
||||||
|
"retained_candidates": receiver.candidate_state_count,
|
||||||
|
"peak_eligible_candidates": receiver.peak_eligible_count,
|
||||||
|
"counter_keys": len(receiver.detection_statistics) + len(receiver.rejected_reasons),
|
||||||
|
**process_memory()})
|
||||||
|
accepted += len(receiver.finish())
|
||||||
|
result = {"blocks": block_count, "iq_seconds": block_count * receiver.block_samples / 8000000,
|
||||||
|
"background_scales": [.002, .2, 20, .02], "accepted_noise_packets": accepted,
|
||||||
|
"peak_retained_iq_bytes": receiver.peak_buffer_samples * 8,
|
||||||
|
"iq_limit_bytes": receiver.buffer_limit_samples * 8,
|
||||||
|
"retained_after_finish_bytes": receiver.buffer.nbytes + receiver.decimator.values.nbytes,
|
||||||
|
"constant_template_bytes": receiver.raw_reference.nbytes + receiver.matched_taps.nbytes + receiver.preamble_bits.nbytes,
|
||||||
|
"candidate_state_after_finish": receiver.candidate_state_count,
|
||||||
|
"peak_eligible_candidates": receiver.peak_eligible_count,
|
||||||
|
"peak_cluster_count": receiver.peak_cluster_count,
|
||||||
|
"detector": dict(receiver.detection_statistics), "strict_candidates": receiver.candidate_count,
|
||||||
|
"processing_seconds": receiver.processing_seconds, "checkpoints": checkpoints,
|
||||||
|
"process_memory": process_memory(), "rf_tx": 0}
|
||||||
|
write_result("memory.json", result)
|
||||||
|
print(json.dumps(result), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def sensitivity_test(sequence=None):
|
||||||
|
"""До/после на одинаковом IQ около границы работоспособности FEC."""
|
||||||
|
from experiments import lab052_candidate_detection as detection
|
||||||
|
physical = previous.phy.lab045
|
||||||
|
payload = bytes(range(256))
|
||||||
|
plan = physical.build_transmission_plan(payload)
|
||||||
|
raw = previous.phy.build_parameterized_tx_cs8(plan, previous.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)))
|
||||||
|
outcomes = []
|
||||||
|
for constructor in (previous.StreamingReceiver, detection.StreamingReceiver):
|
||||||
|
receiver = constructor()
|
||||||
|
packets = []
|
||||||
|
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())
|
||||||
|
# Оценка содержимого только после завершения независимого RX.
|
||||||
|
outcomes.append({"accepted": len(packets),
|
||||||
|
"payload_match": len(packets) == 1 and packets[0].payload == payload,
|
||||||
|
"reasons": dict(receiver.rejected_reasons)})
|
||||||
|
rows.append({"ebn0_db": ebn0, "cfo_hz": cfo, "seed": seed, "amplitude_scale": scale,
|
||||||
|
"baseline": outcomes[0], "optimized": outcomes[1],
|
||||||
|
"lost_baseline_packet": outcomes[0]["payload_match"] and not outcomes[1]["payload_match"]})
|
||||||
|
result = {"cases": rows, "count": len(rows),
|
||||||
|
"baseline_correct": sum(row["baseline"]["payload_match"] for row in rows),
|
||||||
|
"optimized_correct": sum(row["optimized"]["payload_match"] for row in rows),
|
||||||
|
"lost_baseline_packets": sum(row["lost_baseline_packet"] for row in 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 main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--stage", choices=("profile", "baseline", "rules", "stream", "cpu", "memory", "sensitivity"), default="profile")
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
sequence = sequence_fixture()
|
||||||
|
print(f"Lab052 {arguments.stage}: START; RF TX=0", flush=True)
|
||||||
|
{"profile": profile_baseline, "baseline": unprofiled_baseline, "rules": compare_rules, "stream": optimized_stream,
|
||||||
|
"cpu": profile_cpu, "memory": long_state_test, "sensitivity": sensitivity_test}[arguments.stage](sequence)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
211
tests/test_lab052_candidate_detection.py
Normal file
211
tests/test_lab052_candidate_detection.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
"""Свойства локального обнаружителя Lab052 без оборудования и внешних данных."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from experiments import lab052_candidate_detection as lab
|
||||||
|
from experiments.lab050_streaming_receiver import streaming_mode
|
||||||
|
|
||||||
|
|
||||||
|
def waveform(payload: bytes, *, seed: int = 52001, ebn0_db: float = 7, phase: float = .73,
|
||||||
|
cfo_hz: float = 10, scale: float = 1) -> np.ndarray:
|
||||||
|
random_generator = np.random.default_rng(seed)
|
||||||
|
# Генератор Lab045 ограничен payload=256, но приёмный PHY допускает
|
||||||
|
# packet 12..1024. Собираем короткий кадр штатными кодерами для границ.
|
||||||
|
physical = lab.phy.lab045
|
||||||
|
packet = physical.build_packet(payload, physical.MESSAGE_TYPE, physical.SEQUENCE_NUMBER)
|
||||||
|
coded = physical.conv.encode(physical.lab044.bytes_to_bits(packet))
|
||||||
|
permutation = physical.interleaver.build_permutation(len(coded), physical.INTERLEAVER_COLUMNS)
|
||||||
|
bits = np.concatenate((physical.lab044.preamble_bits(), physical.encode_phy_header(len(packet)),
|
||||||
|
physical.interleaver.interleave(coded, permutation)))
|
||||||
|
plan = SimpleNamespace(frame_bits=bits, frame_symbol_count=len(bits))
|
||||||
|
raw = lab.phy.build_parameterized_tx_cs8(plan, lab.phy.PhyMode(500000))
|
||||||
|
components = np.frombuffer(raw, np.int8).astype(np.float32)
|
||||||
|
values = components[0::2] + 1j * components[1::2]
|
||||||
|
values *= np.exp(1j * (phase + 2 * np.pi * cfo_hz * np.arange(len(values)) / 8000000)).astype(np.complex64)
|
||||||
|
energy = float(np.sum(abs(values) ** 2)) / plan.frame_symbol_count
|
||||||
|
sigma = np.sqrt(energy / (2 * 10 ** (ebn0_db / 10)))
|
||||||
|
values += sigma * (random_generator.normal(size=len(values)) + 1j * random_generator.normal(size=len(values)))
|
||||||
|
return (values * scale).astype(np.complex64)
|
||||||
|
|
||||||
|
|
||||||
|
def decode(samples, receiver):
|
||||||
|
packets = []
|
||||||
|
for start in range(0, len(samples), receiver.block_samples):
|
||||||
|
packets.extend(receiver.push_iq_block(samples[start:start + receiver.block_samples]))
|
||||||
|
assert receiver.candidate_state_count == 0 if hasattr(receiver, "candidate_state_count") else True
|
||||||
|
packets.extend(receiver.finish())
|
||||||
|
return packets
|
||||||
|
|
||||||
|
|
||||||
|
def test_cluster_of_false_peaks_has_one_strongest_representative():
|
||||||
|
scores = np.zeros(1600, np.float32)
|
||||||
|
scores[[600, 620, 654, 700, 730]] = [.05, .07, .12, .06, .04]
|
||||||
|
assert lab.cluster_peak_indexes(scores, 256, .03).tolist() == [654]
|
||||||
|
|
||||||
|
|
||||||
|
def test_two_minimum_frames_do_not_compete_even_at_opposite_coarse_errors():
|
||||||
|
distance = lab.MIN_FRAME_SAMPLES - 2 * lab.COARSE_ERROR_SAMPLES
|
||||||
|
scores = np.zeros(8000, np.float32)
|
||||||
|
indexes = [1000, 1000 + distance // 8]
|
||||||
|
scores[indexes] = [.3, .9]
|
||||||
|
assert lab.cluster_peak_indexes(scores, lab.CLUSTER_WIDTH_SAMPLES // 8, .03).tolist() == indexes
|
||||||
|
|
||||||
|
|
||||||
|
def test_equal_peaks_use_reproducible_local_tie():
|
||||||
|
scores = np.zeros(1000, np.float32)
|
||||||
|
scores[[100, 120, 600]] = .2
|
||||||
|
assert lab.cluster_peak_indexes(scores, 100, .03).tolist() == [120, 600]
|
||||||
|
|
||||||
|
|
||||||
|
def test_equal_peak_chain_does_not_extend_cluster_width():
|
||||||
|
scores = np.zeros(500, np.float32)
|
||||||
|
scores[[100, 180, 260]] = .2
|
||||||
|
assert lab.cluster_peak_indexes(scores, 100, .03).tolist() == [180, 260]
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_loud_half_cannot_pass_split_check():
|
||||||
|
random_generator = np.random.default_rng(52002)
|
||||||
|
reference = random_generator.choice([-1., 1.], 2560).astype(np.complex64)
|
||||||
|
samples = (random_generator.normal(size=2560) + 1j * random_generator.normal(size=2560)).astype(np.complex64)
|
||||||
|
samples[:1280] = reference[:1280] * 100
|
||||||
|
assert lab.part_correlations(samples, reference, 1)[0] > lab.part_threshold(1)
|
||||||
|
assert np.any(lab.part_correlations(samples, reference, 2) < lab.part_threshold(2))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("phase,scale", [(0, .001), (.73, 1), (np.pi, 100), (-1.6, .1)])
|
||||||
|
def test_true_preamble_preserves_unknown_gain_phase_and_polarity(phase, scale):
|
||||||
|
random_generator = np.random.default_rng(52003)
|
||||||
|
reference = random_generator.choice([-1., 1.], 2560).astype(np.complex64)
|
||||||
|
samples = reference * np.exp(1j * phase) * scale
|
||||||
|
assert np.all(lab.part_correlations(samples, reference, 2) >= lab.part_threshold(2))
|
||||||
|
|
||||||
|
|
||||||
|
def test_noise_is_rejected_before_strict_timing(monkeypatch):
|
||||||
|
random_generator = np.random.default_rng(52004)
|
||||||
|
samples = (random_generator.normal(size=750000) + 1j * random_generator.normal(size=750000)).astype(np.complex64)
|
||||||
|
|
||||||
|
def forbidden(*args, **kwargs):
|
||||||
|
pytest.fail("Чистый шум этого seed не должен достигнуть строгого тракта")
|
||||||
|
|
||||||
|
monkeypatch.setattr(lab.offline, "decode_candidate_from_memmap", forbidden)
|
||||||
|
receiver = lab.StreamingReceiver()
|
||||||
|
assert decode(samples, receiver) == []
|
||||||
|
assert receiver.detection_statistics["cheap_stage_rejected"] > 0
|
||||||
|
assert receiver.front_statistics["unique_soft_windows"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ebn0_db,scale,cfo", [(7, 1, 10), (2, .02, -100), (1, .2, 100)])
|
||||||
|
def test_weak_true_packet_matches_lab051(ebn0_db, scale, cfo):
|
||||||
|
payload = bytes(range(128))
|
||||||
|
values = waveform(payload, seed=52005, ebn0_db=ebn0_db, scale=scale, cfo_hz=cfo)
|
||||||
|
samples = np.concatenate((np.zeros(63217, np.complex64), values, np.zeros(350000, np.complex64)))
|
||||||
|
baseline = decode(samples, lab.previous.StreamingReceiver())
|
||||||
|
optimized = decode(samples, lab.StreamingReceiver())
|
||||||
|
assert [packet.payload for packet in optimized] == [packet.payload for packet in baseline]
|
||||||
|
assert [packet.payload for packet in baseline] == ([payload] if ebn0_db >= 2 else [])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("block_samples,prefix", [(32749, 32749 - 3), (65536, 65536 - 8),
|
||||||
|
(65536, 65536 + 7)])
|
||||||
|
def test_minimum_adjacent_frames_at_block_boundary_are_both_decoded(block_samples, prefix):
|
||||||
|
# Минимальный packet: пустой payload, заголовок+CRC=12 байт.
|
||||||
|
first = waveform(b"", seed=52006, phase=.4)
|
||||||
|
second = waveform(b"x", seed=52007, phase=-2)
|
||||||
|
assert len(first) == lab.MIN_FRAME_SAMPLES
|
||||||
|
samples = np.concatenate((np.zeros(prefix, np.complex64), first, second,
|
||||||
|
np.zeros(350000, np.complex64)))
|
||||||
|
receiver = lab.StreamingReceiver(block_samples=block_samples)
|
||||||
|
assert [packet.payload for packet in decode(samples, receiver)] == [b"", b"x"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_changing_background_with_weak_packet():
|
||||||
|
random_generator = np.random.default_rng(52008)
|
||||||
|
samples = (random_generator.normal(size=700000) + 1j * random_generator.normal(size=700000)).astype(np.complex64)
|
||||||
|
samples[:180000] *= .001
|
||||||
|
samples[180000:300000] *= 20
|
||||||
|
samples[300000:] *= .03
|
||||||
|
payload = b"varying local background"
|
||||||
|
values = waveform(payload, seed=52009, ebn0_db=2, scale=.02)
|
||||||
|
samples[370013:370013 + len(values)] = values
|
||||||
|
assert [packet.payload for packet in decode(samples, lab.StreamingReceiver())] == [payload]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("true_start", [30000, 65536 - 8, 65536 + 7])
|
||||||
|
def test_false_strong_preamble_does_not_hide_weak_valid_packet(true_start):
|
||||||
|
decoy_start = true_start - 20000
|
||||||
|
samples = np.zeros(700000, np.complex64)
|
||||||
|
decoy = waveform(b"", seed=52022, ebn0_db=100)[:lab.PREAMBLE_SAMPLES + 512]
|
||||||
|
payload = b"actual valid short packet"
|
||||||
|
true = waveform(payload, seed=52021, ebn0_db=7, scale=.2)
|
||||||
|
samples[decoy_start:decoy_start + len(decoy)] += decoy
|
||||||
|
samples[true_start:true_start + len(true)] += true
|
||||||
|
expected = decode(samples, lab.previous.StreamingReceiver())
|
||||||
|
actual = decode(samples, lab.StreamingReceiver())
|
||||||
|
assert [packet.payload for packet in expected] == [payload]
|
||||||
|
assert [(packet.start_sample, packet.payload) for packet in actual] == [
|
||||||
|
(packet.start_sample, packet.payload) for packet in expected]
|
||||||
|
|
||||||
|
|
||||||
|
def test_constant_templates_are_exact_and_restored_on_exception():
|
||||||
|
receiver = lab.StreamingReceiver()
|
||||||
|
owner = lab.phy.lab045.lab044
|
||||||
|
originals = (owner._preamble_reference_samples, owner.preamble_bits, lab.previous.lab043._matched_filter_taps)
|
||||||
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
||||||
|
np.testing.assert_array_equal(receiver.raw_reference, originals[0]())
|
||||||
|
np.testing.assert_array_equal(receiver.preamble_bits, originals[1]())
|
||||||
|
np.testing.assert_array_equal(receiver.matched_taps, originals[2]())
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
with receiver.constant_templates():
|
||||||
|
raise RuntimeError("restore templates")
|
||||||
|
assert originals == (owner._preamble_reference_samples, owner.preamble_bits, lab.previous.lab043._matched_filter_taps)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("count", [1, 2, 3])
|
||||||
|
def test_shared_split_scores_equal_individual_vdot(count):
|
||||||
|
random_generator = np.random.default_rng(52024)
|
||||||
|
values = (random_generator.normal(size=9000) + 1j * random_generator.normal(size=9000)).astype(np.complex64)
|
||||||
|
values[4000:] *= .003
|
||||||
|
reference = random_generator.choice([-1., 1.], size=2560).astype(np.complex64)
|
||||||
|
front = object.__new__(lab.CandidateFrontEnd)
|
||||||
|
front.detection_values = values
|
||||||
|
scores = front.split_scores(reference, count, len(values) - len(reference) + 1)
|
||||||
|
for index in (0, 1001, 3999, len(scores) - 1):
|
||||||
|
np.testing.assert_allclose(scores[index], lab.part_correlations(values[index:index+len(reference)], reference, count),
|
||||||
|
atol=2e-6, rtol=2e-5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounded_candidate_state_and_finish():
|
||||||
|
random_generator = np.random.default_rng(52010)
|
||||||
|
receiver = lab.StreamingReceiver()
|
||||||
|
samples = (random_generator.normal(size=65536) + 1j * random_generator.normal(size=65536)).astype(np.complex64)
|
||||||
|
for index in range(120):
|
||||||
|
receiver.push_iq_block(samples * (.01 if index % 2 else 10))
|
||||||
|
assert receiver.candidate_state_count == 0
|
||||||
|
assert receiver.buffer.size <= receiver.buffer_limit_samples
|
||||||
|
assert receiver.decimator.values.size <= (receiver.buffer_limit_samples + 7) // 8
|
||||||
|
receiver.finish()
|
||||||
|
assert receiver.buffer.size == receiver.decimator.values.size == receiver.candidate_state_count == 0
|
||||||
|
assert receiver.peak_cluster_count <= 20
|
||||||
|
assert receiver.observer is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_20k_does_not_enter_new_filter_and_mode_is_restored():
|
||||||
|
old = lab.previous.lab045.SYMBOL_RATE
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
lab.StreamingReceiver(20000)
|
||||||
|
lab.run_functional_tests()
|
||||||
|
assert lab.previous.lab045.SYMBOL_RATE == old
|
||||||
|
with streaming_mode(lab.phy.PhyMode(20000)):
|
||||||
|
assert lab.previous.lab045.SAMPLES_PER_SYMBOL == 400
|
||||||
|
assert lab.previous.lab045.SYMBOL_RATE == old
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("rule", [dict(parts=4), dict(parts=-1), dict(cluster_width_samples=26720),
|
||||||
|
dict(cluster_width_samples=3)])
|
||||||
|
def test_invalid_rule_rejected(rule):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
lab.DetectorRule(**rule)
|
||||||
Reference in New Issue
Block a user