106 lines
7.4 KiB
Python
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()
|