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 изображений/с,
|
||||
с неизменными PHY/FEC/CRC и независимыми положительными/отрицательными регрессиями.
|
||||
До отдельного разрешения не выполнять 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.
|
||||
- **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`, бинарные объекты и другие аппаратные исходные
|
||||
|
||||
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