diff --git a/rfg_adc_plotter/visualization/matplotlib_backend.py b/rfg_adc_plotter/visualization/matplotlib_backend.py index 2abcd57..05b3da0 100644 --- a/rfg_adc_plotter/visualization/matplotlib_backend.py +++ b/rfg_adc_plotter/visualization/matplotlib_backend.py @@ -2,9 +2,11 @@ Визуализация данных с использованием matplotlib. """ +import csv import sys import threading import time +from datetime import datetime from queue import Empty, Queue from typing import Optional, Tuple @@ -57,6 +59,35 @@ def run_matplotlib(args): ring = None # type: Optional[np.ndarray] ring_time = None # type: Optional[np.ndarray] head = 0 + # Медианные данные для вычитания + median_data: Optional[np.ndarray] = None + median_subtract_enabled = False + # CLI параметры для автоматического сохранения/загрузки + ref_out_file = getattr(args, 'ref_out', None) + ref_in_file = getattr(args, 'ref_in', None) + ref_out_saved = False # Флаг, что медиана уже сохранена + + # Автоматическая загрузка медианы при старте + if ref_in_file: + try: + data = [] + with open(ref_in_file, 'r') as f: + reader = csv.reader(f) + next(reader) # Пропускаем заголовок + for row in reader: + if len(row) >= 2: + try: + data.append(float(row[1])) + except ValueError: + continue + if data: + median_data = np.array(data, dtype=np.float32) + median_subtract_enabled = True + print(f"[ref-in] Загружена медиана из {ref_in_file} ({len(median_data)} точек), вычитание включено") + else: + print(f"[ref-in] Предупреждение: файл {ref_in_file} пустой или неверный формат") + except Exception as e: + print(f"[ref-in] Ошибка загрузки {ref_in_file}: {e}") # Авто-уровни цветовой шкалы водопада сырых данных пересчитываются по видимой области. # FFT состояние (полное FFT для отрицательных частот) fft_bins = FFT_LEN @@ -268,6 +299,14 @@ def run_matplotlib(args): nonlocal ring_phase, prev_phase_per_bin, phase_offset_per_bin, y_min_phase, y_max_phase if s is None or s.size == 0 or ring is None: return + + # Применяем вычитание медианы если включено + if median_subtract_enabled and median_data is not None: + take_median = min(s.size, median_data.size) + s_corrected = s.copy() + s_corrected[:take_median] = s[:take_median] - median_data[:take_median] + s = s_corrected + # Нормализуем длину до фиксированной ширины w = ring.shape[1] row = np.full((w,), np.nan, dtype=np.float32) @@ -551,6 +590,31 @@ def run_matplotlib(args): if changed and current_info: status_text.set_text(format_status_kv(current_info)) + # Автоматическое сохранение медианы при накоплении 1000+ свипов + if ref_out_file and not ref_out_saved and ring is not None: + nonlocal ref_out_saved + filled_count = np.count_nonzero(~np.isnan(ring[:, 0])) + if filled_count >= 1000: + try: + # Получаем последние 1000 свипов + ordered = ring if head == 0 else np.roll(ring, -head, axis=0) + recent_sweeps = ordered[-1000:, :] + median_sweep = np.nanmedian(recent_sweeps, axis=0) + + # Сохраняем в файл + with open(ref_out_file, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(['Index', 'Median_Value']) + for i, value in enumerate(median_sweep): + if np.isfinite(value): + writer.writerow([i, float(value)]) + + ref_out_saved = True + print(f"[ref-out] Сохранена медиана 1000 свипов в {ref_out_file}") + status_text.set_text(f"[ref-out] Сохранено в {ref_out_file}") + except Exception as e: + print(f"[ref-out] Ошибка сохранения: {e}") + # Возвращаем обновлённые артисты return (line_obj, img_obj, fft_line_obj, img_fft_obj, phase_line_obj, img_phase_obj, status_text)