diff --git a/main.py b/main.py index ec55bdb..1789123 100755 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ from datetime import datetime, timedelta import threading import queue from sys import argv +from collections import deque # ================================================================================ # ПАРАМЕТРЫ И КОНСТАНТЫ @@ -45,6 +46,13 @@ FILES_STORED_N_MAX = 100 # Минимально допустимое число точек F0 для принятия данных MIN_F0_POINTS = 100 +# Усреднение B-scan по времени (фон) +BG_AVG_WINDOW_SEC = 5.0 +BG_SUBTRACT_ALPHA = 1.0 +BG_SUBTRACT_ENABLED = True +# Метод расчёта фона: 'median' или 'mean' +BG_BASELINE_METHOD = 'median' + # ПЕРЕЧИСЛЕНИЕ ТИПОВ ДАННЫХ DATA_TYPE_RAW = "RAW" DATA_TYPE_SYNC_DET = "SYNC_DET" @@ -453,6 +461,13 @@ class DataAnalyzerApp: self.processed_count = 0 self.skipped_count = 0 + # Буфер последних колонок для фонового усреднения B-scan + self.bscan_buffer = deque() # элементов: (np.ndarray col, datetime t, str type) + self.bg_avg_window_sec = BG_AVG_WINDOW_SEC + self.bg_subtract_alpha = BG_SUBTRACT_ALPHA + self.bg_subtract_enabled = BG_SUBTRACT_ENABLED + self.bg_method = BG_BASELINE_METHOD + # ============================================================================ # ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА # ============================================================================ @@ -854,6 +869,13 @@ class DataAnalyzerApp: # Собираем данные для отображения display_cols = [] display_types = [] # Тип каждого столбца + # Предрасчёт фонового усреднения по строкам текущего окна + bg_vec = None + if self.bg_subtract_enabled and self.bscan_buffer: + desired_row_start = row_min + desired_row_end = row_max + 1 + bg_vec = self.compute_background_slice(desired_row_start, desired_row_end) + alpha = self.bg_subtract_alpha if self.bg_subtract_enabled else 0.0 for i in range(col_min, col_max): col_data = self.B_scan_data[i] col_type = self.B_scan_types[i] @@ -862,7 +884,13 @@ class DataAnalyzerApp: row_end = min(row_max + 1, len(col_data)) row_start = min(row_min, len(col_data) - 1) if row_end > row_start: - display_cols.append(col_data[row_start:row_end]) + seg = col_data[row_start:row_end] + if bg_vec is not None and col_type != "GAP": + take = min(len(seg), len(bg_vec)) + if take > 0 and alpha != 0.0: + seg = seg.copy() + seg[:take] = seg[:take] - alpha * bg_vec[:take] + display_cols.append(seg) display_types.append(col_type) display_times = self.B_scan_times[col_min:col_max] @@ -1054,6 +1082,10 @@ class DataAnalyzerApp: self.B_scan_times.append(current_time) self.B_scan_types.append(data_type) self.last_file_time = current_time + # Сохраняем в буфер для фонового усреднения (не добавляем GAP) + if data_type != "GAP": + self.bscan_buffer.append((np.asarray(data_col, dtype=float), current_time, data_type)) + self.prune_bscan_buffer(current_time) return # Используем переменный интервал между файлами @@ -1084,6 +1116,61 @@ class DataAnalyzerApp: self.B_scan_times.append(current_time) self.B_scan_types.append(data_type) self.last_file_time = current_time + # Сохраняем в буфер для фонового усреднения (не добавляем GAP) + if data_type != "GAP": + self.bscan_buffer.append((np.asarray(data_col, dtype=float), current_time, data_type)) + self.prune_bscan_buffer(current_time) + + def prune_bscan_buffer(self, now_time: datetime): + """Удаляет столбцы старше окна усреднения.""" + try: + threshold = now_time - timedelta(seconds=self.bg_avg_window_sec) + while self.bscan_buffer and self.bscan_buffer[0][1] < threshold: + self.bscan_buffer.popleft() + except Exception: + pass + + def compute_background_slice(self, row_start: int, row_end: int) -> np.ndarray: + """Пер-строчная статистика (median/mean) по окну последних колонок для строк [row_start:row_end).""" + n_rows = max(0, row_end - row_start) + if n_rows <= 0 or not self.bscan_buffer: + return np.zeros((0,), dtype=float) + + # Собираем сегменты для указанного диапазона строк + segments = [] + for col, t, tname in self.bscan_buffer: + if col is None: + continue + L = len(col) + if L <= row_start: + continue + take = min(n_rows, L - row_start) + if take <= 0: + continue + seg = np.asarray(col[row_start:row_start + take], dtype=float) + segments.append(seg) + + if not segments: + return np.zeros((n_rows,), dtype=float) + + # Формируем матрицу (num_cols x n_rows) с NaN, заполняя доступные значения + num_cols = len(segments) + M = np.full((num_cols, n_rows), np.nan, dtype=float) + for i, seg in enumerate(segments): + take = min(n_rows, seg.shape[0]) + if take > 0: + M[i, :take] = seg[:take] + + # Агрегирование по времени (ось 0), игнорируя NaN + with np.errstate(all='ignore'): + if getattr(self, 'bg_method', 'median') == 'mean': + bg = np.nanmean(M, axis=0) + else: + bg = np.nanmedian(M, axis=0) + + # Заменим NaN на 0 (строки без данных в окне) + bg = np.nan_to_num(bg, nan=0.0) + return bg def compute_fft(self): """Вычисляем FFT спектр."""