Files
radar_system/mimo_calibration.py
T
2026-10-05 17:39:40 +03:00

106 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Фазовая калибровка MIMO-радара по точечному отражателю (шару) и применение её к данным.
Пример использует общий расчёт с интерфейсом программы. Формат файлов калибровки
(cal_*.npy, cal_meta.json) и папки postprocessed сохранён.
Запуск целиком: python mimo_calibration.py (работает по параметрам из блока ниже)
Из другого кода:
import mimo_calibration as mc
cal = mc.compute_calibration(cal_sets, empty_scene_path, mc.Settings(...))
mc.save_calibration(cal, cal_dir)
cal = mc.load_calibration(cal_dir)
s21_corr = mc.apply_calibration(s21, freq, cal) # массив (TX, RX, Nf) в памяти
mc.apply_to_measurement(meas_dir, cal) # preprocessed -> postprocessed
"""
from pathlib import Path
from statistics import mean
from python_app.workflows.mimo_calibration import (
Settings, apply_calibration, apply_to_measurement, calibration_on_grid,
compute_calibration, find_config_profile, load_calibration, load_geometry,
load_s21, save_calibration,
)
# --- что делать -------------------------------------------------------------------------
RUN_CALIBRATION = True # True -> посчитать калибровку по CAL_SETS и записать в CAL_DIR;
# False -> взять готовую калибровку из CAL_DIR
APPLY_TO_DATA = False # True -> записать исправленные данные в <измерение>/postprocessed
# --- калибровочные измерения ------------------------------------------------------------
# Каждая запись — одно положение отражателя. snapshots: список префиксов снимков ('0003')
# или None (все снимки папки). Несколько снимков одной записи усредняются.
# target_scene_m = (X, Y, Z) в осях сцены, метры; для шара — координаты его ЦЕНТРА,
# измеренные физически (а не взятые с некалиброванного изображения):
# X — поперёк, вправо, от начала координат антенн (config_profile, x = 0);
# Y — высота центра отражателя над землёй;
# Z — расстояние вдоль земли от точки под началом координат антенн до отражателя.
CAL_ROOT = Path('/Users/ivan_root/Downloads/Telegram_dwnld/2026-09-31 balls calib/'
'librevna_multi_0.5-6GHz_1031pts_-3dBm_big_ball_h_0cm_z_5m_x_0cm/preprocessed')
CAL_SETS = [
dict(path=CAL_ROOT, snapshots=[f'{index:04d}'], target_scene_m=(0.0, 0.16, 5.20))
for index in range(5)
]
SPHERE_RADIUS_M = 0.16 # радиус шара-отражателя [м]; 0 -> точечная цель
EMPTY_SCENE_PATH = Path('/Users/ivan_root/Downloads/Telegram_dwnld/2026-09-31 balls calib/'
'librevna_multi_0.5-6GHz_1031pts_-3dBm_bkgd_h_0cm/preprocessed')
# Дополнительная пустая сцена для архивного примера.
# Если референс уже удалил фон, задайте None.
# --- геометрия (как в софте построения изображений) ---------------------------------------
INPUT_IDX = [0, 1, 2, 3] # физические входы (Rx), input_pos
OUTPUT_IDX = [0, 1, 2, 3] # физические выходы (Tx), output_pos
ANTENNA_HEIGHT_M = 0.92 # высота начала координат антенн (нижний ряд) над землёй [м]
PITCH_DEG = 10.0 # наклон оси радара ниже горизонта [град]
# --- оценка -----------------------------------------------------------------------------
CAL_F_MIN = 2.0e9 # полоса, по которой оценивается калибровка [Гц]
CAL_F_MAX = 6.0e9
GATE_HALF_M = 0.15 # окно вокруг эха цели, ± по бистатическому пути [м]
GATE_TAPER_M = 0.03 # плавные края окна [м]
COARSE_RANGE_SEARCH_M = 0.6 # грубый поиск эха по дальности перед точной подгонкой, ± по пути [м];
# 0 -> выключен
REFINE_AXES = ('x', 'y', 'z') # какие координаты цели уточнять по данным; если X и Y измерены
# точно — ('z',)
REFINE_RANGE_M = 0.10 # уточнение не дальше этого от найденной точки [м]
APPLY_AMPLITUDE = False # True -> поправка выравнивает и амплитуды антенн
PHASE_MODEL = 'linear' # модель фазы антенны по частоте: 'const' | 'linear' | 'poly3' | 'raw'
# --- куда писать калибровку ---------------------------------------------------------------
CAL_NAME = 'cal_20260931_big_ball_h0_script'
CAL_DIR = Path(__file__).resolve().parent / 'results' / 'calibration' / CAL_NAME
OVERWRITE = True # True -> перезаписать файлы калибровки в CAL_DIR
# --- к каким измерениям применять ---------------------------------------------------------
# Список папок измерений (тех, где лежат preprocessed, raw, results).
APPLY_TO_MEASUREMENTS = [
# '/путь/к/измерению',
]
OVERWRITE_POSTPROCESSED = False # True -> пересоздать postprocessed (только созданный этой калибровкой:
# папка без calibration_applied.json не удаляется никогда)
def main() -> None:
if RUN_CALIBRATION:
cal = compute_calibration(CAL_SETS, EMPTY_SCENE_PATH, Settings(
sphere_radius_m=SPHERE_RADIUS_M, input_idx=tuple(INPUT_IDX), output_idx=tuple(OUTPUT_IDX),
antenna_height_m=ANTENNA_HEIGHT_M, pitch_deg=PITCH_DEG, f_min=CAL_F_MIN, f_max=CAL_F_MAX,
gate_half_m=GATE_HALF_M, gate_taper_m=GATE_TAPER_M, coarse_range_search_m=COARSE_RANGE_SEARCH_M,
refine_axes=tuple(REFINE_AXES), refine_range_m=REFINE_RANGE_M,
apply_amplitude=APPLY_AMPLITUDE, phase_model=PHASE_MODEL))
save_calibration(cal, CAL_DIR, overwrite=OVERWRITE)
q = cal['meta']['quality']
print(f'Калибровка записана в {CAL_DIR}: сфазированность пар на отражателе '
f'{mean(q["coherence_before"]):.2f} -> {mean(q["coherence_final"]):.2f}')
if APPLY_TO_DATA:
cal = load_calibration(CAL_DIR)
targets = APPLY_TO_MEASUREMENTS
if isinstance(targets, (str, Path)): # один путь вместо списка
targets = [targets]
for meas in targets:
post, n_snap, n_fix, _ = apply_to_measurement(meas, cal, OVERWRITE_POSTPROCESSED)
print(f'{Path(meas).name}: {n_snap} снимков, исправлено S21 {n_fix} -> {post}')
if __name__ == '__main__':
main()